Iczelion - 12 - Speicher-Management und Datei Ein-/Ausgaben

Tutorial 12: Speicher-Management und Datei Ein-/Ausgaben


Wir werden die Grundlagen des Speicher-Managements und Datei Ein-/Ausgaben Operationen in diesem Tutorial lernen. Zusätzlich werden wir Standard-Dialog Boxen als Ein-/Ausgabe-Gerät benutzen.

Laden Sie das Beispiel hier herunter.

Theorie:

Speicher-Management unter Win32 aus Sicht der Applikation ist ziemlich einfach. Jeder Prozess besitzt einen 4 GB Speicherbereich. Das Speicher-Modell, welches benutzt wird, wird Flat-Speicher-Modell genannt. In diesem Modell zeigen alle Segment-Register (oder Selektoren) auf die selbe Start-Adresse und der Offset ist 32-Bit, so dass eine Applikation in seinem eigenen Adressbereich auf jeden beliebigen Punkt im Speicher zugreifen kann, ohne die Werte der Selektoren zu verändern. Das vereinfacht das Speicher-Management wesentlich. Es gibt keine "near" oder "far" Pointer mehr.

Unter Win16 gab es zwei Haupt-Kategorien an Speicher-API-Funktionen: Globale und Lokale. Globale API-Aufrufe behandelten Speicher der in anderen Segmenten alloziiert war, demnach waren sie "far"-Speicher-Funktionen. Lokale API-Aufrufe behandelten den lokalen Heap des Prozesses, demnach waren sie "near"-Speicher-Funktionen. Unter Win32 sind diese beiden Arten identisch. Egal ob Sie GlobalAlloc oder LocalAlloc aufrufen, sie werden das selbe Ergebnis erhalten.

Schritte um Speicher zu alloziieren und zu nutzen:

  1. Alloziieren Sie einen Speicherblock, indem Sie GlobalAlloc aufrufen. Diese Funktion liefert ein Handle des angeforderten Speicherblocks zurück.
  2. "Sperren" Sie den Speicherblock, indem Sie GlobalLock aufrufen. Diese Funktion akzeptiert ein Handle auf den Speicherblock und liefert einen Zeiger auf den Speicherblock zurück.
  3. Sie können den Pointer benutzen, um im Speicher zu lesen oder zu schreiben.
  4. "Entsperren" Sie den Speicherblock, indem Sie GlobalUnlock aufrufen. Diese Funktion erklärt den Zeiger auf den Speicherblock für ungültig.
  5. Freigeben des Speicherblocks, indem GlobalFree aufgerufen wird. Diese Funktion erwartet das Handle des Speicherblocks.
Sie können "Global" auch durch "Local" ersetzen, z.B. LocalAlloc, LocalLock,etc.
Die obige Methode kann weiter vereinfacht werden, indem das Flag GMEM_FIXED beim GlobalAlloc-Aufruf mit angegeben wird. Wenn Sie dieses Flag benutzen, ist der Rückgabewert von Global/LocalAlloc der Pointer auf den alloziierten Speicherblock, nicht das Speicherblock Handle. Sie müssen nicht Global/LocalLock aufrufen und Sie können den Pointer Global/LocalFree übergeben ohne vorher Global/LocalUnlock aufrufen. In diesem Tutorial werde ich allerdings die "traditionelle" Vorgehensweise benutzen, da Sie vielleicht beim lesen des Source Codes anderer Programme drüber stolpern könnten.

Datei Ein-/Ausgabe unter Win32 birgt einige merkbare Veränderungen im Gegensatz zu DOS. Die Schritte die Sie benötigen, sind die selben. Sie müssen nur Interrupts mit API-Aufrufen austauschen und fertig.

Die benötigten Schritte sind folgende:

  1. Öffnen oder erzeugen Sie eine Datei indem Sie die CreateFile Funktion aufrufen. Diese Funktion ist sehr vielseitig: zusätzlich zu Dateien, kann sie Kommunikations-Ports, Pipes, Laufwerke oder Konsolen öffnen. Bei Erfolg wird das Handle der Datei oder des Gerätes zurückgeliefert. Sie können dann das Handle benutzen, um Operationen auf die Datei oder das Gerät anzuwenden.
    Bewegen Sie den Dateizeiger zu der gewünschten Position, indem Sie SetFilePointer aufrufen.
  2. Führen Sie Lese- oder Schreib-Operationen aus, indem Sie ReadFile oder WriteFile aufrufen. Diese Funktionen transportieren Daten aus einem Speicherblock aus oder in eine Datei. Demnach müssen Sie einen Speicherblock alloziieren der groß genug ist, alle Daten zu fassen.
  3. Schließen Sie die Datei indem Sie CloseHandle aufrufen. Diese Funktion erwartet das Datei-Handle.

Inhalt:

Das unten stehende Programm, zeigt einen Datei-Öffnen Dialog an. Dieser lässt den Benutzer eine Text-Datei auswählen, die geöffnet wird und der Inhalt in einem Edit-Steuerelement angezeigt wird. Der Benutzer kann den Text im Edit-Steuerelement nach seinen Wünschen verändern und kann die Änderungen in einer Datei speichern.

.386 .model flat,stdcall option casemap:none 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 MEMSIZE equ 65535 EditID equ 1 ; ID des edit Steuerelements .data ClassName db "Win32ASMEditClass",0 AppName db "Win32 ASM Edit",0 EditClass db "edit",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) .data? hInstance HINSTANCE ? CommandLine LPSTR ? hwndEdit HWND ? ; Handle des edit Steuerelements hFile HANDLE ? ; Datei Handle hMemory HANDLE ? ;Handle des alloziierten Speicherblocks pMemory DWORD ? ;Zeiger auf den alloziierten Speicherblock SizeReadWrite DWORD ? ; Anzahl der tatsächlich geschriebenen oder gelesenen Bytes .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:SDWORD 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 uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_CREATE invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\ WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\ ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\ 0,0,0,hWnd,EditID,\ hInstance,NULL mov hwndEdit,eax invoke SetFocus,hwndEdit ;============================================== ; Initialisierung der Element der OPENFILENAME Struktur ;============================================== 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_SIZE mov eax,lParam mov edx,eax shr edx,16 and eax,0ffffh invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE .ELSEIF uMsg==WM_DESTROY 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 or GENERIC_WRITE ,\ FILE_SHARE_READ or FILE_SHARE_WRITE,\ NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ NULL mov hFile,eax invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE mov hMemory,eax invoke GlobalLock,hMemory mov pMemory,eax invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory invoke CloseHandle,hFile invoke GlobalUnlock,pMemory invoke GlobalFree,hMemory .endif invoke SetFocus,hwndEdit .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 hFile,eax invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE mov hMemory,eax invoke GlobalLock,hMemory mov pMemory,eax invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL invoke CloseHandle,hFile invoke GlobalUnlock,pMemory invoke GlobalFree,hMemory .endif invoke SetFocus,hwndEdit .else invoke DestroyWindow, hWnd .endif .endif .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp end start





Analyse:

invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\ WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\ ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\ 0,0,0,hWnd,EditID,\ hInstance,NULL mov hwndEdit,eax


In der WM_CREATE Sektion, erzeugen wir ein Edit-Steuerelement. Beachten Sie, dass die Parameter die x, y, Breite, Höhe des Elements spezifizieren alle gleich Null sind, da wir die Größe des Elements später ändern, damit die komplette Client Area des Eltern-Fensters bedeckt ist.
Beachten Sie in diesem Fall, dass wir ShowWindow nicht aufrufen müssen, damit das Edit-Steuerelement auf dem Bildschirm erscheind, da wir den WS_VISIBLE-Stil einfügen. Sie können diesen Trick auch im Eltern-Fenster benutzen.

;============================================== ; Initialisierung der Element der OPENFILENAME Struktur ;============================================== 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


Nachdem wir das Edit-Stuerelement erzeugt haben, nehmen wir uns die Zeit um die Elemente der ofn-Struktur zu initialisieren. Da wir ofn später beim Speicher-Als-Dialog auch noch benutzen wollen, belegen wir nur *Standard* Elemente, die von beiden, GetOpenFileName und GetSaveFileName benutzt werden.

Die WM_CREATE-Sektion ist der ideale Ort um einmalige Initialisierungen vorzunehmen.

.ELSEIF uMsg==WM_SIZE mov eax,lParam mov edx,eax shr edx,16 and eax,0ffffh invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE


Wir erhalten WM_SIZE Nachrichten immer dann, wenn sich die Größe der Client Area unseres Hauptfensters ändert. Wir erhalten sie auch, nachdem das Fenster erstellt wurde. Um diese Nachricht empfangen zu können, müssen die Stile CS_VREDRAW und CS_HREDRAW in der Fensterklasse enthalten sein. Wir nutzen diese Gelegenheit, um unser Edit-Steuerelement auf die selbe Größe wie die Client Area des Eltern-Fensters zu setzen. Als erstes müssen wir dazu die Breite und Höhe der Client Area des Eltern-Fensters kennen. Wir bekommen diese Info aus lParam. Das obere Word von lParam enthält die Höhe und das untere Word von lParam die Breite der Client Area. Wir benutzen diese Informationen um die Größe des Edit-Steuerelements mittels der MoveWindow-Funktion zu ändern, welche neben der Fensterposition auch die Größe ändern kann.

.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


Wenn der Benutzer das Datei/Öffnen Menü auswählt, füllen wir Flag-Elemente der ofn-Struktur und rufen die GetOpenFileName-Funktion auf um den Datei-Öffnen-Dialog anzuzeigen.

.if eax==TRUE invoke CreateFile,ADDR buffer,\ GENERIC_READ or GENERIC_WRITE ,\ FILE_SHARE_READ or FILE_SHARE_WRITE,\ NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\ NULL mov hFile,eax


Nachdem der Benutzer eine Datei zum öffnen gewählt hat, rufen wir CreateFile auf, um die Datei zu öffnen. Wir spezifizieren, dass die Funktion die Datei für Lese- und Schreibzugriffe öffnen soll. Nachdem die Datei geöffnet ist, gibt die Funktion das Handle der geöffneten Datei zurück, welche wir in einer globalen Variable für den späteren Gebrauch speichern. Diese Funktion hat folgende Syntax:

CreateFile proto lpFileName:DWORD,\ dwDesiredAccess:DWORD,\ dwShareMode:DWORD,\ lpSecurityAttributes:DWORD,\ dwCreationDistribution:DWORD\, dwFlagsAndAttributes:DWORD\, hTemplateFile:DWORD


dwDesiredAccess spezifiziert welche Operation auf die Datei angewandt werden soll.

  • Öffnet die Datei um die Attribute zu erfragen. Sie müssen angeben, ob Daten gelesen oder geschrieben werden sollen.
  • GENERIC_READ
  •    Öffnet die Datei zum Lesen
  • GENERIC_WRITE
  •   Öffnet die Datei zum Schreiben.
dwShareMode spezifiziert welche Operation anderen Prozessen erlaubt sein soll, wenn die Datei geöffnet ist.

  • 0
  •   Kein Teilen der Datei mit anderen Prozessen.
  • FILE_SHARE_READ
  •   erlaube anderen Prozessen Daten aus der geöffneten Datei zu lesen
  • FILE_SHARE_WRITE
  •   erlaube anderen Prozessen Daten in die geöffneten Datei zu schreiben.
lpSecurityAttributes hat keine Bedeutung unter Windows 95.
dwCreationDistribution spezifiziert was CreateFile macht, wenn die in lpFileName angegebene Datei existiert, oder wenn sie nicht existiert.

  • CREATE_NEW
  • Erzeugt eine neue Datei. Die Funktion schlägt fehl, wenn die Datei schon existiert.
  • CREATE_ALWAYS
  • Erzeugt eine neue Datei. Die Funktion überschreibt eine ggf. existierende Datei.
  • OPEN_EXISTING
  • Öffnet die Datei. Die Funktion schlägt fehl, wenn die Datei nicht existiert.
  • OPEN_ALWAYS
  • Öffnet die Datei, wenn sie existiert. Wenn die Datei nicht existiert, die Funktion erzeugt eine Datei, als wenn dwCreationDistribution gleich CREATE_NEW wäre.
  • TRUNCATE_EXISTING
  • Öffnet die Datei. Einmal geöffnet, wird die Datei abgeschnitten, so dass ihre Größe 0 Bytes ist. Der aufrufende Prozess muß die Datei mindestens mit GENERIC_WRITE Zugriff öffnen. Die Funktion schlägt fehl, wenn die Datei nicht existiert.
dwFlagsAndAttributes spezifiziert die Datei-Attribute

  • FILE_ATTRIBUTE_ARCHIVE
  • Die Datei ist eine Archiv-Datei. Applikationen benutzen dieses Attribut um Dateien für den Backup oder zum Entfernen zu markieren.
  • FILE_ATTRIBUTE_COMPRESSED
  • Die Datei oder das Verzeichnis ist komprimiert. Für eine Datei bedeutete das, dass alle Daten in der Datei komprimiert sind. Für ein Verzeichnis bedeutet das, dass Kompression Standard für alle neu erzeugten Dateien und Unterverzeichnisse ist.
  • FILE_ATTRIBUTE_NORMAL
  • Die Datei hat keine weiteren Attribute gesetzt. Dieses Attribut ist nur alleine gültig.
  • FILE_ATTRIBUTE_HIDDEN
  • Die Datei ist versteckt. Sie wird nicht in einer normalen Verzeichnis-Ansicht angezeigt.
  • FILE_ATTRIBUTE_READONLY
  • Die Datei ist nur lesbar. Applikationen können die Datei lesen, aber nicht in sie schreiben oder löschen.
  • FILE_ATTRIBUTE_SYSTEM
  • Die Datei ist Teil oder wird exklusiv vom Betriebssystem benutzt.
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE mov hMemory,eax invoke GlobalLock,hMemory mov pMemory,eax


Wenn die Datei geöffnet wird, alloziieren wir einen Speicherblock, der von den ReadFil und WriteFile-Funktionen genutzt wird. Wir geben das GMEM_MOVEABLE-Flag an, um Windows das verschieben des Speichers zu erlauben. Das GMEM_ZEROINT-Flag teilt GlobalAlloc mit, dass der neu alloziierte Speicherbereich mit Nullen gefüllt werden soll.

Wenn GlobalAlloc erfolgreich zurückkehrt, enthält EAX das Handle des alloziierten Speichterblocks. Wir übergeben das Handle der GlobalLock-Funktion welche eine Zeiger auf den Speicherblock zurückliefert.

invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory


Wenn der Speicherblock bereit zur Nutzung ist, rufen wir die ReadFile-Funktion auf, um Daten aus der Datei zu lesen. Wenn eine Datei das erste Mal geöffnet oder erzeugt wird, ist der Dateizeiger am Offset 0. In diesem Fall beginnen wir also beim ersten Byte aus der Datei zu lesen. Der erste Parameter von ReadFile ist das Handle der zu lesenden Variable, der zweite der Zeiger auf den Speicherblock, der die Daten speichert, als nächstes kommt die Anzahl der Bytes, die aus der Datei gelesen werden sollen, der vierte Parameter ist die Adresse einer Variable der DWORD-Größe, die die Anzahl der tatsächlich gelesenen Bytes speichert.

Nachdem wir den Speicherblock mit den Daten gefüllt haben, können wir die Daten in das Edit-Steuerelement tun, indem wir eine WM_SETTEXT-Nachricht an das Edit-Steuerelement schicken, wobei lParam den Zeiger auf den Speicherblock enthält. Nach diesem Aufruf, enthält die Client Area des Edit-Steuerelements die Daten.

invoke CloseHandle,hFile invoke GlobalUnlock,pMemory invoke GlobalFree,hMemory .endif


Zu diesem Zeitpunkt brauchen wir die Datei nicht länger offen halten, da unsere Absicht das Schreiben der modifizierten Daten aus dem Edit-Steuerlement in eine andere Datei ist, nicht die Ursprungsdatei. Deswegen schließen wir die Datei, indem wir CloseHandle aufrufen, mit dem Handle der Datei als Parameter. Als nächstes entsperren wir den Speicherblock und geben ihn wieder frei. Eigentlich müssen Sie den Speicher hier nicht freigeben, Sie können den Speicherblock später für die Speichern-Operation verwenden, aber für Demonstrations-Zwecke schließe ich sie hier.

                invoke SetFocus,hwndEdit


Wenn der Datei-Öffnen-Dialog angezeigt wird, erhält dieser den Fokus. So, dass wir nach Schließung des Dialogs den Eingabe-Fokus wieder zurück auf das Steuerelement setzen müssen.

Das beendet die Lese-Operation aus der Datei. An diesem Punkt kann der Benutzer den Inhalt des Edit-Steuerelements verändern. Und wenn der Benutzer die Daten in eine andere Datei speichern möchte, muss er Date/Speichern als Menü-Punkt wählen, welchen den Speichern-Unter-Dialog anzeigt. Die Erzeugung des Speichern-Als-Dialogs unterscheidet sich nicht großartig vom Datei-Öffnen-Dialog. In Wirklichkeit unterscheiden sie sich nur im Namen der Funktion, GetOpenFileName und GetSaveFileName. Sie können die meisten Elemente, der ofn-Struktur wieder benutzen, außer den Flag-Elementen.

mov ofn.Flags,OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY


In unserem Fall wollen wir eine neue Datei erzeugen, weshalb OFN_FILEMUSTEXIST und OFN_PATHMUSTEXIST weggelassen werden müssen, da der Dialog uns ansonsten keine Datei erzeugen lässt, die nicht schon existiert.

Der dwCreationDistribution Parameter der CreateFile-Funktion muss zu CREATE_NEW geändert werden, da wir eine neue Datei erstellen wollen.

Der übrige Code ist identisch mit dem der Datei-Öffnen-Sektion mit Ausnahmen von folgendem:

invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL


Wir senden eine WM_GETTEXT-Nachricht an das Edit-Steuerelement um die Daten in den Speicherblock zu kopieren, der Rückgabewert in EAX ist die Länge der Daten innerhalb des Buffers. Nachdem die Daten im Speicherblock sind, schreiben wir sie in die neue Datei.


Deutsche Übersetzung: Joachim Rohde
Die original Win32Asm-Tutorials stammen von Iczelion's Win32 Assembly HomePage