--->also available in English.

Assembler Win32 Tutorial Kapitel 1.2beta

Na, den ersten Teil schon geschafft? Dann gehts gleich weiter mit der Programmierung eines Fensters. Der Code kann als Basis für weitere Projekte verwendet werden.

Inhalt:

Diesmal gibt es kaum Sourcecode im Text, da das meiste sprachunabhängig ist. Quellcode für NASM/TASM/MASM liegt wieder im ZIP bei.

Achtung: Die derzeit brauchbarste windows.inc stammt von Iczelion und Hutch, sie funktioniert leider nur mit MASM. Für NASM gibt es einen Port einer älteren MASM-windows.inc, die aber schon alles Wichtige enthält. Die mir bekannte .inc für TASM deckt leider nur den Bereich ab, der schon unter Win3.x verfügbar war - man wird sich unter TASM die Daten also von den anderen include-Dateien holen müssen.

1. Fenster und Fensterklassen

Ein Fenster dient unter Windows nicht nur dazu, etwas auf dem Bildschirm darzustellen, sondern es stellt den Grundstein der Kommunikation unter Windows dar. Um Tastendrücke, Mausbewegungen, etc. abzufangen braucht man ein Fenster. Manches läßt sich zwar auch pollen, aber
1. ist das alles andere als effektiv und
2. kann zwischen 2 Polls auch etwas passieren, was dann unerkannt bleibt.

Nicht nur das Standardfenster, sondern auch PopUp-Menüs, Buttons, der Desktop, TreeViews, ... sind Fenster. Jedes Fenster wird von einer Fensterklasse abgeleitet, welche bestimmte Eigenschaften des Fensters beschreibt. Der Sinn der Fensterklassen zeigt sich vor allem, wenn ein Programm mehrere Fenster benutzt.
Ein Fenster kann viele Eigenschaften haben: minimiert, maximiert, angezeigt, versteckt, aktiv, inaktiv, mit oder ohne Titelleiste... Die Eigenschaften lassen sich durch das eigene oder externe Programme ändern
(ein Button hat normalerweise nur 2 Möglichkeiten: Versteckt oder sichtbar. Man kann ihn aber genauso gut minimieren oder maximieren, sieht mal ganz anders aus :-).

2. Messages und ihre Verarbeitung

Messages werden verschickt, um ein Ereignis oder eine Anforderung einem Programm mitzuteilen.

Sie werden erzeugt von: Sie werden geschickt an: Eine Message besteht aus einer MessageID, die angibt, um welche Message es sich handelt, sowie 2 Parametern, die erweiterte Informationen über das Ereignis geben (lParam und wParam).

Zur Verarbeitung wird für das Fenster/den Thread eine MessageQueue erzeugt. Diese nimmt alle eingehenden Meldungen auf. Mit der Funktion GetMessage liest man sie der Reihe nach aus. Dabei liefert GetMessage IMMER eine Meldung zurück. Dies liegt daran, das diese Funktion solange wartet (und anderen Programmen Rechenzeit gibt), bis eine Meldung eingeht. Wer nicht warten will, kann statt dessen PeekMessage verwenden. Diese Funktion kommt sofort zurück. Sie liefert deshalb zusätzlich, ob überhaupt eine Message vorhanden war. Ist der Rückgabewert von GetMessage gleich 0, so wurde WM_QUIT empfangen.

Mit DispatchMessage leitet man die Message über das OS an die Fensterprozedur weiter.

