# 共享記憶體 - File Mapping
在同一個專案下,如果想在多個`cpp`中,使用同一個`int`變數,我們也許會宣告一個全域變數(global variable),讓不同`cpp`都能存取到該變數。
但開發大型系統時,通常不會只有單一個專案,而是有很多個`exe`或`dll`,而在這些分開執行的`行程(process)`中,也經常需要共用一些變數或資料。
然而,每個`process`在記憶體中,都會被分配到一塊屬於自己的記憶體空間,並與其它`process`互相隔離、互不干擾,所以在一個`process`宣告的全域變數,並不能讓其它`process`使用。
若想在不同`process`之間,共享同一份資料,其中一個方式就是使用 ++**File Mapping**++。
## File Mapping (檔案對映)
> File Mapping(檔案對映)可以用來讀取大型檔案,作法是在記憶體中創建一塊空間,並對映到硬碟中的檔案。若是不對映任何檔案,就能利用這塊記憶體空間,實作共享記憶體的功能。

:::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`。
- 可以簡單快速地說明,每個函數的用途嗎?
 
- 在實體記憶體中,被建立的`File Mapping Object`,就是`Memory-Mapped File(記憶體對映檔)`,是實際存放共享資料的地方。
- `對映(map)`是一個動詞,指的是將記憶體對映檔,投射到`process`虛擬記憶體中的動作。
- 當記憶體對映檔,被投射到`process`虛擬記憶體中的某個區塊後,`process`彷彿就能透過這個區塊看到實際的對映檔(可視化),這個區塊就稱作`view`。
- 可以在哪裡看到自己建立的`File Mapping Object`?
- 在`【工作管理員】->【效能】->【資源監視器】`,在`【處理程序】`勾選自己的程式。
- 然後在`【關聯控制代碼】`,搜尋`CreateFileMapping`指定的對映檔名稱。

- 有些人會在對映檔名稱前,加上`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)