Einführung in die Grundlagen der Win-API Programmierung


BungeeBug hatte mich gefragt, ob ich ihm mal eine persönliche Einführung in die Win32-API Programmierung unter Delphi per ICQ geben könnte. Da es auch andere interesssiert, wie es scheint, habe ich mich dazu entschlossen, mit der Genehmigung von BungeeBug, das ICQ-Log in leicht überarbeiteter Form hier zu veröffentlichen.

Es kann in dieser Form als eine erweiterete Einführung zu meinen nonVCL-Tutorials auf meiner Seite gesehen werden.

Luckie (11:29 PM):
Als Grundgerüst könnte man das erste Tutorial nehmen ("Fenster").
Luckie (11:33 PM):
Also Windows basiert auf Messages.
Luckie (11:33 PM):
Windows kommuniziert über Messages mit den Programmen/Fenstern.
Luckie (11:34 PM):
Beispiel:
Luckie (11:34 PM):
Zwei Fenster. Fenster A überdeckt Fenster B.
Luckie (11:34 PM):
Fenster A wird geschlossen oder verschoben und Fenster B wird sichtbar.
Luckie (11:35 PM):
Jetzt muß der Bereich, der von B eingenommen wird neu gezeichnet werden.
Luckie (11:35 PM):
Klar?
BungeeBug (11:35 PM):
jup
BungeeBug (11:35 PM):
klingt logisch
Luckie (11:35 PM):
OK. Da Windows aber nicht weiß, was auf dem Fenster ist, kann es das nicht selber machen. Das weiß eben nur Fenster B.
BungeeBug (11:36 PM):
also sendet win ne nachricht "mach neu" an Fenser B?!
Luckie (11:36 PM):
Also schickt Windows an Fenster B eine Nachricht, die B sagt. "Sorg bitte dafür, dass der Bereich deines Fensters wieder stimmt."
Luckie (11:37 PM):
Die Nachricht heißt: WM_PAINT.
BungeeBug (11:37 PM):
ok
Luckie (11:38 PM):
Damit dieser Mechanismus funktioniert brauchen wir zwei Dinge: auf der einen Seite etwas wo Windows die Nachrichten plazieren kann und auf der anderen Seite etwas, das die Nachrichten abholt.
Luckie (11:38 PM):
Auch klar?
BungeeBug (11:38 PM):
sowas wie nen postkasten ?!
Luckie (11:38 PM):
Jupp.
BungeeBug (11:39 PM):
dann hab ichs verstanden
Luckie (11:39 PM):
Der Postkasten heißt Messagequeue.
Luckie (11:39 PM):
Un der Idiot der städig hinrennt und nachkuckt heißt Messageloop.
Luckie (11:40 PM):
Die Messagequeue wird von Windows gestellt und die Messageloop vom Prozess des Fensters.
Luckie (11:40 PM):
Die Messageloop ist eine Endlosschleife, die solange läuft bis eine bestimmte Nachricht kommt.
Luckie (11:41 PM):
So bald in der Messagequeue ein WM_QUIT auftaucht, wird die Messageloop beendet und das Programm geschlossen.
Luckie (11:43 PM):
So. Wie sieht die Umsetztung dieses Teils in Delphi aus?
Luckie (11:44 PM):
Also die Messageloop befindet sich im Hauptprogramm, das ist die DPR-Datei.
Luckie (11:45 PM):

   while true do
   begin
     if not GetMessage(msg, 0, 0, 0) then
       break;
     TranslateMessage(msg);
     DispatchMessage(msg);
   end;

Luckie (11:46 PM):
So das entscheidende ist GetMessage. Das PSDK sagt dazu als Rückgabewert: If the function retrieves a message other than WM_QUIT, the return value is nonzero. If the function retrieves the WM_QUIT message, the return value is zero.
Luckie (11:47 PM):
Wenn also WM_QUIT kommt habe ich null als Rückgabewert und das break wird ausgelöst und damit die while-Schleife verlassen. Der Rest des Hauptprogramms wird abgearbeitet und wir landen beim end mit dem Punkt dahinter.
Luckie (11:48 PM):
Fertig, ende, aus, tot.
Luckie (11:48 PM):
So das war aber nur die erste Hälfte.
Luckie (11:49 PM):
Es nützt ja alles nichts, wenn ich zwar die Nachrichten abhole habe nichts damit anfange.
Luckie (11:50 PM):
In der while-Schleife fallen zwei Funktionen auf: TranslateMessage(msg); DispatchMessage(msg);
Luckie (11:50 PM):
TranslateMessage ist erstmal unwichtig.
Luckie (11:51 PM):
Eine Rolle spielt hier erstmal nur DispatchMessage.
Luckie (11:52 PM):
DispatchMessage leitet die Nachrichten an eine Fensterprozedur weiter, die wir in unserem Programm deklarieren müssen.
Luckie (11:52 PM):
Sie sieht im Grundgerüst so aus.
Luckie (11:53 PM):
   function WndProc(hWnd: HWND; uMsg: UINT; wParam: wParam; lParam: LParam): lresult; stdcall;
   begin
     Result := 0;
     case uMsg of
       WM_CREATE:
       begin
       end
     else
       Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
     end;
   end;

Luckie (11:53 PM):
Im parameter uMsg steht die Nachricht drin, die wir abgefangen haben.
Luckie (11:54 PM):
In hWnd das Fensterhandle an das die Nachricht gerichtet ist.
Luckie (11:54 PM):
wParam und lParam sind weitere messagespezifische Parameter.
BungeeBug (11:55 PM):
wo bekomme ich das Handle her?
Luckie (11:55 PM):
Wird dir von Windows mitgeliefert.
BungeeBug (11:55 PM):
also hab ich damit nix am Hut?
Luckie (11:55 PM):
Nein.
BungeeBug (11:55 PM):
ok
Luckie (11:56 PM):
Richten wir nun unser Augenmerk auf den else Zweig der case-Konstruktion.
Luckie (11:56 PM):
DefWindowProc.
Luckie (11:57 PM):
Steht für Default Window Procedure.
Luckie (11:57 PM):
Alle Nachrichten die mit case nicht abgearbeitet werden landen im else Zweig und werden an die DefWindowProc weitergereicht.
BungeeBug (11:59 PM):
wird dann immer der gleiche code ausgeführt oder hat windows da was parat?
Luckie (11:59 PM):
Dazu komme ich jetzt.
Luckie (00:00 AM):
Butons, Listboxen usw. sind auch nur Fenster mit einer eigenen WindowsProcedure.
Luckie (00:00 AM):
Jetzt ist dir sicherlich aufgefallen, das man sich bei denen nicht um das Neuzeichnen kümmern muß.
BungeeBug (00:01 AM):
also nur bei der Form?
Luckie (00:01 AM):
Das liegt daran, weil das die Fensterprozedur von den Kontrols übernimmt.
BungeeBug (00:01 AM):
ok
Luckie (00:01 AM):
Zeichen ich jetzt in OnPaint auf das Form, um mal ein VCL Beispiel zu nehmen, existiert keine Fensterprozedur dafür.
Luckie (00:02 AM):
Klar? Woher soll auch Windows wissen, was du auf deinem Fenster gemalt hast.
BungeeBug (00:02 AM):
jo
Luckie (00:02 AM):
Ergo mußt du das selber machen, in dem du WM_PAINT abfängst und dort entspre class="precode"chend reagierst.
Luckie (00:03 AM):
Das wären zwei drittel der Grundlagen.
Luckie (00:03 AM):
Kucken wir uns noch mal den Rest an.
BungeeBug (00:03 AM):
bis jetzt hab ichs verstanden
Luckie (00:03 AM):
Guter Schüler.
BungeeBug (00:04 AM):
ich geb mir mühe
Luckie (00:04 AM):
Kuck die mal den Source zu dem Fensterdemo an.
BungeeBug (00:04 AM):
hab ich
Luckie (00:04 AM):
Hast du?
BungeeBug (00:04 AM):
was genau?
Luckie (00:04 AM):
Scroll ganz runter.
Luckie (00:05 AM):
     var
     {Struktur der Fensterklasse}
     wc: TWndClassEx = (
       cbSize :SizeOf(TWndClassEx);
       Style :CS_HREDRAW or CS_VREDRAW;
       lpfnWndProc :@WndProc;
       cbClsExtra :0;
       cbWndExtra :0;
       hbrBackground :COLOR_APPWORKSPACE;
       lpszMenuName :nil;
       lpszClassName :ClassName;
       hIconSm :0;
     );
     msg: TMsg;

     begin
       wc.hInstance: = hInstance;
       wc.hIcon: = LoadIcon(hInstance, MAKEINTRESOURCE(100));
       wc.hCursor: = LoadCursor(0, IDC_ARROW);
       {Fenster registrieren}
       RegisterClassEx(wc);

