C ++ WINAPI共享内存数组的结构

我试图通过使用WINAPI共享命名内存共享一个结构数组。 我能够创build和pipe理共享内存,但是当试图共享结构数组时,数组的大小在读取时总是为0。

下面是我写的testing代码,它应该写/读10个条目的数组,但即使这是失败的。 然而,我的目标是编写/读取一个包含2个dynamic数组的结构的dynamic数组以及目前已经包含的信息。

我知道我不应该分享进程之间的指针,因为他们可以指向一个随机值。 因此,我正在使用新的arrays分配内存。

这是我迄今为止:

在这两个过程中共享:

#define MEMSIZE 90024 typedef struct { int id; int type; int count; } Entry; 

过程1:

 extern HANDLE hMapObject; extern void* vMapData; std::vector<Entry> entries;//collection of entries BOOL DumpEntries(TCHAR* memName) {//Returns true, writing 10 entries int size = min(10, entries.size()); Entry* eArray = new Entry[size]; for (int i = 0; i < size; i++) { eArray[i] = entries.at(i); } ::hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MEMSIZE, memName); if (::hMapObject == NULL) { return FALSE; } ::vMapData = MapViewOfFile(::hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, MEMSIZE); if (::vMapData == NULL) { CloseHandle(::hMapObject); return FALSE; } CopyMemory(::vMapData, eArray, (size * sizeof(Entry))); UnmapViewOfFile(::vMapData); //delete[] eArray; return TRUE; } 

