首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Windows 進程間通訊 (IPC) 實戰:共享記憶體 CreateFileMapping 完整範例

     編輯:關於C++

為什麼選擇共享記憶體?

Windows 進程間通訊 (IPC) 方式眾多:管道、訊息佇列、RPC、Socket、郵件槽、共享記憶體。其中 **共享記憶體 (Memory Mapped File / File Mapping)** 具備:

  • 最高效能: 直接讀寫記憶體,零複製、無上下文切換開銷。
  • 雙向隨機存取: 不像管道只能循序讀寫,可任意位移讀寫結構體、陣列、環狀緩衝區。
  • 跨進程同步配合: 搭配 Mutex/Event/Semaphore 實現生產者-消費者模型。
  • 支援大資料量: 可映射 GB 級檔案 (受限於虛擬位址空間)。

核心 API 流程

  1. 建立/開啟映射物件: CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, name)
    - INVALID_HANDLE_VALUE 表示不關聯實體檔案,純記憶體共享。
    - name 為具名物件,跨進程同名即共享同一區塊。
    - 回傳 HANDLE,GetLastError()==ERROR_ALREADY_EXISTS 表示非首次建立。
  2. 映射視圖至位址空間: MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
    - 回傳進程虛擬位址指標 (LPVOID),各進程位址通常不同,但指向同一實體頁框。
    - 可多次呼叫映射不同位移/大小視圖。
  3. 讀寫資料: 直接透過指標存取 (需型別轉換)。
  4. 同步機制 (關鍵): 多進程並發讀寫**必須**加鎖。常用 CreateMutex (跨進程) 保護關鍵區。
  5. 清理: UnmapViewOfFile(ptr) → CloseHandle(hMap)。最後一個進程關閉時釋放實體記憶體。

封裝工具函數:GetShare / ReleaseShare


// 共用標頭檔
struct TShareHandle { HANDLE hMap; void* pView; };

// 取得共享記憶體指標 (首次建立者自動清零)
TShareHandle GetShare(const char* name, DWORD size) {
  TShareHandle sh = {0,0};
  sh.hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, name);
  if (!sh.hMap) return sh;
  sh.pView = MapViewOfFile(sh.hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
  if (sh.pView && GetLastError() != ERROR_ALREADY_EXISTS)
    ZeroMemory(sh.pView, size); // 僅建立者初始化
  return sh;
}

void ReleaseShare(TShareHandle& sh) {
  if (sh.pView) UnmapViewOfFile(sh.pView);
  if (sh.hMap) CloseHandle(sh.hMap);
  sh.hMap = 0; sh.pView = 0;
}

完整雙進程範例:寫入端 (Writer) / 讀取端 (Reader)

共用資料結構定義 (SharedDef.h)


#pragma once
#define SHARED_MEM_NAME "Global\\MyIpcSharedMem_2024"
#define MUTEX_NAME      "Global\\MyIpcMutex_2024"
#define MAX_MSG_LEN     1024

struct TSharedData {
  volatile LONG writerPid;      // 寫入者 PID
  volatile LONG sequence;       // 遞增序號
  char  message[MAX_MSG_LEN];   // 承載資料
  // 可擴充:時間戳、命令碼、狀態旗標...
};

寫入端 (Writer.exe)


#include 
#include 
#include "SharedDef.h"

int main() {
  TShareHandle sh = GetShare(SHARED_MEM_NAME, sizeof(TSharedData));
  if (!sh.pView) { puts("建立共享記憶體失敗"); return 1; }
  HANDLE hMutex = CreateMutex(NULL, FALSE, MUTEX_NAME);
  if (!hMutex) { ReleaseShare(sh); return 1; }

  TSharedData* pData = (TSharedData*)sh.pView;
  pData->writerPid = GetCurrentProcessId();

  for (int i = 1; i <= 10; ++i) {
    WaitForSingleObject(hMutex, INFINITE);
    pData->sequence = i;
    sprintf_s(pData->message, "Message #%d from Writer PID %lu", i, pData->writerPid);
    ReleaseMutex(hMutex);
    printf("寫入: %s\n", pData->message);
    Sleep(1000);
  }

  ReleaseMutex(hMutex);
  CloseHandle(hMutex);
  ReleaseShare(sh);
  return 0;
}

讀取端 (Reader.exe)


#include 
#include 
#include "SharedDef.h"

