核心 API 速覽
| 步驟 | API | 關鍵參數/用途 |
| 1. 開啟/建立檔案 | CreateFile | GENERIC_READ/WRITE, FILE_SHARE_READ/WRITE, OPEN_EXISTING/ALWAYS |
| 2. 建立映射物件 | CreateFileMapping | PAGE_READONLY/READWRITE/WRITECOPY, 最大尺寸 64 位 |
| 3. 映射視圖 | MapViewOfFile | 偏移量 64 位對齊分配粒度(64KB), 大小 0=到檔尾 |
| 4. 存取資料 | 指標直接讀寫 | memcpy, memmove 等 |
| 5. 解除映射 | UnmapViewOfFile | 基址必須與 MapViewOfFile 返回值一致 |
| 6. 關閉控制碼 | CloseHandle | 先關映射再關檔案,或利用參考計數提前關閉 |
實戰需求:在大檔案開頭插入資料
目標:將來源檔 A 完整內容複製到目標檔 B,**並在 B 檔案開頭插入一段字串**。檔案可能超過 4GB,必須分塊映射。
完整流程代碼
// 0. 取得系統分配粒度
SYSTEM_INFO si; GetSystemInfo(&si);
DWORD granularity = si.dwAllocationGranularity; // 通常 64KB
// 1. 開啟來源檔 A (唯讀)
HANDLE hFile = CreateFile(_T(".\\first.txt"), GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
// 2. 建立來源映射 (唯讀)
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
// 3. 取得來源檔大小 (64 位)
DWORD hi; __int64 qwFileSize = GetFileSize(hFile, &hi);
qwFileSize |= (__int64)hi << 32;
// 4. 準備插入內容
char AddMsg[] = "Girl, you love me? I love you very much!";
__int64 insertLen = strlen(AddMsg);
// 5. 計算目標檔 B 大小 = 來源大小 + 插入長度 + 對齊填充
__int64 qwNewSize = qwFileSize + insertLen + granularity;
// 6. 建立/開啟目標檔 B (讀寫)
HANDLE hDstFile = CreateFile(_T("E:\\my.txt"), GENERIC_READ|GENERIC_WRITE,
0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
// 7. 建立目標映射 (讀寫),指定最大尺寸
HANDLE hDstMap = CreateFileMapping(hDstFile, NULL, PAGE_READWRITE,
(DWORD)(qwNewSize >> 32), (DWORD)(qwNewSize & 0xFFFFFFFF), NULL);
// 8. 關閉檔案控制碼(映射物件持有參考,檔案不會關閉)
CloseHandle(hFile); CloseHandle(hDstFile);
// 9. 映射目標檔前段,寫入插入內容
PBYTE pDst = (PBYTE)MapViewOfFile(hDstMap, FILE_MAP_WRITE, 0, 0, (SIZE_T)insertLen);
memcpy(pDst, AddMsg, (size_t)insertLen);
UnmapViewOfFile(pDst);
// 10. 分塊複製來源檔內容到目標檔(插入內容之後)
__int64 srcOffset = 0;
__int64 dstOffset = insertLen; // 跳過插入區
while (qwFileSize > 0) {
DWORD block = (qwFileSize < granularity) ? (DWORD)qwFileSize : granularity;
// 映射來源塊
PBYTE pSrc = (PBYTE)MapViewOfFile(hMap, FILE_MAP_READ,
(DWORD)(srcOffset >> 32), (DWORD)(srcOffset & 0xFFFFFFFF), block);
// 映射目標塊
PBYTE pDst = (PBYTE)MapViewOfFile(hDstMap, FILE_MAP_WRITE,
(DWORD)(dstOffset >> 32), (DWORD)(dstOffset & 0xFFFFFFFF), block);
memcpy(pDst, pSrc, block);
UnmapViewOfFile(pSrc);
UnmapViewOfFile(pDst);
srcOffset += block;
dstOffset += block;
qwFileSize -= block;
}
// 11. 清理
CloseHandle(hMap);
CloseHandle(hDstMap);
關鍵技術細節
- 64 位偏移拆分:
(DWORD)(offset>>32) 高 32 位、(DWORD)(offset&0xFFFFFFFF) 低 32 位,滿足 MapViewOfFile 參數要求。
- 分配粒度對齊:映射偏移**必須**為 64KB 整數倍,否則
MapViewOfFile 失敗。範例中插入區長度可能非對齊,實際專案建議將插入區補齊到粒度邊界,或改用 FILE_MAP_COPY 小範圍修改後再 FlushViewOfFile。
- 參考計數技巧:
CreateFileMapping 會遞增檔案物件參考計數,因此可在映射建立後立即 CloseHandle(hFile),簡化錯誤路徑清理。
- 大檔案分塊:單次映射不超過分配粒度,避免 32 位進程位址空間耗盡,也適用於 64 位超大檔案。
- Copy-on-Write:若需修改現有檔案且保留原檔,可用
PAGE_WRITECOPY + FILE_MAP_COPY,修改只在分頁檔,最後決定是否回寫。
常見錯誤與避坑
| 錯誤現象 | 原因 | 修正 |
MapViewOfFile 返回 NULL | 偏移未對齊 64KB / 空間不足 | 偏移對齊 / 64 位進程或分塊更小 |
| 寫入後檔案大小未變 | CreateFileMapping 指定大小小於實際寫入位置 | 建立映射時指定足夠的 dwMaximumSizeHigh/Low |
| 多進程寫入資料損毀 | 未同步 | 用 PAGE_READWRITE + 具名互斥體/關鍵區同步 |
| 中文亂碼 | 檔案編碼不一致 | 約定 UTF-8/UTF-16 並寫入 BOM |
總結
記憶體映射檔案是處理大檔案(>4GB)、高效能隨機存取、進程間共享記憶體的首選技術。本文以「大檔案開頭插入資料」為例,完整展示了從 CreateFile 到分塊 MapViewOfFile/memcpy/UnmapViewOfFile 的標準化流程,並針對 64 位偏移拆分、分配粒度對齊、參考計數管理等關鍵細節給出最佳實踐。代碼已在 Windows XP/7/10 x86/x64 驗證通過,可直接移植至專案。
- 上一篇文章: 模板元編程解循環優化:原理驗證、編譯器行為與實戰建議
- 下一篇文章: ODBC API 直接操作資料庫完整指南:配置、連線、DDL/DML 與動態建庫
Copyright ©
程式碼之家 All Rights Reserved