Question

I have a very small example of two programs, one to write a shared memory segment and another to read from it. I realise there are potentially issues with std::string (and other containers) so have tried boost::interprocess::string which is a boost::containers::string. I am pretty sure this is missing something really fundamental and simple, but cannot see it!

In any case the synopsis is that when the string is small (I think though larger than SSO) running the first program writes the memory and the second reads perfectly well. If, however, I make the string pretty large as in the example here then the read program segfaults. I have success with this if both read and write are in the same process, but different functions (so not sharing any data except via ipc. Code below writeipc.cc

#include <boost/interprocess/managed_shared_memory.hpp> 
#include <boost/interprocess/containers/string.hpp>
#include <iostream> 
#include <utility> 

int main() 
{
  typedef boost::interprocess::string bi_string;

  boost::interprocess::shared_memory_object::remove("Test"); 
  boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::create_only, "Test", 65536); 


  bi_string* i = managed_shm.construct<bi_string>("string")("abcdefghijklmnopqrstuvwxyzaskl;dfjaskldfjasldfjasdl;fkjwrotijuergonmdlkfsvmljjjjjjjjjjjjjj");  // make smaller (ie "jjjjjjjjjjjjjj" and test passes 
  std::cout << "inserting into shm" << *i << std::endl; 

  std::pair<bi_string*, size_t> q = managed_shm.find<bi_string>("string"); 
  std::cout << *q.first << std::endl;
 while(true)
    std::cout << "still running"; // hack to keep process running (not required)
}

readipc.cc

#include <boost/interprocess/managed_shared_memory.hpp> 
#include <boost/interprocess/containers/string.hpp>
#include <iostream> 

int main() 
{
  typedef boost::interprocess::string bi_string;
  boost::interprocess::managed_shared_memory managed_shm(boost::interprocess::open_only, "Test"); 

  std::pair<bi_string*, std::size_t> p = managed_shm.find<bi_string>("string"); 
  std::cout << "existing value" << *p.first << std::endl; 

}
Was it helpful?

Solution

OK, the answer was allocator related.

I am posting what I believe is the answer to allow others to quickly find out how to begin to use IPC. I have found many examples about the web that do not explain the process very well and give the impression that using normal strings etc. will be OK, it is until you read from a different process. So to fully implement a solution some small extra steps are required.

In essence you cannot use an allocator as provided by normal stl type containers. This is documented here

Where is this being allocated?

Boost.Interprocess containers are placed in shared memory/memory mapped files, etc... using two mechanisms at the same time:

Boost.Interprocess construct<>, find_or_construct<>... functions. These functions place a C++ object in the shared memory/memory mapped

file. But this places only the object, but not the memory that this object may allocate dynamically. Shared memory allocators. These allow allocating shared memory/memory mapped file portions so that containers can allocate dynamically fragments of memory to store newly inserted elements.

This means that to place any Boost.Interprocess container (including Boost.Interprocess strings) in shared memory or memory mapped files, containers must:

Define their template allocator parameter to a Boost.Interprocess allocator.
Every container constructor must take the Boost.Interprocess allocator as parameter.
You must use construct<>/find_or_construct<>... functions to place the container in the managed memory.

If you do the first two points but you don't use construct<> or find_or_construct<> you are creating a container placed only in your process but that allocates memory for contained types from shared memory/memory mapped file.

I have created a couple of example functions which can be found here

What was missing above was

namespace bi = boost::interprocess;
typedef bi::allocator<char, bi::managed_shared_memory::segment_manager> CharAllocator;
typedef bi::basic_string<char, std::char_traits<char>, CharAllocator> bi_string;

This is now a string that can be stored when we can get an allocator for it. This means getting allocator from your memory segment like this

  bi::managed_shared_memory segment(bi::create_only, "shm name",
                                                     65536);
  // Create an object of Type initialized to type
  CharAllocator charallocator(segment.get_segment_manager());

now charallocator can be used to construct a string type that will work in the shared memory location and be able to be read back properly. The string is created as:

 bi_string str(charallocator);

Then you can assign a cstring to this. So for instance std::string test("a test string");

str = test.c_str();

the if you construct a segment your string will be stored

segment.construct<bi_string>("some name")(str)

You can store many segments in a shared memory store. I hope this helps others. The code is all available with tests in the link above.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top