簡體   English   中英

c ++ WINAPI共享內存結構數組

[英]c++ WINAPI Shared Memory array of structs

我正在嘗試使用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); 在嘗試閱讀時我也嘗試閱讀10 * sizeof(Entry); 字節並將數據轉換為Entry* 有什么我想念的嗎? 歡迎所有幫助。

基於粗略檢查,此代碼似乎嘗試將包含std::string的結構映射到共享內存,供另一個進程使用。

不幸的是,這場冒險在它開始之前注定要失敗。 即使您正確地傳遞了數組長度,我希望其他進程立即崩潰,只要它聞到另一個進程試圖映射到共享內存段的std::string

std::string s是非平凡的類。 std::string維護內部指針,指向保存實際字符串數據的緩沖區; 緩沖區在堆上分配。

你明白sizeof(std::string)不會改變,字符串是否包含五個字符,還是“戰爭與和平”的全部內容,對吧? 停下來思考一下,這是怎么可能的,只需幾個字節就可以存儲一個std::string

一旦你想到這一點,為什么將一個進程的std::string s映射到一個共享內存段,然后嘗試通過另一個進程抓取它們應該變得非常清楚,這是行不通的。

可以實際映射到共享內存/從共享內存映射的唯一內容是普通舊數據 ; 雖然你可以在某些情況下逃避聚合。

我擔心這個問題只存在於_ARRAYSIZE宏中。 我無法在MSDN中找到它,但我在其他頁面ARRAYSIZE找到了_countofARRAYSIZE引用。 所有都定義為sizeof(array)/sizeof(array[0]) 問題是它只適用於定義為Entry entries[10] 真實數組, 而不適用於指向此類數組的指針。 技術上,當您聲明:

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

我對你的代碼做了一些修改:

過程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),我正在計算所需的內存
  • 我把一個“標題”放到內存映射中,一個DWORD用於發送到緩沖區中處理2個條目的數量
  • 您的ReadEntries定義是錯誤的,我修復它將Entry *更改為Entry **

筆記:

  • 在進程2調用ReadEntries之前,需要在進程1中關閉:: hMapObject句柄
  • 在使用之前,您需要刪除在進程2中為ReadEntries返回的條目內存
  • 此代碼僅在相同的Windows用戶下工作,如果要與用戶進程通信服務(例如),則需要在CreateFileMapping過程中處理SECURITY_ATTRIBUTES成員

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM