Iczelion - 03 - Ein einfaches Fenster

Tutorial 3: Ein einfaches Fenster



In diesem Tutorial werden wir ein Windows-Programm erstellen, dass ein voll funktionsfähiges Fenster auf dem Desktop darstellt.

Laden Sie hier die Beispiel-Datei herunter.
Theorie:

Windows-Programme verlassen sich sehr auf die API-Funktionen für ihre GUI (=graphical user interface = Benutzeroberfläche). Dieser Ansatz nutzt sowohl den Benutzern als auch den Programmierern. Die Benutzer müssen nicht lernen, wie sie sich durch jedes neue Programm bewegen, da die GUI von Windows-Programmen immer ähnlich sind. Programmierern nutzt es, da der GUI-Code schon getestet vorhanden und fertig zum benutzen ist. Die Kehrseite der Medaille für Programmierer ist die erhöhte Komplexität. Um ein GUI-Objekt zu erstellen oder zu verändern, wie Fenster, Menüs oder Icons, müssen Programmierer einem strengen Rezept folgen. Aber das kann von modularer Programmierung oder OOP-Paradigmen bewältigt werden.

Ich werde hier die bnötigten Schritte aufzählen, um ein Fenster auf den Desktop zu erstellen:

  1. Ermittlen Sie das Instanz-Handle ihres Programmes (benötigt)
  2. Ermittlen Sie die Kommandozeile (nicht nötig, wenn ihr Programm keine Parameter aus der Kommandozeile braucht)
  3. Registrieren Sie eine Fensterklasse (benötigt, wenn Sie nicht gerade ein vordefinierten Fenstertypen, wie z.B. eine MessageBox oder einen Dialog benutzen)
  4. Erzeugen Sie das Fenster (benötigt)
  5. Zeigen Sie das Fenster auf dem Desktop an (benötigt, es sei denn, Sie wollen das Fenster nicht sofort Zeit anzeigen lassen)
  6. Erneuern Sie die Client Area des Fensters
  7. Rufen Sie in einer Endlosschleife alle Nachrichten von Windows ab.
  8. Wenn Nachrichten ankommen, werden sie von einer speziellen Funktion, die für das Fenster verantwortlich ist, bearbeitet.
  9. Beenden Sie das Programm, wenn der Benutzer das Fenster schliesst.
Wie Sie sehen können, ist die Struktur eines Windows-Programmes wesentlich komplexer, als das eines DOS-Programmes. Aber die Welt von Windows unterscheidet sich gewaltig von der DOS-Welt. Windows-Programme müssen in der Lage sein, friedvoll nebeneinander zu existieren. Sie müssen strikten Regeln folgen. Sie, als ein Programmierer, müssen ebenso strenger mit ihrem Programmierstil und Verhalten sein.

Inhalt:

Unten ist der Source Code unseres einfachen Windows-Programm. Bevor wir uns den Details der Win32 ASM Programmierung widmen, werde ich einige Punkte klären, die ihnen das Programmieren erleichtert.

  • Sie sollten alle Windows-Konstanten, Strukturen und Funktions-Prototypen in eine Include-Datei schreiben und diese am Anfang ihrer .asm-Datei mit einbinden. Es wird ihnen eine Menge Aufwand und Tipparbeit sparen. Zur Zeit ist die kompletteste Include-Datei für MASM Hutch's windows.inc, welche Sie von seiner oder meiner Seite herunterladen können. Sie können auch ihre eingenen Konstanten& Struktur-Definitionen definieren, aber Sie sollten sie in einer seperate Include-Datei speichern.
  • Benutzen Sie die includelib Direktive um die benutzten Import Libraries in ihrem Programm zu spezifizieren. Wenn ihr Programm zum Beispiel MessageBox aufruft, sollten Sie die Zeile:
includelib user32.lib

am Anfang ihrer .asm-Datei einfügen. Diese Direktive teilt MASM mit, dass ihr Programm Gebrauch von Funktionen aus diese Import Library machen wird. Ruft ihr Programm Funktionen in mehr als einer Library auf, fügen Sie einfach für jede Library, die Sie nutzen, ein includelib ein. Wenn Sie die includeLib Direktive benutzen, müssen Sie sich nicht um die Import Libraries während des Linkens kümmern. Sie können auch die /LIBPATH Option des Linkers benutzen, um Link mitzuteilen, wo all die Libs sind.

  • Wenn Sie API-Funktionsprototypen, Strukturen oder Konstanten in ihrer Include-Datei deklarieren, versuchen Sie sich an die Originalnamen, die in Windows Include-Dateien verwendet werden, zu halten, inklusive Groß-/Kleinschreibung. Das wird ihnen eine Menge Kopfschmerzen ersparen, wenn Sie etwas in der Win32 API Referenz nachschlagen.
  • Benutzen Sie makefile um den Assemblierungsprozess zu automatisieren. Das wird ihnen eine Menge Tipparbeit sparen.
.386 .model flat,stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib ; Aufrufe von Funktionen in user32.lib und kernel32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib WinMain proto :DWORD,:DWORD,:DWORD,:DWORD .DATA ; initialisierte Daten ClassName db "SimpleWinClass",0 ; der Name unserer Fensterklassen AppName db "Our First Window",0 ; der Name unseres Fensters .DATA? ; Uninitialisiere Daten hInstance HINSTANCE ? ; Instanzhandle unseres Programms CommandLine LPSTR ? .CODE ; Hier beginnt unser Code start: invoke GetModuleHandle, NULL ; ermittle das Instanz Handle unseres Programm. ; Unter Win32, hmodule==hinstance mov hInstance,eax mov hInstance,eax invoke GetCommandLine ; ermittle die Kommandozeile. Sie müssen diese Funktion nicht aufrufen, WENN ;ihr Programm die Kommandozeile nicht bearbeitet. mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; rufe die Haupt-Funktion auf invoke ExitProcess, eax ; beende unser Programm. Der Exit Code wird in eax von WinMain zurückgegeben. WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX ; erzeuge lokale Variablen auf dem Stack LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX ; fülle Elemente von wc mit Werten mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL 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 ; registriere unsere Fensterklasse invoke CreateWindowEx,NULL,\ ADDR ClassName,\ ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ NULL,\ NULL,\ hInst,\ NULL mov hwnd,eax invoke ShowWindow, hwnd,CmdShow ; zeige unser Fenster auf dem Desktop an invoke UpdateWindow, hwnd ; erneuer die client area .WHILE TRUE ; betrete die message loop invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ; gib exit code in eax zurück ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY ; wenn der Benutzer unser Fenster schliesst invoke PostQuitMessage,NULL ; beende unsere Applikation .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Standard Nachrichtenverarbeitung ret .ENDIF xor eax,eax ret WndProc endp end start


Analyse:

Sie werden sprachlos sein, das ein einfaches Windows-Programm so viel Code benötigt. Aber der meiste Code ist nur *Template*-Code, den Sie aus einer anderen Source Code Datei in eine andere kopieren können. Oder, wenn Sie es vorziehen, können Sie diesen Code in eine Library assemblieren um ihn als Prolog und Epilog Code zu benutzen. Sie können nur den Code in der WinMain-Funktion schreiben. Tatsächlich ist das das, was C Compiler tun. Sie lassen Sie den WinMain Code schreiben, ohne dass Sie sich um den Rest kümmern müssen. Der einzige Nachteil ist, dass Sie eine Funktion haben müssen, die WinMain heißt, ansonsten sind die C Compiler nicht in der Lage ihren Code mit dem Prolog und Epilog Code zu kombinieren. Sie sind keinen solchen Einschränkungen bei Assembler auferlegt. Sie können jede anderen Funktionsnamen benutzen, anstatt WinMain oder überhaupt keine Funktion.

Bereiten Sie sich vor. Das wird ein langes, langes Tutorial. Lassen Sie uns das Programm zu Tode analysieren!

.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 includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib
Die ersten drei Zeilen sind die "Notwendigkeiten". .386 teilt MASM mit, das wir beabsichtigen den 80386 Befehlssatz in unserem Programm zu benutzen. .model flat, stdcall teilt MASM mit, dass unser Programm das flat Speichermodell benutzen wird. Außerdem werden wir die stdcall Parameterübergabekonvention benutzen als Standard in unserem Programm benutzen.

