Как я могу вернуть непрозрачный дескриптор (void* или dword), который можно привести обратно к элементу значения, хранящемуся в карте boost::interprocess?

Меня немного смущает семантика кучи и сравнения по значению и по ссылке, связанная с помещением ключа std::string и большого значения struct в такой контейнер, как boost::interprocess::map.

Вот моя ситуация и некоторые определения типов, которые я использую:

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;

Вот как я вставляю AreaValueType (это typedef для std::pair):

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

Я полагаю, что приведенный выше код копирует A, который является парой std::pair в моем локальном стеке (не разделяемой памяти) в область разделяемой памяти. Могу ли я получить дескриптор этой общей области памяти внутри boost::interprocess::map или я ограничен извлечением этой записи целиком и сохранением ее целиком? (Другими словами, могу ли я сохранить что-то вроде структуры в межпроцессной карте boost, а затем обновить один байт внутри этой записи, или мне нужно только обновить всю запись, заменив все байты в структуре DATA_AREA_DESC совершенно новыми байт.)

Некоторые дополнительные уточнения:

  1. У меня есть простой старый API экспорта ANSI C DLL, который внутренне использует C++ и Boost::interprocess::map. Ожидается, что функция создаст элемент на карте, а затем вернет дескриптор. Как я могу вставить что-то в boost::interprocess::map, а затем вернуть дескриптор этой сущности для пользователей, не использующих C++, желательно привести к void* или unsigned long? Все, что я могу сделать, это получить данные из общей памяти, просматривая значение ключа std::string, и записать новую запись в память. Вместо этого я хотел бы иметь возможность сохранить ссылку на объект общей памяти.

  2. Если я не могу сделать это напрямую, как я могу сделать это косвенно? Я полагаю, что мог бы сохранить неразделяемую память std::vector и выделить неразделяемую память std::string, содержащую значение areaKey, которая является std::string, а затем выполнить приведение void* элемент обратно в std::string, а затем использовать его для извлечения записи из области общей памяти. Все это кажется большим количеством работы, чем должно быть строго необходимо для чего-то столь элементарного. Возможно, boost::interprocess::map не подходит для моих требований?

Что я пробовал? Это компилируется, но я понятия не имею, правильно ли я это делаю. Почему-то я чувствую себя некрасиво, разыменовывая ::iterator, возвращенный из find, а затем сразу же получая его адрес, например:

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

Обновить Все вышеперечисленное работает. Однако очень разумный совет в ответе ниже НЕ работает. Использование boost::interprocess::string приводит к полному сбою и сбоям во время выполнения. Использование std::string, которое не имеет права работать, если только авторы Boost специально не закодировали поддержку std::string, на самом деле прекрасно работает.


person Warren P    schedule 24.03.2013    source источник
comment
ну, вы могли бы использовать at() вместо find(), тогда не было бы разыменования :)   -  person EHuhtala    schedule 27.03.2013
comment
Я думаю, вы правы.   -  person Warren P    schedule 28.03.2013


Ответы (1)


Если handle должен быть указателем на std::pair в общей памяти, тогда ваш код будет работать при условии, что вы знаете, что areaKey находится на карте. В этом нет ничего плохого, за исключением того, что вам не нужно явное приведение (и если вы приведете, то static_cast<void*>() будет предпочтительнее).

Я не использовал boost::interprocess, но я думаю, вам нужно будет использовать boost::interprocess::string или std::basic_string с распределителем не по умолчанию для вашего ключа. Если boost::interprocess не делает что-то необычное под капотом, использование std::string поместит указатель на локальную память (для строкового буфера) в разделяемую память, которая не будет иметь смысла в другом процессе.

Вот тестовая программа, использующая карту со строковыми ключами:

#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;
}

Запустите его без аргументов, чтобы создать новый сегмент разделяемой памяти, и хотя бы с одним аргументом, чтобы открыть существующий сегмент разделяемой памяти (вызов без аргументов уже должен выполняться). В обоих случаях программа будет итеративно считывать ключ из stdin, вставлять запись в карту и записывать содержимое в stdout.

person rhashimoto    schedule 31.03.2013
comment
Мне не нужно делиться нетипизированными значениями между процессами, мне просто нужно предоставить нетипизированный API внутри одного клиента или сервера, и я буду внутренне использовать API-интерфейсы Boost C++, которые будут сведены к Plain Old C для интерфейса DLL ABI. (Application binary interface) целей. - person Warren P; 01.04.2013
comment
Я понимаю, что вам просто нужен старый добрый void* для вашего дескриптора. Во втором абзаце я пытался сказать, что ваша внутренняя карта не будет корректно работать в процессах, которые совместно используют ее, если ее строковый ключ не действителен для разных процессов, и она не будет действительна, если это std::string. То есть, если у вас есть два процесса, пытающихся найти значения на карте, или один процесс вставляет значения, а другой читает значения, это, вероятно, не будет работать с обычным std::string в качестве ключа карты. Если вам не нужна эта возможность, то я не понимаю, почему карта является общей. - person rhashimoto; 01.04.2013
comment
Таким образом, тип Boost MAP (boost::interprocess::map) должен использовать boost::interprocess::map<boost::interprocess::string,...>, верно? Я ценю совет, поскольку я не думал об этом. - person Warren P; 01.04.2013
comment
Да, это кажется самым безопасным. Это то, что я пытался донести. Вы можете думать о ключевой строке как об общем контейнере. - person rhashimoto; 01.04.2013
comment
Хотя я хотел бы отметить ваш ответ как правильный, поведение, которое я наблюдал при использовании Std::String и Boost::Interprocess::String, полностью противоположно тому, что я ожидал. Использование Boost::InterProcess::String в качестве типа ключа на карте приводит к сбоям, нарушениям доступа и повреждению данных в простых тестах, в то время как использование std::string работает, и это указывает на некую ВОЛШЕБНУЮ реализацию карты межпроцессного взаимодействия. . - person Warren P; 06.04.2013
comment
Это странно. Я вижу как раз обратное — boost::interprocess::string works from separate processes and std::string` этого не делает. Я добавил свою тестовую программу в ответ. Вы можете изменить определение SHARED_STRING, чтобы изменить реализацию строки. Я хочу проверить две вещи: (1) вы тестируете вставку и поиск из разных процессов и (2) что вы создаете строки общей памяти с распределителем для вашего сегмента общей памяти. - person rhashimoto; 07.04.2013
comment
Да, я вставляю из процесса №1 и читаю обратно из процесса №2. Я совершенно удивлен. Я постараюсь опубликовать минимальный нерабочий пример. - person Warren P; 07.04.2013
comment
У меня есть возможное объяснение. Поскольку вы упомянули библиотеки DLL, я предполагаю, что вы используете Visual C++, и согласно это, по крайней мере, некоторые версии VC++ реализуют std::string с оптимизация коротких строк. Если это так и ваши строки ключей достаточно короткие, то их хранилище не будет выделяться динамически. Вы можете проверить это, попробовав ключи длиннее sizeof(std::string). - person rhashimoto; 07.04.2013