Tutorial 13: Memory Mapped Files
Ich werde zeigen was Memory Mapped Files (~ im Speicher gehaltene Dateien) sind und wie Sie sei zu ihrem Vorteil nutzen können. Eine Memory Mapped Datei zu benutzen ist ziemlich einfach wie Sie in diesem Tutorial sehen werden.
Laden Sie hier das Beispiel herunter.
Theorie:
Wenn Sie das Beispiel in dem vorherigen Tutorial genau untersuchen, werden Sie herausfinden, dass es eine Unzulänglichkeit hat: was ist, wenn die Datei, die Sie lesen möchten, größer als der alloziierte Speicherblock ist? Oder was ist, wenn der String, nach dem Sie suchen wollen, mittendrin abgetrennt ist am Ende eines Speicherblocks? Die traditionelle Antwort auf die erste Frage ist, dass Sie die Daten wiederholt aus der Datei lesen sollten, bis das Ende der Datei erreicht ist. Die Antwort auf die zweite Frage ist, dass Sie sich für diesen speziellen Fall mit dem Ende des Speicherblocks drauf vorbereiten müssen. Dieses Problem nennt man "Grenz-Wert" Problem. Es ist verursacht Kopfschmerzen beim Programmierer und unzählige Bugs.
Es wäre nett, wenn wir eine sehr großen Speicherblock alloziieren könnten, genug um eine ganze Datei zu speichern, aber unser Programm wäre ein Speicherfresser. Datei Mapping ist unsere Rettung. Indem wir Datei Mapping benutzen, können Sie sich die komplette Datei als bereits in Speicher geladen vorstellen und können Memory Pointern benutzen, um Daten aus der Datei zu lesen oder in diese zu schreiben. So einfach ist das. Keine Speicher-API-Funktionen und Datei I/O-Funktionen werden mehr benötigt, sie sind ein und das selbe beim Datei Mapping. Datei Mapping wird auch dazu verwendet, um Daten zwischen zwei Prozessen zu teilen. Wird Datei Mapping auf diese Art gebraucht, ist keine tatsächliche Datei involviert. Es ist mehr wie ein reservierter Speicherblock, den jeder Prozess *sehen* kann. Aber Daten zwischen Prozessen zu teilen ist ein delikates Thema, was nicht auf die leichte Schulter genommen werden sollte. Sie müssen Prozess und Thread Synchronisation implementieren, ansonsten stürzen ihre Programme innerhalb kürzester Zeit ab.
Wir werden in diesem Tutorial nicht das Thema anschneiden, indem Datei Mapping als geteilte Speicher-Region angesehen wird. Wir konzentrieren uns dadrauf, wie Datei Mapping als Datei im Speicher verwendet wird. Tatsache ist, dass der PE Loader Datei Mapping benutzt, um ausführbare Dateien in den Speicher zu laden. Es ist sehr vorteilhaft, da nur benötigte Teile selektiv aus der Datei von der Festplatte gelesen werden können. Unter Win32 sollten Sie Date Mapping zu oft wie möglich benutzen.
Es gibt aber dennoch einige Einschränkungen für Datei Mapping. Wenn Sie erst einmal eine Memory Mapped File erstellt haben, können Sie die Größe während dieser Session nicht ändern. Demnach ist File-Mapping großartig für read-only Dateien oder Datei-Operationen, die die Größe der Datei nicht verändern. Das bedeutet nicht, dass Sie kein File-Mapping benutzen können, wenn Sie die Größe der Datei erhöhen. Sie können die neue Dateigröße abschätzen und die Memory Mapped File basierend auf der neuen Dateigröße erstellen und die Datei wird auf diese Größe anwachsen. Es ist nur lästig, dass ist alles.
Genug der Erklärungen. Lassen Sie uns in die Implementation von File-Mapping eintauchen. Um File-Mapping zu benutzen, müssen diese Schritte ausgeführt werden:
- Aufruf von CreateFile um die Datei zu öffnen die gemapped werden soll.
- Aufruf von CreateFileMapping mit dem Datei-Handle das von CreateFile zurückgegeben wurde, als einer der Parameter. Diese Funktion erzeugt ein File-Mapping- Objekt von der Datei, die mit CreateFile geöffnet wurde.
- Aufruf von MapViewOfFile um eine selektierte Datei-Region oder die ganze Datei in den Speicher zu mappen. Diese Funktion liefert eine Pointer auf das erste Byte der gemappten Datei-Region zurück.
- Benutzen Sie den Pointer um aus der Datei zu lesen oder in die Datei zu schreiben.
- Aufruf von UnmapViewOfFile um die Datei zu unmappen.
- Aufruf von CloseHandle mit dem Handle der gemappten Datei als Parameter, um die gemappte Datei zu schließen.
- Erneuter Aufruf von CloseHandle, diesmal mit dem Handle, dass von CreateFile zurückgegeben wurde um die eigentliche Datei zu schließen.
Beispiel:
Das unten stehende Programm lässt Sie eine Datei über einen Datei-Öffnen-Dialog öffnen. Es öffnet die Datei indem es File Mapping benutzt, wenn es erfolgreich ist, nimmt der Fenster-Titel den Namen der geöffneten Datei an. Sie können die Datei unter einem anderen Namen speichern, indem Sie das Menüelement File/Save auswählen. Das Programm kopiert den kompletten Inhalt der geöffneten Datei in die neue Datei. Beachten Sie, dass Sie nicht GlobalAlloc in diesem Programm aufrufen müssen, um einen Speicherblock zu alloziieren.
.386
.model flat,stdcall
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
.data
ClassName db "Win32ASMFileMappingClass",0
AppName db "Win32 ASM Datei Mapping Beispiel",0
MenuName db "FirstMenu",0
ofn OPENFILENAME
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
hMapFile HANDLE 0 ; Handle der memory mapped Datei, muss mit
;0 initialisiert werden, da wir es auch als
;Flag in der WM_DESTROY Section benutzen
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ? ; Handle der Quell-Datei
hFileWrite HANDLE ? ; Handle der Ausgabe-Datei
hMenu HANDLE ?
pMemory DWORD ? ; Pointer auf die Daten in der Quell-Datei
SizeWritten DWORD ? ; Anzahl der Bytes die tatsählich von WriteFile geschrieben wurden
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke GetMenu,hWnd ;Menu Handle holen
mov hMenu,eax
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
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
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
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
invoke CloseHandle,hFileWrite
invoke SetWindowText,hWnd,ADDR AppName
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
.endif
.else
invoke DestroyWindow, hWnd
.endif
.endif
.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
end start
Analyse:
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
Wenn der Benutzer eine Datei in dem Datei-Öffnen-Dialog auswählt, rufen wir CreateFile auf, um diese zu öffnen. Beachten Sie, dass wir GENERIC_READ spezifizieren, um die Datei für nur-lese-Zugriffe zu öffnen und dwShareMode ist gleich Null da wir nicht wollen, dass andere Prozesse unsere Datei während unserer Operationen modifizieren.
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
Danach rufen wir CreateFileMapping auf, um eine Memory-Mapped-File aus der geöffneten Datei zu erstellen. CreateFileMapping hat folgende Syntax:
CreateFileMapping proto hFile:DWORD,\
lpFileMappingAttributes:DWORD,\
flProtect:DWORD,\
dwMaximumSizeHigh:DWORD,\
dwMaximumSizeLow:DWORD,\
lpName:DWORD
Sie sollten als erstes wissen, dass CreateFileMapping nicht die ganze Datei in den Speicher mappen muss. Sie können diese Funktion benuzten, um nur einen Teil der aktuellen Datei in den Speicher zu mappen. Sie spezifizieren die Größe der Memory-Mapped-File in den Parametern dwMaximumSizeHigh und dwMaximumSizeLow. Wenn Sie die Größe größer als die tatsächliche Dateigröße spezifizieren, wird die aktuelle Datei auf die neue Größe angepasst. Wenn Sie die Memory-Mapped-File genauso groß haben möchten, wie die aktuelle Datei, geben Sie bei beiden Parametern Null an.
Sie können NULL im lpFileMappingAttributes Parameter benutzen, damit Windows eine Memory-Mapped-File mit den Standard-Sicherheits-Attributen erstellt.
flProtect definiert den gewünschten Schutz für die Memory-Mapped-File. In unserem Beispiel benutzen wir PAGE_READONLY, damit nur Lese-Operation auf die Memory-Mapped-File angewandt werden können. Beachten Sie, dass dieses Attribut nicht den anderen Attributen in CreateFile widersprechen darf, ansonsten schlägt CreateFileMapping fehl.
lpName zeigt auf den Namen der Memory-Mapped-File. Wenn Sie diese Datei mit anderen Prozessen teilen möchten, müssen Sie den Namen mitteilen. Aber in unserem Beispiel, ist unser Prozess der einzige, der die Datei benutzt, deswegen ignorieren wir diesen Parameter.
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
Wenn CreateFileMapping erfolgreich ist, ändern wir den Fenster-Titel in den Namen der geöffneten Datei. Der Dateiname mit vollständigem Pfad ist im Buffer gespeichert, da wir aber nur den Dateinamen im Titel anzeigen wollen, müssen wir den Wert vom nFileOffset-Element aus der OPENFILENAME-Struktur zur Adresse des Buffers addieren.
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
Vorsorglich wollen wir nicht, dass der Benutzer mehrere Dateien auf einmal öffnen kann, weswegen wir das Öffnen-Menü-Element ausgrauen und das Speichern-Menü-Element aktivieren. EnableMenuItem wird benutzt, um das Attribut eines Menü-Items zu ändern.
Danach warten wir darauf, dass der Benutzer File/Save als Menü-Item auswählt oder unser Programm beendet. Wenn der Benutzer beschließt, dass Programm zu beenden, müssen wir die Memory-Mapped-File und die aktuelle Datei schließen, wie im folgenden Code:
.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL
In dem obigen Code-Ausschnitt, wenn die Fenster-Prozedur die WM_DESTROY Nachricht erhält, wird als erstes überprüft, ob der Wert von hMapFile Null ist oder nicht. Wenn er nicht Null ist, wird die CloseMapFile-Funktion aufgerufen, welche folgenden Code enthält:
CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp
CloseMapFile schließt die Memory-Mapped-File und die aktuelle Datei, so dass keine Ressource-Leaks auftreten, wenn unser Programm zu Windows zurückkehrt.
Wenn der Benutzer beschließt, die Daten in einer anderen Datei zu speichern, präsentiert das Programm ihm einen Speicher-Dialog. Nachdem er den Namen der neuen Datei eingegeben hat, wir die Datei von der CreateFile-Funktion erzeugt.
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
Unmittelbar nachdem die Ausgabe-Datei erzeugt wurde, rufen wir MapViewOfFile auf um den gewünschten Teil der Memory-Mapped-File in den Speicher zu mappen. Diese Funktion hat folgende Syntax:
MapViewOfFile proto hFileMappingObject:DWORD,\
dwDesiredAccess:DWORD,\
dwFileOffsetHigh:DWORD,\
dwFileOffsetLow:DWORD,\
dwNumberOfBytesToMap:DWORD
dwDesiredAccess spezifiziert welche Operation wir auf die Datei anwenden wollen. In unserem Beispiel wollen wir nur aus der Datei lesen, weshalb wir FILE_MAP_READ benutzen.
dwFileOffsetHigh und dwFileOffsetLow spezifizieren den Start-Datei-Offset des Datei-Teiles, den Sie in den Speicher mappen wollen. In unserem Fall, wollen wir die komplette Datei lesen, weshalb wir beim Offset 0 anfangen zu mappen.
dwNumberOfBytesToMap spezifiziert die Anzahl der Bytes, die in den Speicher gemapped werden sollen. Wenn Sie die ganze Datei in den Speicher mappen wollen (durch CreateFileMapping spezifiziert), übergeben Sie MapViewOfFile 0.
Nach dem Aufruf von MapViewOfFile, ist der gewünschte Teil in den Speicher geladen. Sie erhalten den Pointer auf den Speicher-Block, der die Daten der Datei enthält.
invoke GetFileSize,hFileRead,NULL
Findet heraus, wie groß die Datei ist. Die Dateigröße wird in EAX zurückgeliefert. Wenn die Datei größer als 4GB ist, wird das high DWORD der Dateigröße in FileSizeHighWord gespeichert. Da wir keine solche große Dateien erwarten, können wir es ignorieren.
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
Schreibt die Daten die im Speicher gemapped sind in die Ausgabe-Datei.
invoke UnmapViewOfFile,pMemory
Wenn wir mit der Quell-Datei durch sind, unmappen wir sie aus dem Speicher.
call CloseMapFile
invoke CloseHandle,hFileWrite
Und schließt alle Dateien.
invoke SetWindowText,hWnd,ADDR AppName
Herstellen des Original Titels.
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
Aktiviere das Öffnen-Menü-Element und graue das Speichern als-Menü-Item aus.
Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage