@TITLE: WIN32-API-TUTORIAL #1 @AUTHOR: Peter Koen entropy@scene.at 4 WIN32-API-TUTORIAL #1 1 ( Entropy/Teklords ) Als erstes mssen wir eine Klasse definieren, die das Fenster beschreibt, das von unserer Anwendung dargestellt werden soll. Unser Fenster wird das einfachstmgliche Fenster sein, das man in Windows berhaupt erzeugen kann. Nichtsdestotrotz mssen wir Windows mit ein paar Basisinformationen ber dieses Fenster versorgen. Die wichtigste Information ist die Adresse der Fensterfunktion. Die Fensterfunktion ist eine sogenannte CALLBACK-Funktion. Das heit, da nicht wir Nachrichten an Windows schicken werden, sondern Windows uns durch Systemnachrichten ber Ereignisse informiert. Diese Systemnachrichten werden immer an die CALLBACK-Funktion geschickt und dort von unserem Programm verarbeitet. Die Fensterfunktion wird mit vier Parametern aufgerufen. Der erste Parameter ist der Handler des Fensters, das die Systemnachricht bekommen soll. Dies wird in den meisten Fllen das im Moment aktive Fenster sein. Der zweite Parameter ist ein unsigned int und enthlt die Systemnachricht, oder besser ausgedrckt: deren ID. So eine Nachricht knnte z.B. sein: WM_SIZE, WM_PAINT etc. Der dritte und der vierte Parameter enthalten zustzliche Informationen zur Systemnachricht, also etwa die Koordinaten eines Mausklicks oder der Tastaturcode bei einem Tastendruck. In der Fensterklasse werden unter anderem auch noch der Mauszeiger, der Handler der Programminstanz, das Hintergrundmuster und der Name der Klasse festgelegt. Da die meisten Eigenschaften der Fensterklasse eher selbsterklrend, sind werde ich hier nicht nher darauf eingehen. Ein schnes Beispiel fr die Fensterklasse ist im beigelegten Quelltext zu finden (siehe 1bonus.zip5). Hier nun ein Ausschnitt aus dem Beispielprogramm: 0 WNDCLASS CWndClass; 0 0 CWndClass.style = 0; 0 CWndClass.lpfnWndProc = WndProc; 0 CWndClass.cbClsExtra = 0; 0 CWndClass.cbWndExtra = 0; 0 CWndClass.hInstance = hInst; 0 CWndClass.hIcon = 0; 0 CWndClass.hCursor = LoadCursor (0, IDC_ARROW); 0 CWndClass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); 0 CWndClass.lpszMenuName = 0; 0 CWndClass.lpszClassName = "ErsteFensterKlasse"; 0 0 RegisterClass (&CWndClass); Sobald die Fensterklasse beim System registriert wurde, kann man das Fenster selbst erstellen. Dies geschieht durch den Aufruf der Funktion CreateWindow. CreateWindow ist eine Funktion aus dem Win32API (Windows 32bit Application Programming Interface). Diese Funktion sieht auf den ersten Blick sehr verwirrend aus, weil sie eine Unmenge an Parametern erwartet. Fr uns sind im Moment nur wenige der Parameter interessant, z.B. der Fenstertitel, der Name der Fensterklasse, das Aussehen des Fensters, die Position und Gre sowie die Instanz des Programms, zu welchem das Fenster gehrt. Alle anderen Parameter werden wir einstweilen mit dem Standardwert aufrufen, welcher 0 ist. Die Funktion CreateWindow erstellt zwar das Fenster im Speicher, damit das Fenster sichtbar wird, d.h. gezeichnet wird, sind aber noch zwei andere Funktionen notwendig. Die erste ist die Funktion ShowWindow. Ihr wird auer dem Fensterhandler auch noch ein Parameter bergeben, der die Art und den Zeitpunkt des Neuzeichnens bestimmt. Die zweite Funktion ist UpdateWindow. UpdateWindow braucht als Parameter nur den Fensterhandler. Auch hier folgt wieder ein kurzer Ausschnitt aus dem Quelltext: 0 HWND hwnd; 0 0 hwnd = CreateWindow("ErsteFensterKlasse", 0 "Dies ist mein erstes Fenster", 0 WS_OVERLAPPEDWINDOW, 0 CW_USEDEFAULT, 0 CW_USEDEFAULT, 0 CW_USEDEFAULT, 0 0, 0 0, 0 hInst, 0 0); 0 0 ShowWindow(hwnd,cmdShow); 0 0 UpdateWindow(hwnd); Ein Windowsprogramm ist nachrichtengesteuert. Oft wird dafr auch der Ausdruck "event-driven" verwendet. Das heit, da wir, also die Programmierer, die passive Rolle bei der Programmflukontrolle bernehmen. Das Betriebssystem bekommt vom Benutzer eine Unmenge an Nachrichten (Events). Jeder Mausklick, jeder Tastendruck, ja sogar jede Mausbewegung produziert eine Systemnachricht, die an das Programm, dessen Fenster gerade im Vodergrund ist bzw. das von der Nachricht betroffen ist, weitergeleitet wird. Alles, was wir nun tun mssen, ist, fr all jene Nachrichten, die wir abfangen/behandeln wollen, eine Funktion zu schreiben und diese beim Auftreten der entsprechenden Nachricht ausfhren. Die Systemnachrichten werden unserem Programm, wie bereits weiter oben erwhnt, als Parameter der Fensterfunktion mitgeteilt. Der vorhergehende Absatz war eher eine schnelle Einfhrung in die Arbeitsweise von Windows als eine Anleitung zum Programmieren. Schauen wir uns einmal etwas nher an, wie denn so eine Nachricht abgehandelt wird. Angenommen, der Benutzer klickt mit der Maus irgendwo in das aktive Fenster hinein. Das Erste, was passiert, ist, da der Maustreiber eine Nachricht an das Betriebssystem schickt mit ungefhr folgendem Inhalt: "Linke Maustaste gedrckt an Position x/y." Als nchstes wird diese Nachricht von Windows umfomatiert, d.h. an das Fenster an der Position x/y wird die ID fr Mausklick als Message geschickt, und die beiden Parameter wparam und lparam enthalten zustzliche Infos, wie etwa die Koordinaten des Pixels, der angeklickt wurde. Da es sehr hufig vorkommt, da sehr viele Nachrichten in sehr kurzer Zeit produziert werden, mu eine "Warteschleife" fr Systemnachrichten vorhanden sein, damit das Programm seine Nachrichten auch in geordneter Reihenfolge erhlt. Jedes Programm, das gestartet wurde, hat seine eigene Warteschleife, genauer gesagt: Jeder gestartete Programmfaden (thread) besitzt eine. Das Herausholen aus der Warteschleife obliegt dem Programmierer: Alle Nachrichten, fr die es einen entsprechenden Haltepunkt im Quelltext gibt, werden aus dieser Schleife herausgenommen und behandelt. Alle nicht implementierten Nachrichten werden vom System selbst abgehandelt. Das heit, da nicht behandelte Nachrichten normalerweise ignoriert werden. Damit wir eine Nachricht aus der Warteschleife an unsere Fensterfunktion weiterreichen knnen, mssen wir die Funktion GetMessage aufrufen. GetMessage macht nichts anderes, als eine Nachricht aus der Schleife zu holen. Eigentlich knnte dieser Programmschritt auch vom Betriebssystem durchgefhrt werden. Allerdings bietet diese Konstruktion wiederum den Vorteil, da wir alle Nachrichten vor ihrer Interpretation "ansehen" knnen. Nach GetMessage wird die Funktion DispatchMessage aufgerufen. Diese Funktion kann man mit einem Postamt vergleichen: Das Betriebssystem schaut nach, zu welchem Fenster die Nachricht gehrt, und leitet diese an die korrespondierende Fensterfunktion weiter. Genau so, wie auf einem Postamt Briefe anhand ihrer Postleitzahl sortiert und weitergeleitet werden. Wenn wir jetzt nur unser Programm betrachten, dann befinden wir uns nun in unserer Fensterfunktion, und das Einzige, was von dem groen Haufen an Nachrichten noch briggeblieben ist, sind die fr unser Fenster relevanten Nachrichten. Und da sind noch immer hunderte davon! Mssen wir nun fr jede einzelne eine Funktion schreiben? Zum Glck, nein! Wir mssen nur diejenigen Nachrichten mit Funktionen versorgen, bei derem Aufreten wir eine bestimmte Aktion durchfhren wollen. Alle anderen Nachrichten werden an die DefWindowProc weitergereicht und vom Betriebsystem abgehandelt. Im Normalfall heit dies, da keine weitere Aktion erfolgt und die Nachricht gelscht wird. Ein Versuch, den gesamten Sachverhalt bildlich dazustellen: 1 A N W E N D U N G 1 1 1 Start des Programms, Windows ruft 1 die Funktion WinMain auf. 1 1 WINMAIN FENSTERFUNKTION 1 ͻ ͻ 1 - 1  1  case-Struktur zwecks Auswertung 1 Warten, bis unser Programm eine der Systemnachrichten 1 Systemnachricht erhlt 1  1 DefWindowProc ( ) 1  1 GetMessage ( )  1 Anwendung kann Nachrichten Ende der Funktion 1 betrachten und verndern. ͼ 1 1 1  1 DispatchMessage ( ) 1 Windows ruft die CALLBACK- 1 Fensterfunktion auf 1 1 1  1 Schleife nochmals durchlaufen, 1 solange, bis die Nachricht fr 1 Programmbeenden aufgerufen wird 1 1 1 ͼ Werfen wir noch einen kurzen Blick auf die WinMain-Funktion, bevor wir zum letzten Beispielquelltext dieser kurzen Einfhrung kommen. So wie "normale" Programme die Funktion main als Hauptfunktion (manchmal auch Hauptprogramm genannt) haben, gibt es unter Windows die WinMain-Funktion. Diese Funktion wird beim Starten des Programmes vom Betriebssytem mit verschiedenen Parametern versorgt, wie etwa die Instanz des Programms, die Kommandozeilenparameter etc. Diese Parameter sind im Moment noch uninteressant fr uns. Wichtig ist derzeit nur, was in welcher Reihenfolge ausgefhrt werden mu. Zuerst erstellen wir unsere Fensterklasse und registrieren diese beim System, so wie wir es weiter oben besprochen haben. Danach wird das Fenster erstellt und gezeichnet. Sobald wir unser Fenster auf den Bildschirm gebracht haben, kmmern wir uns um die Systemnachrichten. Eine typische WinMain-Funktion knnte in etwa so aussehen: 0 int WINAPI WinMain 0 (HINSTANCE hInst, HINSTANCE hPrevInst, char * cmdParam, int cmdShow) 0 { 0 MSG msg; WNDCLASS CWndClass; HWND hwnd; 0 int status = true; 0 0 CWndClass.style = 0; 0 CWndClass.lpfnWndProc = WndProc; 0 CWndClass.cbClsExtra = 0; 0 CWndClass.cbWndExtra = 0; 0 CWndClass.hInstance = hInst; 0 CWndClass.hIcon = 0; 0 CWndClass.hCursor = LoadCursor (0, IDC_ARROW); 0 CWndClass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); 0 CWndClass.lpszMenuName = 0; 0 CWndClass.lpszClassName = "ErsteFensterKlasse"; 0 0 RegisterClass (&CWndClass); 0 0 HWND hwnd; 0 0 hwnd = CreateWindow("ErsteFensterKlasse", 0 "Dies ist mein erstes Fenster", 0 WS_OVERLAPPEDWINDOW, 0 CW_USEDEFAULT, 0 CW_USEDEFAULT, 0 CW_USEDEFAULT, 0 CW_USEDEFAULT, 0 0, 0 0, 0 hInst, 0 0); 0 0 ShowWindow(hwnd,cmdShow); 0 0 UpdateWindow(hwnd); 0 0 0 while (status) 0 { 0 status = GetMessage(&msg, 0, 0, 0); 0 if (status== -1) return -1; 0 DispatchMessage(&msg); } 0 0 return msg.wParam; 0 0 } Besonders beachtenswert ist hierbei der dreifache Boolean. Die Funktion GetMessage ist zwar als BOOL definiert, liefert aber drei verschiedene Werte, nmlich -1, 0, 1. 1 bzw. ein Wert grer 0, wenn eine andere Nachricht als WM_QUIT vorhanden war 0, wenn WM_QUIT (also Beenden) aufgetaucht ist -1, wenn ein Fehler vorgefallen ist Der zweite Teil des Windowsprogramms, der uns noch fehlt, ist die Fensterfunktion. Wir werden hier nur eine sehr einfache erstellen und deshalb auch nur eine Nachricht abfangen: nmlich WM_DESTROY. Diese Nachricht taucht auf, sobald der User das Programm beenden mchte. Da diese Funktion eigentlich selbsterklrend ist, werde ich zu dem nun folgenden Quelltext keine nheren Ausfhrungen schreiben. 0 LRESULT CALLBACK WndProc 0 (HWND hwnd, unsigned int message, WPARAM wParam, LPARAM lParam) 0 { 0 switch(message) 0 { 0 case WM_DESTROY : PostQuitMessage(0); return 0; 0 } 0 return DefWindowProc (hwnd, message, wParam, lParam ); 0 } Der komplette Quelltext zu diesem Tutorium und ein Projekt fr VC++ 6.0 werden in bonus.zip mitgeliefert. Deshalb verzichte ich hier nun auf eine komplette Wiedergabe des Quelltexts und der Compiler- und Linker-Einstellungen. Nur ein Tip fr VC-Benutzer: Das Programm wurde mit einem leeren Win32Application- Projekt erstellt. Dieses Programm kann eigentlich schon recht viel: Fenster verschieben, die Gre ndern, maximieren, minimieren und beenden. Im nchsten Tutorium - falls ich gengend Zeit finde, es zu schreiben - werden wir uns dann ansehen, wie wir so ein leeres Fenster etwas beleben knnen. Ich freue mich ber jedes E-Mail mit Anregungen zu meinen Tutorials. Falls diese Folge halbwegs brauchbar war, schickt mir bitte Feedback an @REF="entropy@scene.at"5. Auch wenn es nicht viel zu sagen gibt, ein kurzes "Ich hab' dein Tutorial gelesen, und es hat mir gefallen" reicht. Hauptsache, ich wei, da die ganze Arbeit nicht umsonst waar. Alle Programme, die in diesen Tutorials geschrieben werden, sind Win32- Programme. Das heit: Es ist mglich, da die vorgestellten Techniken auch mit Win16 funktionieren, aber eher unwahrscheinlich. Sicher ist nur, da dieses Programm speicherplatz verbrauchen wird. Smtliche Informationen, die in diesen Tutorials wiedergegeben werden, basieren auf der Microsoft Developer Network Library, kurz MSDN Lib, und auf zahlreichen Quellen aus dem Internet, die ich hier nicht namentlich anfhren kann. Sei es, da ich den Link schon lngst verloren habe, oder da ich etwas nur im Vorbeisurfen aufgeschnappt habe. Das Internet war mir eine wertvolle Quelle bei meiner Suche nach Informationen, und somit sei allen Autoren und Betreibern von Homepages, FAQs und Tutorials mein Dank ausgesprochen. Alle Beispielprogramme wurden mit Microsoft Visual C++ 6.0 geschrieben und unter Windows NT 4 mit Servicepack 3 und Internet Explorer 4 getestet. Sollte eines dieser Programme auf einem anderen System nicht laufen, dann hattet ihr Pech. Der Befehl _set_new_handler ist Microsoft-spezifisch. Sollte ein anderer Compiler als MSVC++ verwendet werden, ist es ziemlich sicher, da diese Funktion nicht verfgbar ist. Man kann sie in diesem Fall auch weglassen, da nach dem ISO-C++-Standard der new-Operator sowieso eine Exception werfen mu, falls es Probleme gibt. ltere Compiler knnen Probleme mit Templates haben, deshalb wrde es sich empfehlen, Reinterpret-Typecasts zu verwenden. Statt 1CCtrl *pCtrl = WinGetLong 1 (hwnd);5 sollte man 1CCtrl * pCtrl = reinterpret_cast (::GetWindowLong 1 (hwnd, GWL_USERDATA)); 5schreiben. Viel Spa mit Windows, euer @REF="- Peter Koen":"entropy@scene.at" Wien, 24. Mai 1999, 2 Uhr.