Kleine Auswahl wichtiger Messages:
WM_CREATEDas Fenster wurde erzeugt. Wird automatisch von CreateWindowEx erzeugt
WM_QUITDas Programm soll beendet werden
WM_DESTROYDas Fenster wird sogleich eliminiert
WM_PAINTDas Fenster soll neu gezeichnet werden
WM_KEYDOWN, WM_KEYUPBrauch man ja wohl nicht erklären. Nützlich: Der Scancode wird mitverschickt
WM_SYSKEYDOWN, WM_SYSKEYUPWie oben, nur in Kombination mit der ALT-Taste (Menübefehle
WM_MOUSEMOVEWas wohl... enthält zudem den Status der Maustasten. Für die Maustasten selbst gibt es noch die Meldungen WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN, WM_RBUTTONUP
WM_TIMERWird vom Standardtimer verschickt (falls aktiviert). Die Multimediatimer sind für Demos und Spiele jedoch besser geeignet
WM_ACTIVATEAnwendung wird aktiviert oder deaktiviert. Sollte man in Demos und Spielen auswerten, um alle Threads anzuhalten, damit das ganze nicht im Hintergrund weiterläuft
WM_COMMANDEin Menüpunkt wurde ausgewählt

3. Die Fensterprozedur

Zu jedem Fenster gehört eine Fensterprozedur, welche die Messages, die durch DispatchMessage weitergeleitet wurden, endgültig verwurstet.
Bisher wurden nur die Windows-Funktionen aufgerufen. Jetzt ruft Windows zur Abwechslung unsere Funktion auf! Diese CALLBACKS folgen wie die meisten API-Funktionen der STDCALL-Konvention: Parameter landen auf dem Stack, Rückgabewert in EAX, der Stack muß von der aufgerufenen Funktion abgeräumt werden. Callbacks treten z.B. auch bei Verwendung der Multimedia-Timer auf.

Die Parameter der Fensterprozedur sind:
Fensterhandle, MessageID, wParam, lParam.
In dieser Reihenfolge liegen sie auf dem Stack wenn die Fensterprozedur aufgerufen wird. Sie entsprechen der Reihenfolge der MSG-Struktur für GetMessage,PeekMessage und Dispatch Message.

Nun kann man auf dem Stack die MessageID auslesen, wenn die Meldung von Interesse ist mittels wParam und lParam auswerten oder die Abarbeitung Windows überlassen.

Hat man die Message selbst verarbeitet, so kann man direkt mit RET 16 zurückkehren. Die 16 hinter dem RET gibt an, wieviele Bytes vom Stack genommen werden sollen.

Ist die Message unvollständig oder gar nicht bearbeitet worden, kann man die Abarbeitung DefaultWndProc überlassen. Diese Windows-Funktion enthält alle wesentlichen Elemente, um die Messages für ein Fenster zu behandeln.

Da unsere Fensterprozedur nur eine Erweiterung von DefaultWndProc darstellt besitzen beide Funktionen dieselbe Parameterstruktur. Dies kann man nützen, indem man, anstatt die Parameter auf den Stack zu bringen und per CALL die DefaultWndProc aufruft, direkt mit JMP in die Funktion springt. Bedingung ist natürlich, das wir den Stack unverändert lassen. Dadurch sparen wir sowohl das Anlegen der Parameter als auch den Rücksprung - den macht jetzt nämlich DefaultWndProc.

4. Menüs

Menüs sind zwar nicht unbedingt nötig für ein Programm, aber es ist einfach, sie in einem Programm zu verwenden. Dazu gibt es zwei Möglichkeiten:
a) Das ganze mit Hilfe der Windows-Funktionen im Programm aufzubauen (wenn einem schwer langweilig ist)
b) Das Menü in eine Ressource einzubauen - entweder von Hand in das Script tippen oder mit einem Ressourceeditor

Zur Syntax des Menüs im Beispiel:
Win002menu MENU gibt an, das ein Menü namens Win002menu definiert wird. Das Menü selbst wird zwischen dem folgenden BEGIN...END-Block eingebaut.
MENUITEM "Menüpunkt" , zahlerzeugt einen Menüpunkt mit dem Namen Menüpunkt und einer ID von zahl
POPUP "Untermenüname"erzeugt ein Untermenüpunkt mit entsprechendem Namen. Das Unter-menü steht wieder zwischen einem BEGIN - END - Paar.
MENUITEM SEPARATORerzeugt einen Trennbalken
Nun kann man das Menü laden, entweder als Menü mit fester Position wie das Menü unterhalb der Titelleiste eines Fensters, oder als Menü mit variabler Position, also ein Kontextmenü.
Mit den API-Funktionen kann man (muß aber nicht) das Aussehen des Menüs verändern. Nach Gebrauch muß es wieder gelöscht werden - sonst gibts mit der Zeit Speichermangel.

Im Beispielcode wird das Menü auf die einfachste Weise verwendet: Gibt man es bei der Definition der Fensterklasse mit an, so wird es automatisch geladen, aktiviert und wieder gelöscht.

Sobald ein Menüpunkt ausgewählt wird, wird eine WM_COMMAND Message erzeugt. In deren wParam-Feld steht die ID des Menüpunktes.

5. Das Beispielprogramm

1. Aktion: Erzeugen einer Fensterklasse

Mit RegisterClassEx wird eine Fensterklasse definiert.
Folgende Felder in der WNDCLASSEX-Struktur werden dazu gebraucht:
cbSizeGibt die Größe der Struktur an. Setze auf size WNDCLASSEX
styleBeschreibt das Verhalten des Fensters. Siehe API "Class Styles" (CS_xxx)
lpfnWndProcPointer auf die Fensterprozedur
cbClsExtraFalls die Klasse erweitert werden soll. Kann 0 bleiben.
cbWndExtraDasselbe für ein Fenster. Kann außer für Dialogboxen 0 bleiben
hInstanceKennzeichnet unser Programm. Wird mit GetModuleHandle ermittelt
hIconIcon fürs Fenster. Setze 0 für ein Standardicon, sonst Handle einer Ressource
hCursorWie Icon, 0 = Standardcursor
hbrBackgroundHintergrundfarbe. Falls auf 0 gesetzt, wird das Fenster nicht automatisch gefärbt
lpszMenuNameName der Menüressource (0 = kein Menü)
lpszClassNameName, der die Klasse identifiziert. Darf man nennen wie man will
hIconSmKleines Icon. Siehe normales Icon
Handles für Ressourcen müssen mit LoadIcon bzw. LoadCursor oder LoadBitmap erzeugt werden!

2. Her mit dem Fenster!

CreateWindowEx erzeugt nun das Fenster.
Die Parameter sind (Reihenfolge wie im Source):
lpParamZeigt auf einen Wert für lParam der WM_CREATE Message. Braucht man nur für Anwendungen mit Unterfenstern. Sonst 0.
hInstanceIrgendwo hab ich das schon mal erklärt...
hMenuHier kann man ein Menühandle angeben. Da wir schon in der Klasse ein Menü angegeben haben kann man hier 0 setzen. Dadurch muß man auch kein Handle ermitteln.
hWndParentHandle des ParentWindows. Da wir kein Unterfenster erzeugen wird dies auf 0 gesetzt.
nHeightkein Kommentar :-)
nWidthbraucht man nichts dazu zu sagen :-)
yHat irgendwas mit der Position der Fensterecke oben links zu tun :-)
xWie y :-)
dwStyleBestimmen das Aussehen/Verhalten des Fensters. Werden mit OR verknüpft.
lpWindowNameZeigt auf den Namen des Fensters. Hat nur einen optischen Nutzen.
lpClassNameZeigt auf den Namen der Klasse. Eine entsprechende Klasse muß existieren, sonst gibts kein Fenster (siehe RegisterClassEx / WNDCLASSEX)
dwExStyleErweiterung von dwStyle, funktioniert auch so. Erst ab Win95/NT4 vorhanden.
Die Funktion liefert bei Erfolg ein Handle für das Fenster, sonst 0.
Wurde ein Fenster erfolgreich erzeugt, so werden mehrere Messages (WM_CREATE,...) DIREKT an die Fensterprozedur geschickt. Die Fensterprozedur wird hier also nicht über die MessagePump aufgerufen!

3. Die Meldungsschleife aka MessageLoop aka MessagePump

Mit GetMessage leert man Message für Message die Message Queue. GetMessage erwartet 4 Parameter: Mit zweien kann man den Bereich der zu verarbeitenden Messages einschränken, z.B. nur Mausmeldungen herunteholen etc. Um alle Messages zu erhalten, setzt man beide auf 0.
Der nächste Parameter beschränkt die empfangenen Messages auf ein bestimmtes Fenster. Er wird dazu auf das entsprechende Fensterhandle gesetzt. Ein Wert von 0 holt alle Messages, die irgendwas mit unserem laufenden Programm zu tun haben.
Der letzte Parameter zeigt auf einen 7 DWords großen Puffer, in dem die Message abgelegt wird.
Es gibt 3 Rückgabewerte: 1 bedeutet eine erfolgreich empfangene Message. 0 bedeutet, daß WM_QUIT empfangen wurde, eine Aufforderung zum Beenden des Programmes, speziell des Message Loops. Es kann auch -1 zurückkommen, aber dann ging etwas mächtig schief, z.B. das Fensterhandle war falsch (in der Praxis ist dann euer Code oder u.U. das ganze System am dampfen). Wer nicht die meiste Zeit auf GetMessage warten will, bis eine Meldung vorliegt kann auch PeekMessage benützen. Dadurch kann man seine Animation auch in der MessageLoop abarbeiten. Klappt ganz gut, ein separater Thread dafür ist aber die elegantere Methode.

Mit DispatchMessage kann man nun die Message an eine Fensterprozedur weiterleiten. Möchte man einen Text eingeben lassen, so sollte man davor noch mit TranslateMessage die WM_KEYDOWN-Messages in WM_CHAR-Messages verwandeln.

4. Die Fensterprozedur

Hier gibt es nicht viel dazu zu sagen: Man schaut, ob die aktuelle Message interessant ist.
Ist sie es, so kann man irgendwas machen und sofort zurückkehren, falls nicht läßt man Windows das erledigen. Man kann auch eine Message verarbeiten und danach noch an DefWndProc weitergeben, das ist aber nur selten nützlich.

Interessant ist vielleicht noch der Aufruf von PostQuitMessage. Wie der Name schon sagt wird eine WM_QUIT Message verpostet. Der Parameter der Funktion wird zu lParam der Message und stellt einen Rückgabewert dar. Statt dieser Funktion könnte man auch ein Flag setzen, aber da auch andere Quellen WM_QUIT erzeugen können ist diese Version die sauberere.

6. Was noch?

Mehr fällt mir zu diesem Thema grad nicht mehr ein. In der nächsten Folge wirds dann so langsam multimedial.
Mail an den Autor: webmeister@deinmeister.de

Hauptseite Programmieren Win32Asm Downloads Software Hardware Cartoons+Co Texte Sitemap