Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I return an opaque handle (void* or dword) that can be cast back to a value element stored in a boost::interprocess map?

I am a bit confused by the heap and by-value-versus-by-reference semantics involved in putting a std::string key and a large struct value into a container like boost::interprocess::map.

Here is my situation, and some typedefs I'm using:

typedef std::string     AreaKeyType;     
typedef DATA_AREA_DESC          AreaMappedType; // DATA_AREA_DESC is a big struct.
typedef std::pair<const AreaKeyType, AreaMappedType> AreaValueType;
typedef boost::interprocess::allocator<AreaValueType, boost::interprocess::managed_shared_memory::segment_manager> AreaShmemAllocator;
typedef boost::interprocess::map<AreaKeyType, AreaMappedType, std::less<AreaKeyType>, AreaShmemAllocator> AreaMap;

Here is how I'm inserting AreaValueType (which is a typedef for std::pair):

 AreaValueType A(areaKey, arearec);
 anAreaMap->insert(A); 

I believe the above code copies A which is an std::pair on my local (non shared memory) stack into a shared memory area. Can I get a handle to that shared memory area inside the boost::interprocess::map or am I limited to fetching that record back whole and storing it whole? (In other words, can I store something like a structure into a boost interprocess map and then update a single byte inside that record, or do I have to only update the entire record by replacing all the bytes in a DATA_AREA_DESC struct, with entirely new bytes.)

Some further clarification:

  1. I have a plain old ANSI C DLL export api that internally uses C++ and Boost::interprocess::map. The function is expected to create an item in the map and then return a handle. How can I insert something into the boost::interprocess::map and then return a handle to that entity, to non-C++ users, preferably cast to void* or unsigned long? All I can seem to do is fetch stuff from shared memory by looking up the std::string key value, and write a new record into memory. I'd like to instead be able to keep a reference to the shared memory object around.

  2. If I can't directly do that, how would I do it indirectly? I suppose I could keep a non-shared-memory std::vector, and allocate a non-shared memory std::string holding the value of the areaKey, which is a std::string, and then do a cast of the void* item back to std::string and then use that to fetch a record out of the shared memory area. That all seems like more work than should be strictly necessary for something so elementary. Maybe boost::interprocess::map isn't the right choice for my requirements?

What have I tried? This, which compiles, but I have no idea if I am doing this right. Somehow I feel ugly inside dereferencing an ::iterator returned from find, and then immediately taking its address like so:

void ** handle; // actually a parameter in my api.
*handle = (void*)&(*anAreaMap->find(areaKey));

Update The above works. The very sensible advice in the answer below does NOT work however. Using boost::interprocess::string results in complete and total failure and crashes at runtime. Using std::string, which has no right to work unless the authors of Boost coded std::string support in especially, actually works great.

like image 276
Warren P Avatar asked Mar 24 '13 21:03

Warren P


1 Answers

If handle is supposed to be a pointer to the std::pair in shared memory then your code will work provided you know that areaKey is in the map. There's nothing wrong with it except you don't need the explicit cast (and if you do cast then static_cast<void*>() would be preferred).

I haven't used boost::interprocess but I think you will need to use boost::interprocess::string or a std::basic_string with a non-default allocator for your key. Unless boost::interprocess does something fancy under the hood, using std::string will put a pointer to local memory (for the string buffer) into shared memory which won't be meaningful in another process.

Here's a test program that uses a map with string keys:

#include <iostream>
#include <string>
#include <boost/foreach.hpp>
#include <boost/format.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>

namespace bi = boost::interprocess;

#define SHARED_STRING 1 // set to 1 for interprocess::string, 0 for std::string
static const char *SHARED_MEMORY_NAME = "MySharedMemory";
static const char *SHARED_MAP_NAME = "MySharedMap";

int main(int argc, char *argv[]) {
#if SHARED_STRING
   typedef bi::allocator<char, bi::managed_shared_memory::segment_manager> CharAllocator;
   typedef bi::basic_string<char, std::char_traits<char>, CharAllocator> Key;
#else
   typedef std::allocator<char> CharAllocator;
   typedef std::basic_string<char, std::char_traits<char>, CharAllocator> Key;
#endif

   typedef int Mapped;
   typedef std::pair<const Key, Mapped> Value;
   typedef bi::allocator<Value, bi::managed_shared_memory::segment_manager> MapAllocator;
   typedef bi::map<Key, Mapped, std::less<Key>, MapAllocator> Map;

   bi::managed_shared_memory *segment;
   Map *map;
   if (argc <= 1) {
      // Create new shared memory segment.
      bi::shared_memory_object::remove(SHARED_MEMORY_NAME);
      segment = new bi::managed_shared_memory(bi::create_only, SHARED_MEMORY_NAME, 65536);

      MapAllocator mapAllocator(segment->get_segment_manager());
      map = segment->construct<Map>(SHARED_MAP_NAME)(std::less<Key>(), mapAllocator);
      assert(map);
   }
   else {
      // Open existing shared memory segment.
      segment = new bi::managed_shared_memory(bi::open_only, SHARED_MEMORY_NAME);

      map = segment->find<Map>(SHARED_MAP_NAME).first;
      assert(map);
   }

#if SHARED_STRING
   CharAllocator charAllocator(segment->get_segment_manager());
#else
   CharAllocator charAllocator;
#endif
   while (true) {
      std::string input;
      if (!getline(std::cin, input))
         break;

      map->insert(std::make_pair(Key(input.begin(), input.end(), charAllocator), 0));

      BOOST_FOREACH(const Value& value, *map)
         std::cout << boost::format("('%s',%d)\n") % value.first % value.second;
   }

   delete segment;
   bi::shared_memory_object::remove(SHARED_MEMORY_NAME);

   return 0;
}

Run it with no arguments to create a new shared memory segment and with at least one argument to open an existing shared memory segment (a no-argument invocation must already be running). In both cases, the program will iteratively read a key from stdin, insert an entry into the map, and write contents to stdout.

like image 95
rhashimoto Avatar answered Sep 30 '22 18:09

rhashimoto