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

記憶體映射檔案 完整指南:原理、API 流程與大檔案複製實戰範例

     編輯:組合語言

為什麼需要 Memory Mapped Files (MMF)?

傳統檔案 I/O (ReadFile/WriteFile) 需要:

  1. 配置記憶體緩衝區 (GlobalAlloc/HeapAlloc)
  2. 讀取檔案區塊到緩衝區
  3. 處理資料
  4. 寫回目標檔案
  5. 釋放緩衝區

痛點:

  • 檔案超大 (GB 級) 時,無法一次配置足夠記憶體。
  • 跨緩衝區邊界搜尋字串 (如「Hello」切在兩個區塊中間) 需處理複雜邊界邏輯。
  • 多次系統呼叫開銷大。

MMF 解決方案: 讓作業系統將檔案直接映射到進程虛擬位址空間。您只需操作指標 (像操作記憶體陣列),OS 自動處理分頁讀寫、快取、預讀。甚至可作為進程間共享記憶體 (需同步機制)。

核心 API 七步曲

  1. CreateFile:開啟來源檔案,取得 hFile。
  2. CreateFileMapping:建立檔案映射物件 (hMapFile),指定保護屬性 (如 PAGE_READONLY) 與大小 (0=全檔)。
  3. MapViewOfFile:將映射物件的某範圍 (或全部) 映射到位址空間,返回基址指標 pMemory。
  4. 直接操作記憶體:mov eax, [pMemory], rep movsb, WriteFile(hDest, pMemory, size, ...) 等。
  5. UnmapViewOfFile:解除映射視圖。
  6. CloseHandle(hMapFile):關閉映射物件代碼。
  7. CloseHandle(hFile):關閉檔案代碼。

完整實戰範例:GUI 檔案複製器 (開啟 -> 另存新檔)

功能:選擇來源檔 -> MMF 映射 -> 選擇目標檔 -> WriteFile 直接寫入映射記憶體指標 -> 完成。全程零 GlobalAlloc/ReadFile。

關鍵程式碼片段

.data
hMapFile HANDLE 0        ; 映射物件代碼,0 也作為旗標判斷是否已建立
hFileRead HANDLE ?       ; 來源檔代碼
hFileWrite HANDLE ?      ; 目標檔代碼
pMemory DWORD ?          ; 映射視圖基址指標
SizeWritten DWORD ?      ; WriteFile 實際寫入位元組數

; ... 視窗建立、選單建立略 ...

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg == WM_COMMAND
        mov eax, wParam
        .if lParam == 0
            .if ax == IDM_OPEN
                ; --- 開啟檔案並建立 MMF ---
                mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
                invoke GetOpenFileName, ADDR ofn
                .if eax == TRUE
                    invoke CreateFile, ADDR buffer, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL
                    mov hFileRead, eax

                    invoke CreateFileMapping, hFileRead, NULL, PAGE_READONLY, 0, 0, NULL
                    mov hMapFile, eax

                    ; 設定視窗標題為檔名 (利用 ofn.nFileOffset 取純檔名)
                    mov eax, OFFSET buffer
                    movzx edx, ofn.nFileOffset
                    add eax, edx
                    invoke SetWindowText, hWnd, eax

                    invoke EnableMenuItem, hMenu, IDM_OPEN, MF_GRAYED
                    invoke EnableMenuItem, hMenu, IDM_SAVE, MF_ENABLED
                .endif

            .elseif ax == IDM_SAVE
                ; --- 另存新檔:建立目標檔 -> 映射視圖 -> WriteFile ---
                mov ofn.Flags, OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
                invoke GetSaveFileName, ADDR ofn
                .if eax == TRUE
                    invoke CreateFile, ADDR buffer, GENERIC_READ or GENERIC_WRITE, \
                        FILE_SHARE_READ or FILE_SHARE_WRITE, NULL, CREATE_NEW, FILE_ATTRIBUTE_ARCHIVE, NULL
                    mov hFileWrite, eax

                    invoke MapViewOfFile, hMapFile, FILE_MAP_READ, 0, 0, 0
                    mov pMemory, eax

                    invoke GetFileSize, hFileRead, NULL
                    invoke WriteFile, hFileWrite, pMemory, eax, ADDR SizeWritten, NULL

                    invoke UnmapViewOfFile, pMemory
                    call CloseMapFile          ; 關閉 hMapFile, hFileRead
                    invoke CloseHandle, hFileWrite

                    invoke SetWindowText, hWnd, ADDR AppName
                    invoke EnableMenuItem, hMenu, IDM_OPEN, MF_ENABLED
                    invoke EnableMenuItem, hMenu, IDM_SAVE, MF_GRAYED
                .endif
            .endif
        .endif
    .ELSEIF uMsg == WM_DESTROY
        .if hMapFile != 0
            call CloseMapFile
        .endif
        invoke PostQuitMessage, NULL
    .ELSE
        invoke DefWindowProc, hWnd, uMsg, wParam, lParam
        ret
    .ENDIF
    xor eax, eax
    ret
