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:
- Alloziieren Sie einen Speicherblock, indem Sie GlobalAlloc aufrufen. Diese Funktion liefert ein Handle des angeforderten Speicherblocks zurück.
- "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.
- Sie können den Pointer benutzen, um im Speicher zu lesen oder zu schreiben.
- "Entsperren" Sie den Speicherblock, indem Sie GlobalUnlock aufrufen. Diese Funktion erklärt den Zeiger auf den Speicherblock für ungültig.
- 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:
- Ö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.
- 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.
- 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.
- 0
Ö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