WinAPI Basics
Posted by fkrauthan | Posted in Artikel, WinAPI, Windows | Posted on 12-09-2008
Tags: c++, cpp, desktop, einführung, grundlagen, win, win32, WinAPI, windows
0
Damit man sich in der Windowswelt etwas besser zurechtfindet, möchte ich zuerst auf einige Eigenheiten von Windows eingehen.
Handle
Oft begegnet man dem so genannten Handle. Ein Handle ist einfach ein
Integer, der ein Objekt bezeichnet und intern von Windows genutzt wird,
um zwischen verschiedenen Objekten unterscheiden zu können.
| Name | Beschreibung | |
| HANDLE | Ein generisches Objekt | |
| HCURSOR | Ein Cursor | |
| HFILE | Eine Datei | |
| HGDIOBJ | Ein GDI Objekt | |
| HOBJECT | Ein Objekt | |
| HWND | Ein Fenster |
Als nächstes möchte ich einen Überblick über die häufigsten Datentypen von Windows geben:
| Name | C++ Äquivalent | |
| BOOL | int | |
| TRUE | 1 | |
| FALSE | 0 | |
| UINT | unsigned int | |
| LONG | long | |
| LRESULT | long | |
| LPCSTR | const char * | |
| LPVOID | void * | |
| BYTE | unsigned char | |
| WORD | unsigned short int | |
| DWORD | unsigned long |
Die oben vorgestellten Datentypen sind allesamt einfach als Makro (Define) implementiert.
Wer sich wundert, warum Windows versucht einen eigenen booleschen
Datentyp einzuführen, muss wissen, dass es zu der Zeit als Windows
programmiert wurde, noch keinen booleschen Datentyp in C gab.
Ein anderes wichtiges Element von zentraler Bedeutung ist der Device
Context, kurz DC. Ein Device Context repräsentiert eine Sammlung von
grafischen Objekten zusammen mit ihren Attributen und Modis. Einen DC
kann mal als Programmierer nicht direkt manipulieren, sondern es gibt
für diese Zwecke einen Satz von Funktionen.
Normalerweise wird ein Device Context benötigt, wenn man Grafik auf
einem Fenster darstellen möchte. Dazu holt man sich den Device Context
per GetDC() und gibt ihn nach dem Zeichnen mit ReleaseDC wieder frei.
Anscheinend ist das ein Schutzmechanismus von Windows, der verhindern
soll, dass zwei Anwendungen gleichzeitig versuchen in einen bestimmten
Bereich zu zeichnen.
Ein DC kann auch mit CreateCompatibleDC() erzeugt werden. Dieser muss,
wenn er nicht mehr gebraucht wird, wieder mit DeleteDC freigegeben
werden.
Auch auf die Hungarian Notation soll hier hingewissen werden. Hungarian
Notation bedeutet nichts anderes, als vor jedem Variablennamen ein
Zeichen zu setzen (der sogenannte Präfix [prä = vor, fix = fest]), an
dem man erkennt, um welchen Datentyp es sich handelt. Aber nicht nur
Datentypen werden so gekennzeichnet, sondern auch Funktionen, Arrays,
Globals, usw. Dadurch soll der Code übersichtlicher werden und
einfacher zu verstehen sein.
Dieses Prinzip versuchte Microsoft zu standardisieren indem man erstens
festlegte, dass jede Variable, Zeiger, Klasse usw. einen Präfix erhält,
und zweitens, welcher Datentyp welchen Präfix bekommt. Diesen Standard
bezeichnen wir heute als Hungarian Notation.
In folgender Tabelle sind einige der wichtigsten Elemente dieses Standards aufgelistet:
| Prefix | Definiton | |
| b | bool | |
| c | char | |
| dw | DWORD | |
| h | Handle | |
| l | LONG | |
| lp | 32 Bit Zeiger | |
| sz | Null terminierter String | |
| lpsz | Zeiger auf Null terminierten String | |
| rgb | LONG, der einen RGB Wert enthält |
Natürlich gibt es viele, die sich nicht an diesen Standard halten, aber
Microsoft hält sich daran (wie es am Microsoft Platform SDK oder an der
MFC zu sehen ist).
Der Programmstart
Jedes Windowsprogramm startet nicht mit der main Funktion, sondern mit
der WinMain Funktion. Unter MsVC .Net 2003 kann man aber ohne Probleme
die WinMain Funktion durch die main Funktion ersetzen. Durch Umwege
(compilerspezifische Einstellungen oder Start der Anwendung durch die
Konsole) kann man dafür sorgen, dass eine Anwendung mit WinMain auch ein
Konsolenfenster besitzt, das für eventuelle Debug-Ausgaben genutzt
werden kann, was sehr hilfreich sein kann.
Die WinMain Funktion sieht wie folgt aus:
- int WinMain (
- HINSTANCE hInstance,
- HINSTANCE hPrevInstance,
- LPSTR lpszCmdLine,
- int nCmdShow
- );
hInstance ist ein Handle für die Instanz der Anwendung. Man kann die
Anwendung (Maschinencode, EXE), als Schablonen-Klasse betrachten und
eine gerade laufende Anwendung dieser Klasse als Objekt, also als
Instanz betrachten. Genau das bezeichnet hInstance.
hPrevInstance ist nur aus Gründen der Aufwärtskompatibilität erhalten
geblieben und kann ignoriert werden (liefert NULL als Wert).
lpszCmdLine ist schon fast selbsterklärend. Es liefert einfach alle Kommandozeilenangaben als String.
nCmdShow gibt an wie die Anwendung angezeigt werden soll. Zum Beispiel versteckt, minimiert, maximiert, usw.
Die WinMain liefert einen Wert zurück, der beliebig sein darf.
Fenster
Ein Fenster kann mit der Funktion CreateWindow() erzeugt werden. Bevor
diese Funktion aber aufgerufen werden kann, muss das Fenster erst noch
mit dem Aufruf der Funktion RegisterClass() angemeldet werden.
Der Prototyp von RegisterClass() sieht wie folgt aus:
ATOM RegisterClass(CONST WNDCLASS *lpWndClass);
Wie man sieht, erwartet diese Funktion einen Zeiger, oder besser gesagt die Adresse auf eine Fensterklasse (WNDCLASS).
Die Funktion liefert bei fehlgeschlagenem Aufruf den Wert 0 zurück,
ansonsten liefert die Funktion einen Integerzurück, der eine eindeutige
Kennung eines Fensters darstellt.
- typedef struct {
- UINT style;
- WNDPROC lpfnWndProc;
- int cbClsExtra;
- int cbWndExtra;
- HINSTANCE hInstance;
- HICON hIcon;
- HCURSOR hCursor;
- HBRUSH hbrBackground;
- LPCTSTR lpszMenuName;
- LPCTSTR lpszClassName;
- } WNDCLASS, *PWNDCLASS;
Die einzelnen Parameter der Struktur werde ich jetzt nicht mehr so
ausführlich beschreiben. Einfach das Plattform SDK zu Rate ziehen und
dort nachlesen, welches Strukturelement welche Bedeutung hat und mit
welchen sinnvollen Werten es belegt werden kann. Es ist an dieser
Stelle einfach sinnlos, das Plattform SDK einfach zu übersetzen (was ich
hier schon oft gemachte habe) und dadurch das Ganze noch weiter
aufzublähen. Der einzige Paramter über den es Sinn macht etwas zu
schreiben, ist lpfnWndProc.
lpfnWndProc ist einfach ein Funktionszeiger auf eine Meldeschleife.
Windows arbeitet ereignisgesteuert (event-driven). Das heißt, es gibt
Ereignisse wie z. B. Mausklicks, Tastatureingaben oder zeitgesteuerte
Ereignisse auf die eine Anwendung reagieren kann.
Die Meldeschleife (Event Handler) erhält vom Betriebssystem Nachrichten
(Messages), auf die die Anwendung reagieren kann, aber nicht muss.
Beispielsweise wird der Anwendung ein Mausklick gemeldet, wenn auf
eines ihrer Fenster geklickt wurde.
Jedes Fenster kann seinen eigenen Event Handler besitzen oder mehere
Fenster können gemeinsam einen Event Handler nutzen, je nachdem wie
man es wünscht.
Der Event Handler muss die Form
- LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
besitzen.
Der Parameter hwnd enthält den Handle des Fenster von dem die Nachricht
erzeugt wurde. uMsg identifiziet die Nachricht. wParam und lParam
werden für eventuelle Parmater der Nachricht genutzt.
Sobald eine Fensterklasse angemeldet ist, kann sie mit CreateWindow erzeugt werden:
- HWND CreateWindow(LPCTSTR lpClassName,
- LPCTSTR lpWindowName,
- DWORD dwStyle,
- int x,
- int y,
- int nWidth,
- int nHeight,
- HWND hWndParent,
- HMENU hMenu,
- HINSTANCE hInstance,
- LPVOID lpParam);
Um ein Fenster der vorher erzeugten Fensterklasse zu erzeugen, wird
einfach als lpWindowName der vorher gewählte Name der Fensterklasse
übergeben.
Windows würde nicht Windows heißen, wenn nicht alles aus Fenstern
bestehen würde. Und es ist tatsächlich so – alles ist ein Fenster.
Windows besitzt schon vordefinierte Fenster. So kann man beispielsweise
als lpWindowName COMBOBOX, BUTTON, EDIT oder LISTBOX angeben und so
einen Button auf ein Fenster platzieren.
Vorhin wurde erklärt, dass nCmdShow Parameter der WinMain Funktion
angibt wie die Anwendung angezeigt werden soll. nCmdShow ist eher als
Empfehlung zu betrachten – ignoriert man den Parameter einfach passiert
nämlich gar nichts. Mithilfe der Funktion ShowWindow kann man
beeinflussen, wie ein Fenster angezeigt werden soll:
hWnd bezeichnet das Fensterobjekt, und nCmdShow wie das Fenster
angezeigt werden soll. Hier übergibt man einfach den nCmdShow Parameter
der WinMain Funktion und kann so auf die „Empfehlung“ von Windows
eingehen, sofern man das will.
Nachrichten und Ereignisse
In Windows gibt es Nachrichten und Ereignisse. Damit das Programm in
der Lage ist, diese Nachrichten zu empfangen – und an die entsprechenden
Event Handler – weiterzugeben, ist folgender Code notwendig:
- MSG msg;
- while (GetMessage(&msg, NULL, 0, 0))
- {
- // Übersetzt alle Accelerator Tasten
- TranslateMessage(&msg);
- // Sendet die Nachricht an die WindowProc des Fensters (beim Registrieren angegeben!)
- DispatchMessage(&msg);
- }
Wer wissen will was dahinter steckt, wirft am besten wieder einen Blick in das Platform SDK.
Ein Event Handler empfängt eingehende Nachrichten und verarbeitet sie – ein minimaler Event Handler könnte wie folgt aussehen:
- LRESULT CALLBACK WindowProc(HWND hwnd,
- UINT msg,
- WPARAM wparam,
- LPARAM lparam)
- {
- switch(msg)
- {
- case WM_DESTROY:
- PostQuitMessage(0);
- return 0;
- default: break;
- }
- return (DefWindowProc(hwnd, msg, wparam, lparam));
- }
DefWindowProc ist ein Standard Windows Event Handler und verarbeitet alle Nachrichten, die man nicht selbst abfängt.
Es gibt einen Haufen von Nachrichten. Ich möchte hier nur mal die
Nachricht WM_DEVMODECHANGE erwähnen. Diese wird immer gesendet, wenn
der Benutzer Geräteeinstellungen wie Auflösung des Monitors verändert.
Beispielprogramm
- #define WIN32_LEAN_AND_MEAN // Weist den Compiler an unnötige Header auszulassen
- #include
- LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdline, int nCmdShow)
- {
- WNDCLASSEX winclass;
- HWND hWnd = NULL;
- ZeroMemory(&winclass, sizeof(WNDCLASSEX));
- winclass.cbSize = sizeof(WNDCLASSEX);
- winclass.style = CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_DBLCLKS;
- winclass.lpfnWndProc = WindowProc;
- winclass.hInstance = hInstance;
- winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
- winclass.hCursor = LoadCursor(NULL, IDC_ARROW);
- winclass.hbrBackground = static_cast (GetStockObject(WHITE_BRUSH));
- winclass.lpszMenuName = NULL; // Name des Menüs das dem Fenster angehängt werden soll
- winclass.lpszClassName = TEXT("WINAPI_BASIC_SAMPLE"); // Name der Fensterklasse
- winclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
- if (RegisterClassEx(&winclass) == 0)
- return -1;
- hWnd = CreateWindowEx(0, TEXT("WINAPI_BASIC_SAMPLE"), // Name der Klasse von
- //der das Fenster sein soll
- TEXT("Fenstertitel"), // Fenstertitel
- WS_OVERLAPPEDWINDOW | WS_VISIBLE,// Aussehen des Fensters
- // (WS_VISIBLE um das Fenster anzuzeigen)
- 0, // X-Position im Parent-Fenster (Desktop)
- 0, // Y-Position im Parent-Fenster (Desktop)
- 400, // Breite des Fensters
- 400, // Höhe des Fensters
- NULL, // Handle des Parent-Fensters
- NULL, // Handle des Menüs
- hInstance, // Handle der Anwendungsinstanz
- NULL); // Von Windows reserviert
- if (hWnd == NULL)
- return -1;
- MSG msg;
- while (GetMessage(&msg, NULL, 0, 0))
- {
- // Übersetzt alle Accelerator Tasten
- TranslateMessage(&msg);
- // Sendet die Nachricht an die WindowProc des Fensters (beim Registrieren angegeben!)
- DispatchMessage(&msg);
- }
- return 0;
- }
- LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- static HWND hWndButton = NULL;
- switch (message)
- {
- case WM_CREATE:
- {
- // WM_CREATE bekommt als lParam die CREATESTRUCT übergeben. Sie enthält den Handle
- //der Anwendungsinstanz.
- hWndButton = CreateWindow(TEXT("BUTTON"), TEXT("Mein Button"),
- WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 100, 100,
- 100, 100, hWnd, NULL,((LPCREATESTRUCT)lParam)->hInstance, NULL);
- // Erstellt ein Fenster der Fensterklasse "Button"
- CreateWindow(TEXT("STATIC"), TEXT("Mein Label"),
- WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 10, 10,
- 100, 100, hWnd, NULL, ((LPCREATESTRUCT)lParam->hInstance, NULL);
- // Erstellt ein Fenster der Fensterklasse "Static"
- } break;
- case WM_DESTROY:
- {
- PostQuitMessage(0);
- } break;
- case WM_COMMAND:
- {
- if (HIWORD(wParam) == BN_CLICKED && // HIWORD(wParam) == Button Status =>
- //Wurde der Button geklickt (BN_CLICKED)?
- HWND(lParam) == hWndButton) // HWND(lParam) == Handle des Windows das
- //die Message schickt => Ist es der richtige Button?
- DestroyWindow(hWnd);
- } break;
- default:
- return DefWindowProc(hWnd, message, wParam, lParam); // Keine besondere Behandlung der
- //Nachricht. Windows soll die Standardbearbeitung einleiten!
- }
- return 0;
- }