WndProc endp

CloseMapFile PROC
    invoke CloseHandle, hMapFile
    mov hMapFile, 0
    invoke CloseHandle, hFileRead
    ret
CloseMapFile endp

關鍵參數深度解析

CreateFileMapping

CreateFileMapping proto hFile:DWORD, \
    lpFileMappingAttributes:DWORD, \
    flProtect:DWORD, \
    dwMaximumSizeHigh:DWORD, \
    dwMaximumSizeLow:DWORD, \
    lpName:DWORD
  • flProtect:PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY (複製時寫入,不影響原檔)。必須與 CreateFile 的存取權限相容 (如 GENERIC_READ 不能配 PAGE_READWRITE)。
  • dwMaximumSizeHigh/Low:映射大小。皆為 0 表示映射整個檔案。若指定大於檔案大小,檔案會擴充至該大小。
  • lpName:命名映射物件,供跨進程共享使用 (需 OpenFileMapping 開啟)。本例為單進程使用,傳 NULL。

MapViewOfFile

MapViewOfFile proto hFileMappingObject:DWORD, \
    dwDesiredAccess:DWORD, \
    dwFileOffsetHigh:DWORD, \
    dwFileOffsetLow:DWORD, \
    dwNumberOfBytesToMap:DWORD
  • dwDesiredAccess:FILE_MAP_READ, FILE_MAP_WRITE, FILE_MAP_COPY。須與 flProtect 相容。
  • dwFileOffsetHigh/Low:檔案起始偏移 (64-bit)。必須是系統配置粒度倍數 (通常 64KB),否則失敗。映射整檔傳 0,0。
  • dwNumberOfBytesToMap:映射位元組數。0 = 從偏移處映射到檔案結尾。

MMF 限制與注意事項

  • 大小固定:映射建立後,該會話期間無法改變大小。解法:預估最大尺寸建立大映射,實際使用部分即可。
  • 適用場景:唯讀檔案、固定大小資料庫、進程間高速共享記憶體 (配合 Mutex/Event 同步)。
  • 不適用:頻繁擴充/截斷的日誌檔、需精確控制磁碟同步的資料庫引擎 (除非搭配 FlushViewOfFile)。
  • PE 載入器也在用:Windows 載入 EXE/DLL 即使用 MMF (區段對齊映射),實現按需分頁載入。

進階:跨進程共享資料範例架構

; 進程 A (建立者)
invoke CreateFileMapping, INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 4096, "MySharedMem"
invoke MapViewOfFile, eax, FILE_MAP_WRITE, 0, 0, 0
; 寫入資料...
; 需用 Mutex 保護寫入

; 進程 B (開啟者)
invoke OpenFileMapping, FILE_MAP_READ, FALSE, "MySharedMem"
invoke MapViewOfFile, eax, FILE_MAP_READ, 0, 0, 0
; 讀取資料...

警告:共享記憶體必須處理同步 (Mutex, Semaphore, Event),否則會讀到髒資料、導致競爭條件甚至崩潰。

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