BungeeBug (00:05 AM):
hab ich
Luckie (00:05 AM):
Dahin. das ist der einzigste bisher unklare Punkt.
Luckie (00:06 AM):
So, was passiert da?
Luckie (00:06 AM):
Bzw. warum machen wir das?
Luckie (00:06 AM):
Windows unterhält für jedes Fenster eine Messagequeue.
Luckie (00:07 AM):
Nur müssen wir Windows dazu veranlassen diese einzurichten und entspre class="precode"chend Speicher für das Fenster bereitzustellen. Mal so vereinfacht gesagt.
Luckie (00:07 AM):
Dies geschiet mit der Funktion RegisterClassEx.
Luckie (00:08 AM):
Diese Funktion erwartet eine Zeiger auf eine Struktur vom Typ TWndClassEx.
Luckie (00:09 AM):
Diese Struktur wird mit Werten vorbelegt, die unser Fenster charackterisieren.
Luckie (00:10 AM):
Wie zum Beispiel, der Hintergrundfarbe, dem Icon, dem Cursor, dem Menü usw.
Luckie (00:10 AM):
Und ganz wichtig. Über die Strukturvariable lpfnWndProc wird eine Verknüfung von DispatchMessage zu unserer Fensterprozedur hergestell.
Luckie (00:12 AM):
Ist dies geschehen, können wir unser Fenster mit CreateWindowEx erzeugen und anzeigen.
Luckie (00:13 AM):
Alles klar?
BungeeBug (00:13 AM):
muss ich die verbindung selbst herstellen oder passiert das von selbst?
Luckie (00:14 AM):
Die stellst du selber her, in dem du bei lpfnWndProc deine Fensterprozedur angibst.
BungeeBug (00:14 AM):
also das hier? lpfnWndProc : @WndProc;
Luckie (00:14 AM):
Genau.
BungeeBug (00:14 AM):
ok verstanden
Luckie (00:15 AM):
Das war eigentlich schon alles.
Luckie (00:15 AM):
Und das alles macht auch die VCL, nur dass sie es vor dir versteckt.
BungeeBug (00:15 AM):
und jetzt kann ich nen leeres Forumlar erzeugen?
Luckie (00:15 AM):
nonVCl Grundkenntinsse tragen also auch zum tieferen Verständnis von Windows bei, wie du siehst.
Luckie (00:16 AM):
Genau. Nur das wir wohl besser von Fenster reden.
BungeeBug (00:16 AM):
das meiste hab ich davon gewusst nur nich das es so funktioniert
Luckie (00:16 AM):
Ok. Jetzt machen wir es mal, wie in der Schule. Ich stelle mal ein paar Fragen und du antwortest mir.
BungeeBug (00:17 AM):
i try
Luckie (00:17 AM):
Gut. Lange for-Schleife mit irgendwas drin.
Luckie (00:17 AM):
Dauert 3 Minuten oder so.
Luckie (00:17 AM):
Warum reagiert mein Fenster nicht mehr?
BungeeBug (00:18 AM):
weil wärend der schleife keine nachrichten angenommen werden?
Luckie (00:19 AM):
Genau. Dein Programm/Fenster hängt in der for-Schleife und kann demnach mit der Messageloop keine Nachrichten empfangen, an die Fensterprozedur weiterleiten und abarbeiten.
BungeeBug (00:20 AM):
*freu*
Luckie (00:20 AM):
Warum kommen WM_TIMER Nachrichten nicht immer pünktlich bzw. werden nicht immer im Intervall abgearbeitet?
BungeeBug (00:21 AM):
ich denk mal aus dem gleichen Grund weil irgend ne Schleife mal länger braucht als der Intervall is und tada ne nette verzögerung
Luckie (00:21 AM):
Also OnTimer wäre das in der VCL.
Luckie (00:21 AM):
Nicht ganz.
BungeeBug (00:22 AM):
dann beleher mich
Luckie (00:22 AM):
Nehmen wir an es kommt ein WM_TIMER - WM_PAINT - WM_TIMER.
Luckie (00:22 AM):
Intervall 1 Sekunde.
Luckie (00:22 AM):
Jetzt braucht dein Programm aber 5 Sekunden um den Code in WM_PAINT auszuführen.
Luckie (00:23 AM):
Und was ist? Käse war es mit dem Intervall von einer Sekunde.
BungeeBug (00:23 AM):
das is das was ich meinte ...
Luckie (00:24 AM):
OK. Ist jetzt extrem. Aber es zeigt, was ich meine. Zu dem haben WM_TIMER Messages eine sehr geringe Priorität bei Windows.
Luckie (00:24 AM):
Nächste Frage.
Luckie (00:24 AM):
Warum muß ich mich um das Neuzeichnen von Buttons nicht kümmern?
BungeeBug (00:26 AM):
weil das das Fenster sprich lpfnWndProc
:@WndProc; macht?!
Luckie (00:26 AM):
Mit der Fensterprozedur hat es was zu tun.
BungeeBug (00:27 AM):
ach ne das gibt nur an wie das Fenster aussieht nicht wo die buttons sind oder?
Luckie (00:27 AM):
Das gibt nur an welche Fenster zu der Fensterklasse gehört, die du mit RegisterWindowsEx registriert hast.
Luckie (00:27 AM):
Du kuckst an der falschen Stelle.
Luckie (00:28 AM):
Kuck dir mal die Fensterprozedur an.
Luckie (00:28 AM):
case ... else ??? end;
Luckie (00:29 AM):
Der entscheidenede Code steht bei den ???
Luckie (00:30 AM):
Noch 30 Sekunden.
BungeeBug (00:30 AM):
hmm ... ich passe ... evl seh ich den wald gard nich
Luckie (00:31 AM):
Jedes Fenster, auch Buttons, haben eine eigenständige Fensterprozedur.
Luckie (00:31 AM):
Da Windows weiß, wie ein Button aussieht kann es den Button selber zeichnen.
Luckie (00:32 AM):
Kuck dir noch mal an, was ich zu DefWindowProc geschrieben habe.
Luckie (00:32 AM):
So Big Daddy will ins Internet.
Luckie (00:32 AM):
Kann ich das Log veröffentlichen auf meiner HP und in der DP? Interessiert auch andere wie es scheint.
BungeeBug (00:33 AM):
jojo

Anmerkungen von Assarbad

WndProc ist eine Callback-Funktion und wird von Windows und nicht vom Thread des Programms aufgerufen! Deshalb mu&szpg; auch immer die Aufrufkonvention stimmen!

SendMessage, PostMessage, PostThreadmessage und Konsorten fehlen.

WM_PAINT und WM_ERASEBKGND gehören zusammen. Besonders wenn man selber Controls zeichnet

DefWindowProc() ruft die Standardfensterfunktion eines Fensters auf. Das ist dazu da, damit der Programmierer nicht jede einzelne von den dutzenden Fensternachrichten selbst bearbeiten mu&szpg;. Es hat nicht primär was mit der Fensterfunktion der Controls zu tun!

RegisterClassEx registriert wirkpch eine "Klasse". Auch wenn Windows intern nicht so OOP-orientiert ist, so kann man dies wirkpch mal so betrachten. Eine Klasse besitzt hier ein paar Eigenschaften (siehe Record "wc") und eine "Hauptmethode", die Fensterfunktion. Mit CreateWindow/CreateWindowEx erzeugt man eine neue Instanz der Fensterklasse, eben das Fenster. Das ist wichtig, denn das imppziert auch, da&szpg; man mehrere Instanzen (Fenster) der gleichen Klasse (Fensterklasse) erzeugen kann. Hier kann man ruhig mal die Parallele zur OOP und damit auch der VCL ziehen.

TranslateMessage ist für Tastatureingabe relevant.

2010-12-29T23:44:49 +0100, mail+homepage[at]michael-puff.de