Als nächstes kommte der Funktions-Prototyp für WinMain. Da wir später WinMain aufrufen werden, müssen wir erst den Funktions-Prototyp definieren, damit wir später invoke benutzen können.

Wir müssen windows.inc am Anfang unseres Source Codes inkludieren. Es enthält wichtige Strukturen und Konstanten, die von unserem Programm genutzt werden. Die Include-Datei, windows.inc, ist lediglich eine Textdatei. Sie können sie mit jedem Texteditor öffnen. Bitte beachte Sie, dass windows.inc (bisher) nicht alle Strukturen und Konstanten enthält. Hutch und ich arbeiten dran. Sie können neue Sachen einfügen, wenn sie noch nicht in der Datei sind.

Unser Programm ruft API-Funktionen auf, die in der user32.dll enthalten sind (CreateWindowEx, RegisterWindowClassEx, zum Beipspiel) und kernel32.dll (ExitProcess), daher müssen wir diese zwei Import Libraries zu unserem Programm hinzulinken. Die nächste Frage: wie kann ich wissen, welche Import Library ich zu meinem Programm linken sollte? Die Antwort: Sie müssen wissen, wo sich die aufzurufenden API-Funktionen befinden. Zum Beispiel, wenn Sie eine API-Funktion aus gdi32.dll aufrufen, müssen Sie gdi32.lib linken.

Das ist der Nachteil von MASM. TASM's Weg Import Libraries zu linken ist wesentlich einfacher, einfach die einzige und wirklich einzige Datei: import32.lib.

.DATA ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 .DATA? hInstance HINSTANCE ? CommandLine LPSTR ?


Als nächstes kommt die "DATA" Sektionen.
In .DATA, deklarieren wir zwei nullterminierte Strings (ASCIIZ Strings): ClassName, welche den Namen unsere Fensterklasse enthält und AppName, die den Namen unsres Fensters enthält. Beachten Sie, dass beide Variablen initialisiert sind.
In .DATA? sind zwei Variablen deklariert: hInstance (Instanz Handle unseres Programms) und CommandLine (Kommandozeile unseres Programms). Die unbekannten Datetypen, HINSTANCE und LPSTR, sind in Wirklichkeit neue Namen für DWORD. Sie können sie in der windows.inc nachschlagen. Beachten Sie, dass alle Variablen in der .DATA? Sektion nicht initialisiert sind, das ist so, weil sie noch keinen speziellen Wert am Anfang haben, aber wir wollen schon mal Speicherplatz für den späteren Gebrauch reservieren.

.CODE start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax ..... end start


.CODE enthält all unsere Befehle. Ihr Code muss nach dem <start Label> beginnen und vor dem end <start Label> enden. Der Name des Label ist dabei unwichtig. Sie können es benennen wie Sie wollen, solange es einmalig ist und nicht die Namenskonventionen von MASM verletzt.

Unser erste Befehl ist der Aufruf von GetModuleHandle, um das Instanz Handle unseres Programms zu erhalten. Unter Win32 sind Instanz Handle und Modul Handle ein und das Selbe. Sie können sich das Instanz Handle als ID ihres Programmes vorstellen. Es wird bei verschiedenen API-Funktionen als Parameter erwartet, die unser Programm aufrufen muss, weshalb es eine gute Idee ist, es am Anfang unseres Programms zu ermitteln.

Anmerkung: Unter Win32 ist das Instanz Handle tatsächlich die lineare Adresse unseres Programms im Speicher.
Nach der Rückkehr einer Win32-Funktion findet sich der ggf. vorhandene Rückgabewert in EAX wieder. Alle anderen Werte werden über Variablen zurückgegeben, die sie in der Parameterliste übergeben haben.

Eine Win32-Funktion die Sie aufrufen, wird fast immer die Segment-Register und EBX, EDI, ESI und EBP sichern. Folglich sind ECX und EDX fast immer undefiniert nach der Rückkehr einer Win32-Funktion.

Anmerkung: Erwarten Sie nicht, dass die Werte in EAX, ECX und EDX durch API-Funktions-Aufrufe gesichert werden.
Der Schluss daraus: wenn Sie eine API-Funktion aufrufen, wird der Rückgabewert in EAX erwartet. Falls irgendeine Funktion von ihnen von Windows aufgerufen wird, müssen Sie auch die Regeln befolgen: sichern Sie die Werte der Segment-Register, EBX, EDI, ESI und EBP und stellen Sie sie vor verlassen der Funktion wieder her, ansonsten wird ihr Programm ziemlich schnell abstürzen, das umfast auch die Fenster-Prozedur und die Callback-Funktionen.

Der GetCommandLine-Aufruf ist unnötig, wenn ihr Programm keine Kommandozeile bearbeitet. In diesem Beispiel zeige ich ihnen, wie Sie sie aufrufen, für den Fall, dass Sie sie in ihrem Programm gebrauchen.

Als nächstes folgt der Aufruf von WinMain. Hier werden vier Parameter übergeben: das Instanz Handle unseres Programms, das Instanz Handle der vorherigen Instanz unseres Programms, die Kommandozeile und der Fensterstatus beim ersten Erscheinen. Unter Win32 gibt es KEINE vorherige Instanz. Jedes Programm befindet sich allein in seinem Adressraum, weshalb der Wert von hPrevInst immer 0 ist. Das ist ein Überbleibsel aus den Win16 Tagen, wo alle Instanzen eines Programmes im selben Adressraum liefen und eine Instanz wissen wollte, ob es die erste sei. Unter Win16 war die Instanz die erste, wenn hPrevInst gleich NULL war.

Anmerkung: Sie müssen den Funktionsnamen nicht als WinMain deklarieren. Sie haben in dieser Hinsicht alle Freiheiten. Sie müssen überhaupt keine WinMain-equivalente Funktion benutzen. Sie können den WinMain-Code hinter GetCommandLine einfügen und ihr Programm wird immer noch perfekt funktionieren.

Nach Rückkehr von WinMain, enthält EAX den Exit Code. Wir übergeben diesen Exit Code ExitProcess als Parameter, was unsere Applikation beenden wird.

WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD


Die obere Zeile ist die Funktionsdeklaration von WinMain. Beachten Sie das Parameter-Typen-Paar, das der PROC Direktive folgt. Das sind Parameter, die WinMain vom Aufrufer erhält. Sie können diese Parameter benutzen, anstatt den Stack zu manipulieren. Außerdem wird MASM den Prolog und Epilog Code für die Funktion generieren. So, dass wir uns nicht selbst um Stackrahmen bei Funktionsein- und Austritt kümmern müssen.

LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND


Die LOCAL Direktive alloziiert Speicher auf dem Stack für lokale Variablen in dieser Funktion. Die LOCAL Direktiven müssen unmittelbar hinter der PROC Direktive folgen. Der LOCAL Direktive folgt unmittelbar <Name der lokalen Variable>:<Variablentyp>. Demnach teilt LOCAL wc:WNDCLASSEX MASM mit, dass es Speicher auf dem Stack in der Größe der WNDCLASSEX Struktur für den Variablennamen wc alloziieren soll. Wir können wc in unserem Code ohne irgendwelche schwierigen Stack-Manipulationen ansprechen. Ich denke, das ist wirklich ein Geschenk des Himmels. Die Kehrseite ist, dass lokale Variablen nicht außerhalb der Funktion benutzt werden können, in denen sie erzeugt wurden und automatisch zerstört werden, wenn die Funktion zum Aufrufer zurückkehrt. Ein weiterer Nachteil ist, dass Sie keine lokalen Variablen automatisch initialisieren können, weil sie nur Stack-Speicher sind, der dynamisch beim Funktionseintritt alloziiert wird. Sie müssen sie manuell, nach der LOCAL Direktive, mit den gewünschten Werten belegen.

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 hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL 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


