c++ WINAPI 共享内存结构数组

c++ WINAPI Shared Memory array of structs

我正在尝试使用 WINAPI 通过共享命名内存来共享结构数组。我能够创建和管理共享内存,但是当尝试共享结构数组时,读取时数组的大小始终为 0。

下面是我编写的测试代码,它应该 write/read 一个包含 10 个条目的数组,但即使这样也是失败的。然而,我的目标是 write/read 包含 2 个动态数组的动态结构数组以及它们目前已包含的信息。

我知道我不应该在进程之间共享指针,因为它们可能指向随机值。因此,我正在使用 new 为数组分配内存。

这是我目前拥有的:

在两个进程中共享:

#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 个条目似乎有效,但是当尝试读取内存时它 returns 成功并且大小 数组的值为 0,如下所示:

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

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

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

不幸的是,这场冒险还没开始就注定了。即使你让数组长度正确传递,我希望另一个进程会立即崩溃,只要它甚至闻到另一个进程试图映射到共享内存段的 std::string

std::string 非常重要 类。 std::string 维护指向保存实际字符串数据的缓冲区的内部指针;在堆上分配缓冲区。

你明白 sizeof(std::string) 不会改变,无论字符串包含五个字符,还是 "War And Peace" 的全部内容,对吧?停下来想一想,这怎么可能,只需要几个字节就可以存储 std::string?

一旦你想一想,就会crystal清楚为什么将一个进程的 std::string 映射到共享内存段,然后试图由另一个进程获取它们是不会工作。

唯一可以实际映射to/from共享内存的是plain old data;尽管在某些情况下您也可以摆脱聚合。

恐怕问题只出在_ARRAYSIZE宏上。我无法在 MSDN 中真正找到它,但我在其他页面中找到了 _countofARRAYSIZE 的引用。全部定义为 sizeof(array)/sizeof(array[0])。问题是它只对定义为 Entry entries[10]true 数组有意义,但对指向此类数组的指针 not 才有意义。从技术上讲,当您声明时:

Entry* entries;

sizeof(entries)sizeof(Entry *) 即指针的大小。它小于结构的大小,因此整数除法的结果是... 0!

总之,目前的代码还有其他问题。通过共享内存交换可变大小数组的正确方法是使用包含大小的辅助结构和数组本身声明为 incomplete:

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

您可以注意到,由于结构的最后一个成员是一个不完整的数组,它分配了 0 个大小,因此您必须分配结构的大小 + 数组的大小。

然后您可以这样从内存中读取它:

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 消失时条目数丢失,它作为函数的 return 值传递。并且你想修改作为第二个参数传递的指针,你必须通过引用传递它(如果你通过值传递它,你只会改变一个本地副本并且原始中仍然有NULL函数后的变量 returns).

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

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")

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

进程 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),我正在准确计算所需的内存
  • 我将一个 "header" 映射到内存,一个 DWORD 用于发送以处理缓冲区中的 2 个条目
  • 您的 ReadEntries 定义有误,我将 Entry* 更改为 Entry**

备注:

  • 您需要在进程 2 调用 ReadEntries 之前关闭进程 1 中的 ::hMapObject 句柄
  • 您需要删除为进程 2 中的 ReadEntries 返回的条目内存,然后再使用它
  • 此代码仅在同一 windows 用户下有效,如果您想与用户进程通信服务(例如),您需要处理 CreateFileMapping 过程中的 SECURITY_ATTRIBUTES 成员