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

时间:2016-06-17 21:29:14

标签: c++ arrays windows shared-memory

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

下面是我编写的测试代码,它应该写入/读取10个条目的数组,但即使这样也是失败的。然而,我的目标是编写/读取包含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个条目似乎正在工作,但在尝试读取内存时,它会成功返回并返回大小 数组的数字是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);个字节并将数据转换为Entry*。有什么我想念的吗?欢迎大家帮忙。

3 个答案:

答案 0 :(得分:2)

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

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

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

你明白sizeof(std::string)不会改变,字符串是否包含五个字符,还是“战争与和平”的全部内容,对吧?停下来思考一下,这是怎么可能的,只需几个字节即可存储std::string

一旦你想到这一点,为什么将一个进程的std::string s映射到一个共享内存段,然后尝试通过另一个进程抓取它们应该变得非常清楚,这是行不通的。< / p>

唯一可以实际映射到共享内存/来自共享内存的是plain old data;虽然你可以在某些情况下使用聚合物来逃避。

答案 1 :(得分:2)

我担心这个问题只存在于_ARRAYSIZE宏。我无法在MSDN中找到它,但我在其他页面中找到了_countofARRAYSIZE的引用。所有都定义为sizeof(array)/sizeof(array[0])。问题是它只对定义为Entry entries[10] true 数组有意义,而对于指向此类数组的指针则。从技术上讲,当你声明:

Entry* entries;

sizeof(entries)sizeof(Entry *),是指针的大小。它小于struct的大小,因此整数除法的结果是...... 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;
}

您可以注意到,由于结构的最后一个成员是一个不完整的数组,因此它被分配了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消失时,当条目数丢失时,它将作为函数的返回值传递。并且你希望修改作为第二个参数传递的指针,你必须通过引用传递它(如果你通过值传递它,你只会更改一个本地副本,并且在函数之后仍然在原始变量中有NULL返回)。

您的代码仍有一些可能的改进,因为向量entries在作为参数传递给DumpEntries时是全局的,而hMapObject也可以是全局的由函数返回。在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")

答案 2 :(得分:1)

我对您的代码进行了一些更改:

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

PROCESS 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),我正在计算所需的内存
  • 我放了一个&#34;标题&#34;到内存映射,一个DWORD用于发送到缓冲区中的进程2个条目
  • 您的ReadEntries定义错误,我修正了将Entry *更改为Entry **

注意:

  • 您需要在进程2调用ReadEntries
  • 之前关闭进程1中的:: hMapObject句柄
  • 在使用之前,您需要删除为进程2中的ReadEntries返回的条目内存
  • 此代码仅适用于相同的Windows用户,如果要与用户进程通信服务(例如),则需要在CreateFileMapping过程中处理SECURITY_ATTRIBUTES成员