Iczelion - 13 - Memory Mapped Files

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:

  1. Aufruf von CreateFile um die Datei zu öffnen die gemapped werden soll.
  2. 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.
  3. 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.
  4. Benutzen Sie den Pointer um aus der Datei zu lesen oder in die Datei zu schreiben.
  5. Aufruf von UnmapViewOfFile um die Datei zu unmappen.
  6. Aufruf von CloseHandle mit dem Handle der gemappten Datei als Parameter, um die gemappte Datei zu schließen.
  7. 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