Die obigen Zeilen sind im Prinzip ganz einfach. Es benötigt lediglich mehrere Zeilen Code um den Befehl aufzurufen. Das Konzept hinter all diesen Zeilen ist die Fenster Klasse. Eine Fenster Klasse ist nichts mehr als eine Vorlage oder Spezifikation eines Fensters. Es definiert einige wichtige Charakteristika eines Fenster, so wie sein Icon, Cursor, die Funktion die für es verantwortlich ist, seine Farbe, etc. Sie erzeugen ein Fenster von einer Fensterklasse. Das ist eine Art Objektorientiertes Projekts. Wenn Sie mehr als ein Fenster mit den selben Charakteristika erstellen wollen, währe es ein Grund, all diese Charakteristika an einem Ort zu speichern und darauf zurückzugreifen, wenn es benötigt wird. Dieses Schema speichert eine Menge Speicherplatz, in dem Duplikationen von Informationen vermieden werden. Erinnern Sie sich, Windows wurde in der Vergangenheit designed, als Speicherchips teuer waren und die meisten Computer nur 1 MB Speicher hatten. Windows musss sehr Effizient mit dem knappen Speicherressourcen umgehen. Der Punkt ist: wenn Sie ihr eigenes Fenster definieren, müssen Sie die gewünschten Charakteristika ihres Fensters in einer WNDCLASS oder WNDCLASSEX Struktur speichern und RegisterClass oder RegisterClassEx aufrufen, bevor Sie ihr Fenster erzeugen können. Sie müssen eine Fensterklasse aus der Sie ein Fenster erstellen nur einmal für jeden Fenstertyp registrieren.

Windows hat verschieden vordefinierte Fensterklassen, so wie Button und Edit Box. Für diese Fenster (oder Steuerelemente) müssen Sie keine Fensterklasse registrieren, rufen Sie lediglich CreateWindowEx mit dem vordefinierten Klassennamen auf.

Das einzige wichtige Element in WNDCLASSEX ist lpfnWndProc. lpfn steht für long pointer to funtion (=long Zeiger auf Funktion). Unter Win32 gibt es keine "near" oder "far" Pointer, nur Pointer, wegen dem neuen FLAT Speichermodell. Aber das ist auch ein Überbleibsel aus Win16 Tagen. Jede Fensterklasse muss mit eine Funktion, die Fenster-Prozedur genannt wird, verbunden werden. Die Fenster-Prozedur ist für das Behandeln von Nachrichten verantwortlich, die von der Fensterklasse erzeugt werden. Windows sendet Nachricht zur Fenster-Prozedur um anzuzeigen, dass wichtige Ereignisse in dem Fenster, für das es zuständig ist, stattgefunden haben, so wie Tastatur- oder Mauseingaben. Es gehört zur Aufgabe der Fenster-Prozedur um intelligent auf diese Nachrichten zu antworten. Sie werden die meiste Zeit damit verbringen, um Ereigniss-Handler in Fenster-Prozeduren zu schreiben.

Ich beschreibe die einzelnen Elemente von WNDCLASSEX unten:

WNDCLASSEX STRUCT DWORD cbSize DWORD ? style DWORD ? lpfnWndProc DWORD ? cbClsExtra DWORD ? cbWndExtra DWORD ? hInstance DWORD ? hIcon DWORD ? hCursor DWORD ? hbrBackground DWORD ? lpszMenuName DWORD ? lpszClassName DWORD ? hIconSm DWORD ? WNDCLASSEX ENDS


cbSize: Die Größe der WNDCLASSEX-Struktur in Bytes. Wir können den SIZEOF Operator benutzen, um den Wert zu ermitteln.
style: Der Stil des Fensters der von dieser Klasse erzeugt wird. Sie können verschieden Stile mit dem "or" Operator kombinieren.

lpfnWndProc: Die Adresse der Fenster-Prozedur die verantwortlich für das von dieser Klasse erzeugt Fenster ist.
cbClsExtra: Spezifiziert die Anzahl der zu alloziierenden Extrabytes, die der Fensterklassen-Struktur folgen soll. Das Betriebssystem initialisiert diesen Wert mit Null. Sie können Fensterklassen-spezifische Daten hier speichern.
cbWndExtra: Spezifiziert die Anzahl der zu alloziierenden Extrabytes, die der Fensterinstanz folgen soll. Das Betriebssystem initialisiert diesen Wert mit Null. Wenn eine Applikation die WNDCLASS-Struktur benutzt um eine Dialog Box zu registrieren, die mit der CLASS Direktive in der Ressource Datei erzeugt wurde, muss dieses Element auf DLGWINDOWEXTRA gesetzt werden.

hInstance: Instanz Handle des Moduls.
hIcon: Handle des Icons. Ermitteln Sie es mit LoadIcon.
hCursor: Handle des Cursor. Emitteln Sie es mit LoadCursor.
hbrBackground: Hintergrundfarbe des Fenster.
lpszMenuName: Standard Menü Handle.
lpszClassName: Der Name dieser Fensterklasse.
hIconSm: Handle eines kleinen Icons, das mit der Fensterklasse verbunden ist. Wenn dieses Element gleich NULL ist, durchsucht das System die Ressource des Icons, das im hIcon-Element spezifziert ist, nach einem Icon das die entsprechende Größe hat, um es als kleines Icon zu benutzen.

invoke CreateWindowEx, NULL,\ ADDR ClassName,\ ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ NULL,\ NULL,\ hInst,\ NULL


Nach der Registrierung der Fensterklasse, können CreateWindowEx aufrufen, um unser Fenster basierend auf der übermittelten Fensterklasse zu erzeugen. Beachten Sie, dass es 12 Parameter für diese Funktion gibt.

CreateWindowExA proto dwExStyle:DWORD,\ lpClassName:DWORD,\ lpWindowName:DWORD,\ dwStyle:DWORD,\ X:DWORD,\ Y:DWORD,\ nWidth:DWORD,\ nHeight:DWORD,\ hWndParent:DWORD ,\ hMenu:DWORD,\ hInstance:DWORD,\ lpParam:DWORD


Schauen wir uns eine genaue Beschreibung jedes Parameters an:
dwExStyle: Extra Fenster Stil. Das ist der neue Parameter der zur alten CreateWindow-Funktion hinzugefügt wurde. Sie können neue Stile für Windows 95 & NT hier angeben. Sie können ihren gewöhnlichen Fenster Stil in dwStyle angeben, aber wenn Sie einige spezielle Stile wie topmost-Fenster wollen, müssen Sie sie hier spezifizieren. Sie können NULL benutzen, wenn Sie keinen extra Fenster-Stile wollen.

lpClassName: (benötigt). Adresse des ASCIIZ Strings, der den Namen der Fensterklasse enthält, die Sie als Template für dieses Fenster benutzen wollen. Die Klasse kann eine eigene registrierte Klasse oder eine vordefinierte Fensterklasse sein. Wie oben erwähnt, muss jedes Fenster, das Sie erzeugen, auf einer Fensterklasse basieren.

lpWindowName: Adresse des ASCIIZ Strings, der den Namen des Fensters enthält. Er wird in der Titelleiste des Fensters angezeigt. Wenn dieser Parameter gleich NULL ist, wird die Titelleiste des Fenster leer sein.
dwStyle:  Stile des Fensters. Sie können hier das Erscheinungsbild des Fensters spezifizieren. NULL zu übergeben ist ok, aber das Fenster wird keine System-Menü-Box, kein Minimierungs-Maximierungs Buttons und kein Fenster-schließen Button. Das Fenster würde fast keinen Nutzen haben. Sie müssten Alt+F4 drücken, um es zu schließen. Der meist verwendetetse Fensterstile ist WS_OVERLAPPEDWINDOW. Ein Fensterstil ist nur ein Bit-Flag. Demnach können Sie verschiedene Fensterstile mit dem "or" Operator kombinieren um das gewünschte Aussehen des Fensters zu erreichen. WS_OVERLAPPEDWINDOW ist dabei eine Kombination der gängigsten Fensterstile.

X,Y: Die Koordinaten der oberen linken Ecke des Fensters. Normalerweise sollte dieser Wert gleich CW_USERDEFAULT sein, was heißen würde, dass Sie Windows für Sie entscheidet, wo das Fenster auf dem Desktop erscheinen soll.

nWidth, nHeight: Die Breite und Höhe des Fensters in Pixeln. Sie können auch hier CW_USEDEFAULT benutzen, um Windows die passende Breite und Höhe für Sie zu wählen.

hWndParent: Ein Handle auf das Elternfenster des Fensters (falls dieses existiert). Dieser Parameter teilt Windows mit, ob dieses Fenster ein Kind (untergeordnet) eines anderem Fensters ist und, wenn es so ist, welches Fenster das Eltern ist. Beachten Sie, dass das nicht die Eltern-Kind Beziehung vom Multiplen Dokumenten Interface (MDI) ist. Kindes-Fenster sind nicht an die Grenzen der Client Area der Eltern-Fenster gebunden. Diese Beziehung ist speziell für Windows internen Gebrauch. Wenn das Eltern-Fenster zerstört wird, werden die Kindes-Fensters automatisch zerstört. Es ist wirklich so einfach. Da es in unserem Beispiel nur ein Fenster gibt, ist dieser Parameter bei uns gleich NULL.

hMenu: Ein Handle auf das Menü des Fensters. Gleich NULL wenn das Klassenmenü benutzt wird. Schauen Sie noch mal auf das Element der WNDCLASSEX-Struktur, lpszMenuName. lpszMenuName spezifiziert das *Standard* Menü für die Fensterklasse. Jedes Fenster die mit dieser Fensterklasse erstellt wird, wird standardmäßig das selbe Menü haben. Es sei denn, Sie *überschreiben* für ein spezielles Fenster über seinen hMenu Parameter. hMenu ist dabei ein dualer-Zweck-Parameter. In dem Fall, dass das Fenster, das Sie erstellen möchten, ist ein vordefinierter Fenstertyp (z.B. Control), solch ein Control kann kein Menü besitzen. hMenu wird statt der ID von Control verwendet. Windows kann entscheiden, ob hMenu ist wirklich ein Menü Handle oder eine Control ID, indem es auf den lpClassName Parameter schaut. Wenn es der Name einer vordefinierten Klasse ist, hMenu ist eine Control ID. Wenn dem nicht so ist, dann ist es ein Handle auf ein Fenster-Menü.

hInstance: Das Instanz Handle des Programm-Moduls, dass das Fenster erzeugt.

lpParam: Optionaler Pointer auf eine Daten-Struktur die dem Fenster übergeben wird. Wird von MDI Fenstern benutzt um CLIENTCREATESTRUCT Daten zu übergeben. Normalerweise wird dieser Wert auf NULL gesetzt, was bedeutet, dass keine Daten via CreateWindow() übergeben werden. Das Fenster kann den Wert dieses Parameters ermitteln, indem die GetWindowLong-Funktion aufgerufen wird.

mov hwnd,eax invoke ShowWindow, hwnd,CmdShow invoke UpdateWindow, hwnd


Nach erfolgreicher Rückkehr von CreateWindowEx, wird das Fenster Handle in EAX zurückgeliefert. Wir müssen diesen Wert für den späteren Gebrauch sichern. Das Fenster, das wir eben erzeugt haben, wird nicht automatisch angezeigt. Sie müssen ShowWindow zusammen mit dem Fenster Handle und dem gewünschten *Anzeigestatus* des Fensters aufrufen, um es auf den Bildschirm anzuzeigen. Als nächstes können Sie UpdateWindow aufrufen, damit ihr Fenster seine Client Area neuzeichnet. Diese Funktion ist nützlich, wenn Sie den Inhalt ihrer Client Area updaten wollen. Sie können diesen Aufruf allerdings weglassen.

.WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW


Zur Zeit ist unser Fenster auf dem Bildschirm. Aber es kann noch keine Eingaben von der Welt entgegennehmen. Deshalb müssen wir es über relevante Ereignisse *informieren*. Wir erreichen das mit einer Message Loop (=Nachrichtenschleife). Es gibt jeweils nur eine Message Loop je Modul. Diese Message Loop checkt kontinuierlich die Nachrichten von Windows mit einem GetMessage Aufruf. GetMessage wird ein Pointer auf eine MSG-Struktur übergeben und die wiederum an Windows. Diese MSG-Struktur wird mit den Informationen über die Nachricht gefüllt, die Windows an ein Fenster in einem Modul sendet. Die GetMessage Funktion wird so lange nicht zurückkehren, bis eine Nachricht für das Fenster vorliegt. Während dieser Zeit kann Windows die Kontrolle anderen Programmen übergeben. Das ist das Kooperative Multitasking Schema von Win16 Plattformen. GetMessage gibt FALSE zurück, wenn eine WM_QUIT Nachricht in der Message Loop empfangen wird, beendet die Message Loop und verlässt das Programm.

TranslateMessage ist eine Hilfsfunktion, die aus normalen Tastatureingaben eine neue Nachricht erzeugt (WM_CHAR), welche in die Message Queue eingereiht wird. Die WM_CHAR Nachricht enthält den ASCII Wert für die gedrückte Taste, was einfacher zu handeln ist, als bloße Tastatur Scancodes. Sie können diesen Aufruf weglassen, wenn ihr Programm keine Tastenanschläge verarbeitet.

DispatchMessage sendet die Nachrichten-Daten an die Fenster-Prozedur, die für das spezifizierte Fenster an das die Nachricht ist, zuständig ist.

mov eax,msg.wParam ret WinMain endp


Wenn die Message Loop beendet wird, ist der Exit Code im wParam Element der MSG-Struktur gespeichert. Sie können den Exit Code in EAX speichern um ihn an Windows zurückzugeben. Zur Zeit macht Windows keinen Gebrauch vom Rückgabewert, aber es ist besser auf der sicheren Seite zu sein und sich an die Regeln zu halten.

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM


Das ist unsere Fenster-Prozedur. Sie müssen sie nicht WndProc nennen. Der erste Parameter, hWnd, ist das Fenster-Handle des Fensters, für das die Nachrichten bestimmt sind. uMsg ist die Nachricht. Beachten Sie, dass uMsg keine MSG-Struktur ist. Es ist nur eine Nummer, wirklich. Windows definiert hunderte von Nachricht, die meisten wird ihr Programm aber nicht interessieren. Windows sendet die passende Nachricht zu einem Fenster, in dem Fall, dass etwas relevantes mit Fenster passiert ist. Die Fenster-Prozedur erhält Nachrichten und reagiert angemessen auf sie. wParam und lParam sind nur extra Parameter, die von manchen Nachrichten benutzt werden. Einige Nachrichten senden zusätzliche Daten zur Nachricht. Diese Daten werden der Fenster-Prozedur halt über lParam und wParam übergeben.

.IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp


Hier kommt der entscheidende Part. Hier befindet sich die Intelligenz ihres Programms. Der Code, der auf jede Windows Nachricht antwortet befindet sich in der Fenster-Prozedur. Ihr Code muss die Windows Nachrichten überprüfen um zu sehen, ob eine Nachricht dabei ist, die von Interesse ist. Falls dem so ist, machen Sie was Sie möchten, um auf die Nachricht zu antworten und kehren Sie dann mit einer Null in EAX zurück. Falls dem nicht so ist, MÜSSEN Sie DefWindowProc aufrufen, alle Parameter übergebend, die Sie erhalten haben, für die Standardabarbeitung. Diese DefWindowProc ist eine API-Funktion bearbeitet all die Nachrichten, die für Sie nicht von Interesse sind.

Die einzige Nachricht auf die Sie antworten MÜSSEN, ist WM_DESTROY. Diese Nachricht wird an ihre Fenster-Prozedur gesendet, wenn ihr Fenster geschlossen wird. Wenn ihr Fenster diese Nachricht erhält, ist ihr Fenster schon vom Bildschirm verschwunden. Das ist nur eine Anmerkung, dass ihr Fenster schon zerstört wurde und Sie sich auf die Rückkehr zu Windows bereit machen sollten. Als Antwort darauf können Sie noch Aufräumarbeiten machen, bevor Sie zu Windows zurückkehren. Sie haben keine andere Wahl als das Programm zu beenden, wenn diese Nachricht kommt. Wenn Sie eine Chance haben wollen, den Benutzer vom Schließen des Fensters abzuhalten, sollten Sie die WM_CLOSE Nachricht bearbeiten. Nun zurück zu WM_DESTROY, nach den Aufräumarbeiten müssen Sie PostQuitMessage aufrufen, was eine WM_QUIT Nachricht an ihr Modul senden wird. WM_QUIT wird GetMessage einen Null Wert in EAX zurückliefern lassen, was wiederum die MessageLoop beendet und zu Windows zurückgekehrt wird. Sie können selbst ein WM_DESTROY Nachricht an ihr eigenes Fenster senden, indem Sie die DestroyWindow Funktion aufrufen.


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