int main() {
  TShareHandle sh = GetShare(SHARED_MEM_NAME, sizeof(TSharedData));
  if (!sh.pView) { puts("開啟共享記憶體失敗 (Writer 未啟動?)"); return 1; }
  HANDLE hMutex = OpenMutex(SYNCHRONIZE, FALSE, MUTEX_NAME);
  if (!hMutex) { ReleaseShare(sh); return 1; }

  TSharedData* pData = (TSharedData*)sh.pView;
  LONG lastSeq = 0;

  while (true) {
    WaitForSingleObject(hMutex, INFINITE);
    LONG seq = pData->sequence;
    if (seq != lastSeq) {
      lastSeq = seq;
      printf("讀取: Seq=%ld, PID=%ld, Msg=%s\n", seq, pData->writerPid, pData->message);
      if (seq >= 10) break; // 示範結束條件
    }
    ReleaseMutex(hMutex);
    Sleep(200); // 輪詢間隔
  }

  CloseHandle(hMutex);
  ReleaseShare(sh);
  return 0;
}

進階主題:環狀緩衝區 (Ring Buffer) 實現高吞吐隊列

適用於單一生產者、單一消費者 (SPSC) 無鎖 (需記憶體屏障) 或輕量鎖場景:


struct TRingBuffer {
  volatile LONG head;  // 寫入位置
  volatile LONG tail;  // 讀取位置
  volatile LONG mask;  // size-1 (size 必須為 2 的冪次)
  char  data[1];       // 彈性陣列,實際配置 sizeof(TRingBuffer) + size
};

// 寫入 (生產者)
bool RingPush(TRingBuffer* rb, const void* src, DWORD len) {
  LONG h = rb->head, t = rb->tail, m = rb->mask;
  if ((((h + 1) & m) == t)) return false; // 滿
  memcpy(&rb->data[h], src, len);
  // MemoryBarrier(); // 確保寫入可見
  rb->head = (h + 1) & m;
  return true;
}

// 讀取 (消費者)
bool RingPop(TRingBuffer* rb, void* dst, DWORD len) {
  LONG h = rb->head, t = rb->tail, m = rb->mask;
  if (h == t) return false; // 空
  memcpy(dst, &rb->data[t], len);
  // MemoryBarrier();
  rb->tail = (t + 1) & m;
  return true;
}

配合 CreateEvent (資料就緒/空間可用) 實現阻塞式等待,效能遠超管道/訊息佇列。

常見坑點與避坑指南

坑點後果解決方案
忘記同步 (Mutex/Event)資料撕裂、讀到半更新結構、競態條件**永遠**搭配跨進程 Mutex 保護複雜結構讀寫
名稱衝突/權限不足CreateFileMapping 失敗 (ERROR_ACCESS_DENIED)使用 Global\\ 前綴需管理員/SeCreateGlobalPrivilege;一般用戶用 Local\\ 或無前綴 (Session 範圍)
大小不一致映射視圖大小與結構不符,導致越界雙方包含同一標頭檔定義 sizeof(TSharedData),統一大小
指標跨進程傳遞指標僅在當前進程有效,其他進程解引用崩潰**嚴禁**在共享區儲存指標,只存 offset/index/handle/純資料
未釋放資源共享記憶體洩漏,重啟前無法重建同名物件RAII 封裝、atexit、進程結束自動清理 (但最好顯式釋放)
64 位元指標截斷32/64 位元混合部署時結構對齊/大小不同避免在共享結構使用指標;固定大小類型 (int32_t, int64_t);#pragma pack 對齊

安全性考量

  • DACL (Discretionary ACL): CreateFileMapping 第二參數傳入 SECURITY_ATTRIBUTES 指定 LPSECURITY_DESCRIPTOR,限制特定用戶/群組存取,防止惡意進程竄改/讀取敏感資料。
  • 完整性等級: 低完整性進程 (IE Protected Mode, AppContainer) 無法存取高完整性建立的 Global\\ 物件。跨完整性等級通訊需明確設定 SACL 或使用 AppContainer 允許的命名空間。
  • 輸入驗證: 讀取端務必驗證序號、長度、魔數、校驗和,防止惡意寫入端造成緩衝區溢位或邏輯錯誤。

替代方案快速選型

  • 簡單通知/命令: PostMessage (需視窗)、WM_COPYDATA、RegisterWindowMessage。
  • 雙向串流/網路透明: 命名管道 (CreateNamedPipe)、本地 TCP/Unix Domain Socket (AF_UNIX Win10+)。
  • 結構化 RPC/COM: MIDL 定義介面、RPC/COM、WinRT 元件。
  • 海量資料/持久化: 記憶體對應檔案 (CreateFileMapping 關聯實體檔案)、資料庫 (SQLite 共享 WAL 模式)。

結語

共享記憶體是 Windows 高效能 IPC 的基石。掌握 CreateFileMapping/MapViewOfFile 核心流程、嚴謹套用跨進程同步原語、妥善處理命名空間與權限,即可構建極低延遲、高吞吐的進程間資料交換通道。本文提供的封裝函數與雙進程範例可直接整合至 C++ Builder、Visual C++、Delphi、C# (P/Invoke) 等專案中。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved