---
# System prepended metadata

title: 共享記憶體 - File Mapping
tags: [共享記憶體, C++, Shared Memory]

---

# 共享記憶體 - File Mapping
在同一個專案下，如果想在多個`cpp`中，使用同一個`int`變數，我們也許會宣告一個全域變數(global variable)，讓不同`cpp`都能存取到該變數。

但開發大型系統時，通常不會只有單一個專案，而是有很多個`exe`或`dll`，而在這些分開執行的`行程(process)`中，也經常需要共用一些變數或資料。

然而，每個`process`在記憶體中，都會被分配到一塊屬於自己的記憶體空間，並與其它`process`互相隔離、互不干擾，所以在一個`process`宣告的全域變數，並不能讓其它`process`使用。

若想在不同`process`之間，共享同一份資料，其中一個方式就是使用 ++**File Mapping**++。

## File Mapping (檔案對映)
> File Mapping(檔案對映)可以用來讀取大型檔案，作法是在記憶體中創建一塊空間，並對映到硬碟中的檔案。若是不對映任何檔案，就能利用這塊記憶體空間，實作共享記憶體的功能。

![image](https://hackmd.io/_uploads/SkTuVge3Jx.png)

:::success
##### 💡 你可能也會查到「Memory-Mapped File (記憶體對映檔)」，中國稱「內存映射」，是一樣的。
:::

以下範例，須建立兩個++各自獨立的專案++`ProcessA`和`ProcessB` :
- 在`ProcessA`++建立記憶體對映檔++並寫入資料。
- 在`ProcessB`++讀取記憶體對映檔++的資料，達到共享資料的作用。

### ✔程式碼 - ProcessA

>[!Tip] 關鍵步驟  
>#### 1. CreateFileMapping : 在實體記憶體，建立`Memory-Mapped File`(記憶體對映檔)
>#### 2. MapViewOfFile : 將記憶體對映檔，對映(map)到此`行程(Process)`的虛擬記憶體
>#### 3. UnmapViewOfFile : 取消對映
>#### 4. CloseHandle : 釋放記憶體

```cpp=
void ProcessAFunction()
{
  char szMsg[256] = "Message from ProcessA."; //要共享給ProcessB的資料
  char szErr[256]; //記錄error訊息

  HANDLE hMapFile;
  LPCTSTR pBuf;

  //建立記憶體對映檔(實體記憶體)
  hMapFile = CreateFileMapping(
                INVALID_HANDLE_VALUE,   // use paging file
                NULL,                   // default security
                PAGE_READWRITE,         // read/write access
                0,                      // maximum object size (high-order DWORD)
                BUF_SIZE,               // maximum object size (low-order DWORD)
                "MyFileMappingObject"); // name of mapping object

  if(hMapFile == NULL) {
    sprintf(szErr, "Could not create file mapping object (%d).", GetLastError());
    return;
  }

  //將對映檔，對映(map)到此行程的虛擬記憶體
  pBuf = (LPTSTR) MapViewOfFile(hMapFile, // handle to map object
                     FILE_MAP_ALL_ACCESS, // read/write permission
                     0,
                     0,
                     BUF_SIZE);

  if(pBuf == NULL) {
    sprintf(szErr, "Could not map view of file (%d).", GetLastError());
    CloseHandle(hMapFile);
    return;
  }

  //將資料寫入對映檔
  CopyMemory((PVOID)pBuf, szMsg, 256);
  //關閉虛擬記憶體的對映
  UnmapViewOfFile(pBuf);
  //須在別處釋放對映檔
  //CloseHandle(hMapFile);
  return;
}
```

#### ✅程式說明
- 第10~16行，使用`CreateFileMapping()`，在++實體記憶體++中建立`Memory-Mapped File`(記憶體對映檔)，並回傳對映檔的`handle`。
📍`CreateFileMapping(檔案控制代碼, 安全屬性, 存取權限, 對映檔大小, 對映檔大小, 對映檔名稱);`
  - `INVALID_HANDLE_VALUE`，代表沒有跟任何檔案有關聯，因為File Mapping其實有其它用途，這裡只單純用來建立一個對映檔的空間。
  - `NULL`，代表預設安全屬性。
  - `PAGE_READWRITE`，代表建立的對映檔可以被讀/寫。
  - `0, BUF_SIZE`，第一個固定填0，第二個填共享資料的大小。
  - `"MyFileMappingObject"`，其它`process`可以用這個名稱，找到相同的記憶體對映檔。
- 第18~21行，判斷對映檔是否建立失敗，可以用`GetLastError()`取得錯誤代碼。
- 第24~28行，使用`MapViewOfFile()`將對映檔，對映到此`process`的++虛擬記憶體++，並回傳對映檔的++起始位址(address)++。
📍`MapViewOfFile(對映檔控制代碼, 所需權限, 檔案讀取偏移, 檔案讀取偏移, 讀取大小);`
  - `hMapFile`，要使用的對映檔`handle`。
  - `FILE_MAP_ALL_ACCESS`，代表可讀/寫。
  - `0, 0`，代表讀取位置不偏移。
  - `BUF_SIZE`，要讀取對映檔內容的大小。
- 第30~34行，判斷對映檔是否對映(讀取)失敗，可以用`GetLastError()`取得錯誤代碼。
- 第37行，將要共享的資料，複製到對映檔。
- 第39行，使用`UnmapViewOfFile()`取消此`process`虛擬記憶體中的對映。
📍`UnmapViewOfFile(對映檔起始位址);`
  - 參數必須是`MapViewOfFile()`回傳的位址。
  - 實體記憶體的對映檔++不會++消失。

### ✔程式碼 - ProcessB

>[!Tip] 關鍵步驟  
>#### 1. OpenFileMapping: 取得`Memory-Mapped File`(記憶體對映檔)
>#### 2. MapViewOfFile : 將記憶體對映檔，對映到此`行程(Process)`的虛擬記憶體
>#### 3. UnmapViewOfFile : 取消對映
>#### 4. CloseHandle : 釋放記憶體

```cpp=
void ProcessBFunction()
{
  char szMsg[256];
  char szErr[256];

  HANDLE hMapFile;
  LPCTSTR pBuf;

  //開啟記憶體對映檔(實體記憶體)
  hMapFile = OpenFileMapping(
                FILE_MAP_ALL_ACCESS,    // read/write access
                FALSE,                  // do not inherit the name
                "MyFileMappingObject"); // name of mapping object

  if(hMapFile == NULL) {
    sprintf(szErr, "Could not open file mapping object (%d).", GetLastError());
    return;
  }

  //將對映檔，對映(map)到此行程的虛擬記憶體
  pBuf = (LPTSTR) MapViewOfFile(hMapFile, // handle to map object
                     FILE_MAP_ALL_ACCESS, // read/write permission
                     0,
                     0,
                     BUF_SIZE);

  if(pBuf == NULL) {
    sprintf(szErr, "Could not map view of file (%d).", GetLastError());
    CloseHandle(hMapFile);
    return;
  }

  //使用對映檔內的資料
  sprintf(szMsg, "%s", pBuf);
  //關閉虛擬記憶體的對映
  UnmapViewOfFile(pBuf);
  //釋放實體記憶體的對映檔
  CloseHandle(hMapFile);
  return;
}
```

#### ✅程式說明
- 第10~13行，使用`OpenFileMapping()`開啟記憶體對映檔，並回傳對映檔的`handle`。
📍`OpenFileMapping(存取權限, 繼承權限, 對映檔名稱);`
  - `FILE_MAP_ALL_ACCESS`，代表可讀/寫。
  - `FALSE`，固定填false。
  - `"MyFileMappingObject"`，要開啟的對映檔名稱。
- 第15~18行，判斷對映檔是否開啟失敗，可以用`GetLastError()`取得錯誤代碼。
- 第21~25行，使用`MapViewOfFile()`將對映檔，對映到此`process`的++虛擬記憶體++，並回傳對映檔的++起始位址(address)++。
- 第27~31行，判斷對映檔是否對映(讀取)失敗，可以用`GetLastError()`取得錯誤代碼。
- 第34行，印出對映檔的內容。
- 第36行，使用`UnmapViewOfFile()`取消此`process`虛擬記憶體中的對映。
- 第38行，使用`CloseHandle()`釋放實體記憶體中的對映檔`handle`(實體記憶體的對映檔++會++消失)。
📍`CloseHandle(對映檔控制代碼);`
  - 若有其它`process`正在使用對映檔，則對映檔不會被釋放，也不會跳出錯誤。

### 📌其它說明
>[!Caution] 以下是不負責任說明，若有錯誤請務必指正。

- **File Mapping**在記憶體中，是怎麼運作的?
  1. `A`使用`CreateFileMapping`後，會在++實體++記憶體創建一個`File Mapping Object(檔案對映物件)`，並且回傳這個物件的`handle(控制代碼)`。
  2. `A`使用`MapViewOfFile`，將這個`File Mapping Object`對映到`A`的++虛擬++記憶體中，並且回傳虛擬記憶體中，此區塊的指標，並利用此指標，將要共享的資料寫入記憶體中。
  3. `A`使用完畢後，須使用`UnmapViewOfFile`釋放虛擬記憶體的區塊。
  4. `B`使用`OpenFileMapping`，來取得指定`File Mapping Object`的`handle`。
  5. `B`也使用`MapViewOfFile()`，將`File Mapping Object`對映到`B`的++虛擬++記憶體中，並且回傳虛擬記憶體中，此區塊的指標，並利用此指標，讀取到`A`寫入的資料。
  6. `B`使用完畢後，也須使用`UnmapViewOfFile`釋放虛擬記憶體的區塊。
  
  📍==當所有操作結束後，`A`和`B`都必須呼叫一次`CloseHandle`釋放`File Mapping Object`。==
  
  - 雖然`File Mapping Object`，只有當`A`呼叫`CreateFileMapping`時，在記憶體被建立一次。
  - 但++建立`File Mapping Object`後，會產生一個「計數器」，記錄這個`File Mapping Object`被引用幾次++。
  - 若`A`先用`CloseHandle`釋放`File Mapping Object`，但是`B`還沒有釋放，則`File Mapping Object`並不會真的被釋放，只會讓`A`的`handle`失效而已。
  - 當所有引用該`File Mapping Object`的`process`都釋放`handle`(計數器歸零)，`File Mapping Object`才會真正從記憶體中被釋放。

- `ProcessA`使用`CloseHandle`後，如何再次操作`File Mapping Object`?
  - 若`File Mapping Object`還沒被釋放，只需使用`OpenFileMapping`取得`handle`即可。

- 為何不同`process`，取得同一個`File Mapping Object`的`handle`值不同?
  - `CreateFileMapping`和`OpenFileMapping`回傳的`handle`是不一樣的，即使這兩個`handle`都指向同一個`File Mapping Object`。
  - 這是因為每個`process`都有自己的`handle表單`，只有產生該`handle`的`process`知道是指向哪個`object`。
  - 每次`process`呼叫`OpenFileMapping`，也會重新從表單取出新的`handle`。

- 可以簡單快速地說明，每個函數的用途嗎?
![image](https://hackmd.io/_uploads/HylUVDbhyg.png) ![image](https://hackmd.io/_uploads/HyMADwb3ke.png)
  - 在實體記憶體中，被建立的`File Mapping Object`，就是`Memory-Mapped File(記憶體對映檔)`，是實際存放共享資料的地方。
  - `對映(map)`是一個動詞，指的是將記憶體對映檔，投射到`process`虛擬記憶體中的動作。
  - 當記憶體對映檔，被投射到`process`虛擬記憶體中的某個區塊後，`process`彷彿就能透過這個區塊看到實際的對映檔(可視化)，這個區塊就稱作`view`。

- 可以在哪裡看到自己建立的`File Mapping Object`?
  - 在`【工作管理員】->【效能】->【資源監視器】`，在`【處理程序】`勾選自己的程式。
  - 然後在`【關聯控制代碼】`，搜尋`CreateFileMapping`指定的對映檔名稱。
![image](https://hackmd.io/_uploads/H1UE2HK2yx.png)

- 有些人會在對映檔名稱前，加上`Global\`，有什麼差別?
  - 在Windows切換不同使用者，系統會幫每個使用者都建立一個`Session`。
  - 名稱加上`Global\`，可以在切換使用者後，用同一個對映檔名稱，找到同一個對映檔。

- 想在不同`process`之間，共享同一份資料的其它方式?
  - DLL共享區間

## 參考資料

[File Mapping](https://learn.microsoft.com/en-us/windows/win32/memory/file-mapping "官方文件")
[Creating Named Shared Memory](https://learn.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory "官方文件")
[Windows API 之 CreateFile、CreateFileMapping 、MapViewOfFile](https://www.cnblogs.com/predator-wang/p/4902793.html)
[Windows10系统FileMapping跨进程共享需要权限](https://blog.csdn.net/qq_31042143/article/details/121754159)
[进程间通信：使用file-mapping kernel object(文件映射内核对象)](http://www.cppblog.com/canmeng50401/archive/2012/04/09/file-mapping-kernel-object.html)