过程2:

 BOOL ReadEntries(TCHAR* memName, Entry* entries) {//Returns true reading 0 entries HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, memName); if (hMapFile == NULL) { return FALSE; } Entry* tmpEntries = (Entry*)(MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 10 * sizeof(Entry))); if (tmpEntries == NULL) { CloseHandle(hMapFile); return FALSE; } entries = new Entry[10]; for (int i = 0; i < 10; i++) { entries[i] = tmpEntries[i]; } UnmapViewOfFile(tmpEntries); CloseHandle(hMapFile); return TRUE; } 

写入10个条目似乎是工作,但当试图读取内存它成功返回和数组的大小为0,如下所示:

 Entry* entries = NULL; if (ReadEntries(TEXT("Global\Entries"), entries)) { int size = _ARRAYSIZE(entries); out = "Succesfully read: " + to_string(size);// Is always 0 } 

所以我的问题是,我做错了什么? 我在两个进程之间共享相同的结构,我为要写入的条目分配新的内存,并复制大小为10 * sizeof(Entry);的内存10 * sizeof(Entry); 。 当试图读取我也尝试读取10 * sizeof(Entry); 字节并将数据转换为Entry* 。 有什么我失踪? 所有的帮助是欢迎的。

我对你的代码做了一些修改:

过程1:

 BOOL DumpEntries(TCHAR* memName) { int size = entries.size() * sizeof(Entry) + sizeof(DWORD); ::hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, memName); if (::hMapObject == NULL) { return FALSE; } ::vMapData = MapViewOfFile(::hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, size); if (::vMapData == NULL) { CloseHandle(::hMapObject); return FALSE; } (*(DWORD*)::vMapData) = entries.size(); Entry* eArray = (Entry*)(((DWORD*)::vMapData) + 1); for(int i = entries.size() - 1; i >= 0; i--) eArray[i] = entries.at(i); UnmapViewOfFile(::vMapData); return TRUE; } 

过程2:

 BOOL ReadEntries(TCHAR* memName, Entry** entries, DWORD &number_of_entries) { HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, memName); if (hMapFile == NULL) { return FALSE; } DWORD *num_entries = (DWORD*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (num_entries == NULL) { CloseHandle(hMapFile); return FALSE; } number_of_entries = *num_entries; if(number_of_entries == 0) { // special case: when no entries was found in buffer *entries = NULL; return true; } Entry* tmpEntries = (Entry*)(num_entries + 1); *entries = new Entry[*num_entries]; for (UINT i = 0; i < *num_entries; i++) { (*entries)[i] = tmpEntries[i]; } UnmapViewOfFile(num_entries); CloseHandle(hMapFile); return TRUE; } 

处理2(使用例):

 void main() { Entry* entries; DWORD number_of_entries; if(ReadEntries(TEXT("Global\\Entries", &entries, number_of_entries) && number_of_entries > 0) { // do something } delete entries; } 

变化:

  • 我没有使用静态大小(MEMSIZE)当我映射内存时,我正在计算确切的内存requiered
  • 我把一个“标题”映射到内存,一个DWORD发送到处理缓冲区中的2个条目
  • 你的ReadEntries定义是错误的,我修复它改变条目*条目**

笔记:

  • 您需要在进程2调用ReadEntries之前关闭进程1中的:: hMapObject句柄
  • 您需要删除在进程2中为ReadEntries返回的条目内存,然后才能使用它
  • 这个代码只能在同一个windows用户下工作,如果你想通过用户进程传递一个服务(例如),你需要在CreateFileMapping过程中处理SECURITY_ATTRIBUTES成员

基于粗略的检查,这段代码似乎试图将包含std::string的结构映射到共享内存中,以供其他进程使用。

不幸的是,这个冒险在它开始之前就注定了。 即使你的数组​​长度正确传递,我期望其他进程立即崩溃,只要它甚至闻到其他进程试图映射到共享内存段的std::string

std::string s是非平凡的类。 一个std::string维护指向实际字符串数据的缓冲区的内部指针; 与缓冲区获取分配在堆上。

你明白sizeof(std::string)不会改变,不管字符串是包含5个字符还是“War And Peace”的全部内容,对吗? 停下来思考一会儿,怎么可能,只需要几个字节来存储一个std::string

一旦你想了一会儿,就会明白为什么将一个进程的std::string映射到一个共享内存段,然后试图通过另一个进程来抓取它们,是不行的。

唯一可以实际映射到/从共享内存的东西是普通的旧数据 ; 尽管在某些情况下,你也可以逃避集合。

恐怕问题只在于_ARRAYSIZE宏。 我无法在MSDN中找到它,但是在其他页面ARRAYSIZE找到了_countofARRAYSIZE引用。 全部被定义为sizeof(array)/sizeof(array[0]) 。 问题在于它只对定义为Entry entries[10] 数组有效,对于指向这样的数组的指针则不适用。 从技术上讲,当你声明:

 Entry* entries; 

sizeof(entries)sizeof(Entry *) ,它是指针的大小。 它小于结构的大小,所以整数除法的结果是… 0!

无论如何,目前的代码还有其他的问题。 通过共享内存交换可变大小的数组的正确方法是使用一个包含大小的辅助结构,并声明数组本身是不完整的

 struct EntryArray { size_t size; Entry entries[]; }; 

你可以这样转储它:

 BOOL DumpEntries(TCHAR* memName) {//Returns true, writing 10 entries int size = min(10, entries.size()); EntryArray* eArray = (EntryArray *) malloc(sizeof(EntryArray) + size * sizeof(Entry)); for (int i = 0; i < size; i++) { eArray->entries[i] = entries.at(i); } eArray->size = size; ::hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MEMSIZE, memName); if (::hMapObject == NULL) { return FALSE; } ::vMapData = MapViewOfFile(::hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, MEMSIZE); if (::vMapData == NULL) { CloseHandle(::hMapObject); return FALSE; } CopyMemory(::vMapData, eArray, (sizeof(EntryArray) + size * sizeof(Entry))); UnmapViewOfFile(::vMapData); free(eArray); return TRUE; } 

你可以注意到,由于struct的最后一个成员是一个不完整的数组,所以它被分配了0的大小,所以你必须分配struct的大小和数组的大小。

然后你可以用这种方式从内存中读取它:

 size_t ReadEntries(TCHAR* memName, Entry*& entries) {//Returns the number of entries or -1 if error HANDLE hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, memName); if (hMapFile == NULL) { return -1; } EntryArray* eArray = (EntryArray*)(MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 10 * sizeof(Entry))); if (eArray == NULL) { CloseHandle(hMapFile); return -1; } entries = new Entry[10]; // or even entries = new Entry[eArray->size]; for (int i = 0; i < 10; i++) { // same: i<eArray->size ... entries[i] = eArray->entries[i]; } UnmapViewOfFile(eArray); CloseHandle(hMapFile); return eArray.size; } 

但是在这里你应该注意一些不同之处。 由于eArray消失时条目的数量会丢失,因此它将作为函数的返回值传递。 而且你想修改作为第二个参数传递的指针,你必须通过引用传递它(如果你通过值传递,你将只能改变一个本地拷贝,并且在函数返回后仍然在原始变量中有NULL)。

代码中还有一些可能的改进,因为向量entries可以作为参数传递给DumpEntries ,所以向量entries是全局的,而hMapObject也可以是全局的,因为它可以被函数返回。 在DumpObject您可以通过直接在共享内存中构建DumpObject来避免复制:

 HANDLE DumpEntries(TCHAR* memName, const std::vector<Entry>& entries) { //Returns HANDLE to mapped file (or NULL), writing 10 entries int size = min(10, entries.size()); HANDLE hMapObject = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, MEMSIZE, memName); if (hMapObject == NULL) { return NULL; } void * vMapData = MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, MEMSIZE); if (vMapData == NULL) { CloseHandle(hMapObject); return NULL; } EntryArray* eArray = (EntryArray*) vMapData; for (int i = 0; i < size; i++) { eArray->entries[i] = entries.at(i); } eArray->size = size; UnmapViewOfFile(vMapData); return hMapObject; } 

最后但并非最不重要的一点是,反斜杠\是一个字符串中特殊的引用字符,它必须引用它自己。 所以你应该写。 TEXT("Global\\Entries")