WinAPI Basics

Drucke diesen Post This page as PDF Posted by fkrauthan | Posted in Artikel, WinAPI, Windows | Posted on 12-09-2008

Tags: , , , , , , , ,

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

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:

Code: C++ | Plain Text
  1. int WinMain (
  2.     HINSTANCE hInstance,
  3.     HINSTANCE hPrevInstance,
  4.     LPSTR lpszCmdLine,
  5.     int nCmdShow
  6. );

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.

Code: C++ | Plain Text
  1. typedef struct {
  2.     UINT style;
  3.     WNDPROC lpfnWndProc;
  4.     int cbClsExtra;
  5.     int cbWndExtra;
  6.     HINSTANCE hInstance;
  7.     HICON hIcon;
  8.     HCURSOR hCursor;
  9.     HBRUSH hbrBackground;
  10.     LPCTSTR lpszMenuName;
  11.     LPCTSTR lpszClassName;
  12. } 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

Code: C++ | Plain Text
  1. 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:

Code: C++ | Plain Text
  1. HWND CreateWindow(LPCTSTR lpClassName,
  2.     LPCTSTR lpWindowName,
  3.     DWORD dwStyle,
  4.     int x,
  5.     int y,
  6.     int nWidth,
  7.     int nHeight,
  8.     HWND hWndParent,
  9.     HMENU hMenu,
  10.     HINSTANCE hInstance,
  11.     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:

Code: C++ | Plain Text
  1. BOOL ShowWindow(HWND hWnd, int nCmdShow);

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:

Code: C++ | Plain Text
  1. MSG msg;
  2. while (GetMessage(&msg, NULL, 0, 0))
  3. {
  4.     // Übersetzt alle Accelerator Tasten
  5.     TranslateMessage(&msg);
  6.     // Sendet die Nachricht an die WindowProc des Fensters (beim Registrieren angegeben!)
  7.     DispatchMessage(&msg);
  8. }

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:

Code: C++ | Plain Text
  1. LRESULT CALLBACK WindowProc(HWND hwnd,
  2.     UINT msg,
  3.     WPARAM wparam,
  4.     LPARAM lparam)
  5. {
  6.     switch(msg)
  7.     {
  8.         case WM_DESTROY:
  9.             PostQuitMessage(0);
  10.             return 0;
  11.         default: break;
  12.     }
  13.  
  14.     return (DefWindowProc(hwnd, msg, wparam, lparam));
  15. }

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

Code: C++ | Plain Text
  1. #define WIN32_LEAN_AND_MEAN     // Weist den Compiler an unnötige Header auszulassen
  2. #include  
  3.  
  4. LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  5.  
  6. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdline, int nCmdShow)
  7. {
  8.     WNDCLASSEX    winclass;
  9.     HWND        hWnd        = NULL;
  10.  
  11.     ZeroMemory(&winclass, sizeof(WNDCLASSEX));
  12.     winclass.cbSize            = sizeof(WNDCLASSEX);
  13.     winclass.style            = CS_VREDRAW | CS_HREDRAW | CS_OWNDC | CS_DBLCLKS;
  14.     winclass.lpfnWndProc    = WindowProc;
  15.     winclass.hInstance        = hInstance;
  16.     winclass.hIcon            = LoadIcon(NULL, IDI_APPLICATION);
  17.     winclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
  18.     winclass.hbrBackground    = static_cast (GetStockObject(WHITE_BRUSH));
  19.     winclass.lpszMenuName    = NULL; // Name des Menüs das dem Fenster angehängt werden soll
  20.     winclass.lpszClassName    = TEXT("WINAPI_BASIC_SAMPLE");    // Name der Fensterklasse
  21.     winclass.hIconSm        = LoadIcon(NULL, IDI_APPLICATION);
  22.  
  23.     if (RegisterClassEx(&winclass) == 0)
  24.         return -1;
  25.  
  26.        hWnd    = CreateWindowEx(0, TEXT("WINAPI_BASIC_SAMPLE"),  // Name der Klasse von
  27.                                  //der das Fenster sein soll
  28.                                 TEXT("Fenstertitel"),            // Fenstertitel
  29.                                 WS_OVERLAPPEDWINDOW | WS_VISIBLE,// Aussehen des Fensters
  30.                                  // (WS_VISIBLE um das Fenster anzuzeigen)
  31.                                 0,                               // X-Position im Parent-Fenster (Desktop)
  32.                                 0,                               // Y-Position im Parent-Fenster (Desktop)
  33.                                 400,                             // Breite des Fensters
  34.                                 400,                             // Höhe des Fensters
  35.                                 NULL,                            // Handle des Parent-Fensters
  36.                                 NULL,                            // Handle des Menüs
  37.                                 hInstance,                       // Handle der Anwendungsinstanz
  38.                                 NULL);                           // Von Windows reserviert
  39.  
  40.      if (hWnd == NULL)
  41.         return -1;
  42.  
  43.     MSG msg;
  44.     while (GetMessage(&msg, NULL, 0, 0))
  45.     {
  46.         // Übersetzt alle Accelerator Tasten
  47.         TranslateMessage(&msg);
  48.         // Sendet die Nachricht an die WindowProc des Fensters (beim Registrieren angegeben!)
  49.         DispatchMessage(&msg);
  50.     }
  51.  
  52.     return 0;
  53. }
  54.  
  55. LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  56. {
  57.     static HWND hWndButton = NULL;
  58.     switch (message)
  59.     {
  60.     case WM_CREATE:
  61.         {
  62.             // WM_CREATE bekommt als lParam die CREATESTRUCT übergeben. Sie enthält den Handle
  63.         //der Anwendungsinstanz.
  64.             hWndButton = CreateWindow(TEXT("BUTTON"), TEXT("Mein Button"),
  65.                       WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 100, 100,
  66.                       100, 100, hWnd, NULL,((LPCREATESTRUCT)lParam)->hInstance, NULL);
  67.             // Erstellt ein Fenster der Fensterklasse "Button"
  68.  
  69.             CreateWindow(TEXT("STATIC"), TEXT("Mein Label"),
  70.             WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 10, 10,
  71.             100, 100, hWnd, NULL, ((LPCREATESTRUCT)lParam->hInstance, NULL);
  72.             // Erstellt ein Fenster der Fensterklasse "Static"
  73.  
  74.         } break;
  75.     case WM_DESTROY:
  76.         {
  77.             PostQuitMessage(0);
  78.         } break;
  79.     case WM_COMMAND:
  80.         {
  81.             if (HIWORD(wParam) == BN_CLICKED &&   // HIWORD(wParam) == Button Status =>
  82.                          //Wurde der Button geklickt (BN_CLICKED)?
  83.                 HWND(lParam) == hWndButton)      // HWND(lParam) == Handle des Windows das
  84.                          //die Message schickt => Ist es der richtige Button?
  85.                 DestroyWindow(hWnd);
  86.         } break;
  87.     default:
  88.         return DefWindowProc(hWnd, message, wParam, lParam);    // Keine besondere Behandlung der
  89.                                 //Nachricht. Windows soll die Standardbearbeitung einleiten!
  90.     }
  91.     return 0;
  92. }

Write a comment