API-Windows-Programmierung (ohne MFC)
Inhalt
1. WinMain(...) und windows.h
2.
Eigene Fenster erzeugen
3.
Zwei Fenster auf Basis einer WNDCLASS-Struktur erzeugen
4.
Nachrichtenverarbeitung
5.
Interaktionen mit Kindfenstern
6.
DLL-Programmierung
Einleitung
Selbst Windows-Programme erstellen zu können, beinhaltet eine starke Faszination. Das ideale Werkzeug hierfür gibt es nicht. Jeder hat hier andere Vorstellungen. Der eine will einfache Werkzeuge, die wie von Zauberhand in kurzer Zeit umfangreiche Oberflächen erzeugen. Am besten wäre es, wenn man gar keinen Sourcecode sehen würde. Alles soll grafisch und assistentengestützt erfolgen. Die anderen wollen alles verstehen, damit sie alles beeinflussen können. Der rechte Weg ist sicher wie so oft in der Mitte. Soll man reines C oder C++ verwenden? Der Fortgang der Zeit läßt das Pendel immer mehr in Richtung Objektorientierung ausschlagen, also auch zugunsten von C++. Aber es naht schon C#. Es gibt Java, Visual Basic und Delphi. Die 3D-Programmierung ist bereits ein völlig eigenes Feld. Wie soll man sich hier als Einsteiger verhalten? Die altgedienten Programmierer verweisen vielfach auf C und WinAPI als solide Grundlage. Soll man auf sie hören oder sofort mit C++ und MFC beginnen?
Ich meine, man sollte die Basis auf jeden Fall verstehen. ... und das ist C und WinAPI. Wenn man nur mittels MFC oder anderen Klassenbibliotheken programmiert, erhält man kein Gefühl für die klaren nachvollziehbaren Zusammenhänge. Dies bietet nur die klassische Windows-Programmierung. Der Klassiker unter den Büchern ist "Windows-Programmierung" von Charles Petzold. Dieses Werk leidet in seiner heutigen 5. Auflage nur unter der ungeheuren Last seiner Perfektion, die es dem Anfänger so schwer macht. Aber mit der Zeit werden Sie dieses nützliche Buch immer mehr zu schätzen wissen.
Der Anfänger in der Windows-Programmierung will das Gerüst verstehen. Er will die Wege der Nachrichten und der darauf folgenden Aktionen klar erkennen können. Er muß auf den Punkt deuten können, wo ein Fenster entsteht und wieder vergeht. Er will die Nachrichtenpumpe im Detail verstehen. Perfektion im Detail und Klassenbibliotheken sind für ein grundlegendes Verständnis eher hinderlich. Nur die einfache WinAPI-Programmierung schafft die notwendige Klarheit.
Es muß auch zu Fehlern kommen
dürfen. Nur dann erwacht die Neugier, und nur dann werden die
analytischen Sinne
geschärft.
Daher mein Rat: Experimentieren Sie
möglichst viel mit vorgegebenen Programmen. Fügen Sie Dinge
hinzu, lassen Sie
Dinge weg. Schärfen Sie ihr analytisches Verständnis.
Umgang mit der IDE des MS VC++
Viele Einsteiger haben Verständnisprobleme bezüglich der Entwicklungsumgebung. Daher soll hier zu Beginn am Beispiel von MS VisualC++ der Unterschied zwischen Konsole und WinAPI32 im Umgang mit der IDE dargestellt werden, damit man später sicher zwischen diesen Varianten der Programmierung unterscheiden kann. Sollten Sie einen anderen Compiler / Linker verwenden, so sollten Sie bei Bedarf in den diesbezüglichen Beschreibungen nachlesen.
Die Konsolen-Programmierung ist vielen Anfängern geläufig. Man hat oft nur eine einzige Datei, z.B. main.cpp, und das typische Programm ist vielleicht ein "Hallo ..."-Programm. Die Einstiegs-Funktion ist main(...).
Also beginnen wir dort:
Mit Datei - Neu gelangt man zu
dem obigen Auswahl-Dialog. Hier wählt man Win32-Konsolenanwendung.
Als Projektname können Sie z.B. MyFirstProgram
eingeben:
Im nächsten Fenster wählt man Ein leeres Projekt.
Mittels Projekt - Dem Projekt
hinzufügen - Neu gelangt man zum nächsten Feld.
Hier wählt man C++-Quellcodedatei
und gibt als Dateinamen z.B. main ein:
Nun öffnet sich ein Eingabebereich
zur Aufnahme des Source-Codes.
Hier geben Sie z.B. folgenden Code ein:
#include
<iostream>
int
main() |
Das Ergebnis ist ein einfaches Konsolen-Programm, das "Hallo" ausgibt.
Nehmen wir nun an, Sie bevorzugen ein WinAPI-Programm, das "Hallo" als Message-Box ausgibt. Dann können Sie das oben dargestellte Prozedere erneut von Anfang an durchlaufen. Nur müssen Sie nun Win32-Anwendung wählen und natürlich einen anderen Source-Code eingeben.
Aber es gibt einen alternativen Weg, der nicht allzu bekannt ist:
Ändern Sie den Source-Code wie
folgt auf ein WinAPI32-Programm ab:
#include
<windows.h>
int
WINAPI WinMain(HINSTANCE hI, HINSTANCE, TCHAR*, int) |
Wenn Sie nun kompilieren / linken / ausführen (Strg + F5), dann erhalten Sie folgende Fehlermeldung:
--------------------Konfiguration:
MyFirstProgram - Win32 Debug--------------------
Kompilierung
läuft...
main.cpp
Linker-Vorgang
läuft...
LIBCD.lib(crt0.obj)
: error LNK2001: Nichtaufgeloestes externes Symbol _main
Debug/MyFirstProgram.exe
: fatal error LNK1120: 1 unaufgeloeste externe Verweise
Fehler beim
Ausführen von link.exe.
MyFirstProgram.exe - 2 Fehler, 0 Warnung(en)
Der Linker hat
hier offenbar Probleme. Er sucht die Funktion main(...). Das ist auch
klar, denn Sie haben ein Konsole-Programm ausgewählt.
Wo kann man das
verändern? Hier:
Mittels Projekt - Einstellungen -
Linker gelangt man im Feld Projekt Optionen zu der
Einstellung
/subsystem:console.
Hier müssen Sie einfach mutig
eingreifen und die Eintragung auf
/subsystem:windows
abändern:
Wenn Sie nun kompilieren / linken / ausführen (Strg + F5), dann erhalten Sie fehlerfrei folgende Ausgabe:
Nun sollten Sie den wesentlichen
Unterschied zwischen Konsole und WinAPI32-Programm verstanden haben.
Der entscheidende Punkt ist die
Linker-Einstellung!
Konsolen-Programm | Windows-Programm | |
Auswahl beim VC++-Anwendungs-Assistent | Win32-Konsolenanwendung | Win32-Anwendung |
Einsprung-Funktion im Sourcecode | main(...) | WinMain(...) |
Linker-Einstellung | /subsystem:console | /subsystem:windows |
(Wie man ein MFC-Programm erstellt, ist detailliert im MFC-Tutorial beschrieben.)
Setzen Sie es sich zum Ziel, die
Windows-Programmierung mit WinAPI zumindest in ihren Grundfunktionen
beherrschen und verstehen
zu können. In diesem Sinne wünsche ich Ihnen viel Freude mit
den
nachfolgenden Kapiteln.
Sie haben bisher mit C oder C++
Konsolenanwendungen erstellt und wollen nun in die
Windows-Programmierung mit C / C++ einsteigen.
Nun gut, beginnen wir. Es gibt da eine zentrale
Master-Header-Datei namens windows.h, die uns Zugang zu den
API-Funktionen gewährt.
API steht für application
programming interface.
Wir werden windows.h im ersten Schritt in eine Konsolenanwendung einfügen, um zu zeigen, dass man die damit zur Verfügung gestellten API-Funktionen auch in einer Konsolenanwendung einsetzen kann. Seit Windows 95 / 98 / 2000 / NT verfügt man über sogenannte Win32-API-Funktionen (32-bit-Versionen anstelle 16-bit-Versionen wie bei Windows 3.x).
Als erstes Beispiel für den Einsatz der
Win32-API verwenden wir ausgewählte System-Information-Funktionen,
nämlich GetComputerName(...),
GetUserName(...) und GetWindowsDirectory(...).
Wichtig bei der Verwendung der Win32-API ist die
Beschaffung und Umsetzung der Informationen zu Funktionen und
Strukturen.
Ein guter Berater ist z.B. MSDN (Microsoft Developer
Network) oder andere API-Hilfen (z.B. von Borland). Besonders wichtig
ist
das Verständnis für (notwendige) Parameter und
Rückgabewerte.
Hier werden oft Zeiger auf Datenbereiche, z.B. für Strings,
eingesetzt.
Wenn Ihre Entwicklungsumgebung keine Informationen
zu API bereit stellt, kann MSDN weiterhelfen:
http://msdn.microsoft.com/default.asp
Ohne WinAPI-Hilfe haben Sie keine Chance!
Ihre API-Hilfe sollte zumindest folgende
Informationen über Rückgabewerte und Parameter liefern:
BOOL GetComputerName | ( LPTSTR lpBuffer, LPDWORD nSize ); |
BOOL GetUserName | ( LPTSTR lpBuffer, LPDWORD nSize ); |
UINT GetWindowsDirectory | ( LPTSTR lpBuffer, UINT uSize ); |
Der Zeiger lpBuffer beinhaltet nach Ausführen der obigen Funktionen die Adresse des null-terminierten Strings, der den Computernamen / Usernamen / Namen des Windows-Verzeichnisses aufnehmen wird. Daher muß vor dem Ausführen der Funktion ein solcher String, z.B. als character-array angelegt werden. Die Anfangsadresse von string[x] ist string, identisch mit &string[0].
Bewaffnet mit diesem Wissen können wir ein
kleines C++-Konsolen-Programm basteln:
#include <windows.h> #include <iostream> using namespace std; int main() { DWORD nSize; char computer_name[255]; nSize = sizeof( computer_name ); GetComputerName( computer_name, &nSize ); cout << "Computer name: " << computer_name << endl; char user_name[255]; nSize = sizeof( user_name ); GetUserName( user_name, &nSize ); cout << "User name: " << user_name << endl; char windir_name[MAX_PATH]; GetWindowsDirectory( windir_name, sizeof( windir_name ) ); cout << "Windows-Directory: " << windir_name << endl; cout << endl; cin.get(); return 0; } |
Nun verabschieden wir uns von der Konsole und bauen "echte" Windows-Programme:
Was macht eigentlich ein echtes Windows-Programm aus? Natürlich: Fenster!
Bisher haben Sie Ihr C- oder
C++-Programm mit der Funktion main(...) als Einstiegspunkt
begonnen.
Das ändert sich bei
Windows-Programmen. Hier verwendet man die Einstiegsfunktion WinMain(...).
Zur Verdeutlichung erstellen wir ein
"minimales" Beispiel:
#include <windows.h>
int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int )
|
Die Header-Datei windows.h ist die Basis für die Windows-Programmierung. Öffnet man diese Header-Datei, dann findet man die eigene Bezeichnung "Master include file for Windows applications". Innerhalb dieser Datei findet man eine lange Liste weiterer eingebundener (includierter) Dateien. Schauen Sie doch einmal selbst hinein. Mit windows.h stellt uns MS VC++ die für Windows-Programme benötigten Daten und Funktionen zur Verfügung.
int WINAPI
WinMain
(
HINSTANCE
hInstance,
HINSTANCE
hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
);
hInstance:
Handle to the current instance of the
application.
hPrevInstance:
Handle to the previous instance of the
application.
For a Win32-based application, this
parameter is always NULL.
lpCmdLine:
Pointer to a null-terminated string
specifying the command line for the application,
excluding the program name. To
retrieve
the entire command line, use the GetCommandLine function.
nCmdShow:
Specifies how the window is to be
shown.
Wenn Sie WINAPI weglassen, also folgendes
schreiben:
#include
<windows.h>
int
WinMain( HINSTANCE, HINSTANCE, PSTR, int ) |
erhalten Sie z.B. von VC++ die Warnung:'WinMain' : '__stdcall' muss angegeben werden.
Wenn Sie die Datei windef.h öffnen, finden Sie folgende Anweisung: #define WINAPI __stdcall
WINAPI ist
also nur ein Ersatz für __stdcall (Microsoft-spezifischer
Aufruf an den Compiler für Win32-API-Funktionen).
Es gibt ebenso FAR PASCAL, far pascal oder APIENTRY
für diesen Zweck.
Sie können also auch folgendes schreiben:
#include
<windows.h>
int __stdcall WinMain(
HINSTANCE, HINSTANCE, PSTR, int ) |
oder:
#include
<windows.h>
int APIENTRY WinMain(
HINSTANCE, HINSTANCE, PSTR, int ) |
oder:
#include
<windows.h>
int FAR PASCAL WinMain(
HINSTANCE, HINSTANCE, PSTR, int ) |
oder:
#include
<windows.h>
int far pascal WinMain(
HINSTANCE, HINSTANCE, PSTR, int ) |
Diese Vielfalt macht die
Windows-Programmierung bei Anfängern und Puristen so ungeheuer
beliebt!
Ich möchte Sie hier nicht verwirren,
sondern Ihnen nur typische Varianten aufzeigen,
damit Sie die Scheu vor dieser Vielfalt
abbauen. Härten Sie sich von Anfang an dagegen ab.
Nur zum Verständnis, nicht
als Vorbild gedacht: Sie können eine Menge weglassen. Folgender Zweizeiler läuft z.B. durch den MS-VC++-Compiler: #include <windows.h>
Sie erhalten dann z.B.
entsprechende Warnungen: Allerdings ist nicht jeder Compiler so
großzügig. Der freie Compiler Dev-C++ verweigert
hier den Dienst mit Hinweis auf den ISO C++ Standard.
Er besteht zumindest auf das vorgestellte "int". |
Innerhalb WinMain(...) wird in unserem
kleinen Programm ein Fenster ausgegeben.
Die API-Funktion MessageBox(...)
kümmert
sich hier einfach um alles, vor allem um das Fenster:
int MessageBox
(
HWND hWnd, // handle of owner window
LPCTSTR lpText, //
address of text in message box
LPCTSTR lpCaption, // address of
title of message box
UINT uType // style of message box
);
Manche Programmierer wollen fensterlose
Windows-Programme erstellen.
Das ist leicht möglich, wenn man z.B.
anstelle MessageBox(...) andere "fensterlose" Aktionen einfügt.
Damit Sie verstehen, was damit gemeint
ist, hier ein kleines Beispiel:
#include <windows.h>
int WINAPI WinMain( HINSTANCE, HINSTANCE, PSTR, int )
|
Nachfolgend finden Sie noch ein
weiteres
Beispiel für ein Windows-Programm ohne eigenes Fenster.
Das Programm ruft den PC-Taschen-Rechner
(calc.exe) und den Notizblock (notepad.exe) auf.
#include <windows.h>
int WINAPI WinMain( HINSTANCE, HINSTANCE, PSTR, int )
|
2-Zeiler-Version (nur
zum Spaß!): #include <windows.h> WinMain( HINSTANCE, HINSTANCE, PSTR, int ) { WinExec("calc.exe",0); } |
Nachstehend finden Sie noch ein kleines
Beispiel, mit dem Sie testen können, ob eine Internet-Verbindung
existiert oder nicht.
Hierbei müssen Sie außer der
Datei windows.h auch noch WinInet.h in den Sourcecode einbinden und im
Linker die Datei WinInet.lib angeben:
#include <windows.h> #include <WinInet.h> // einzubindende Library : WinInet.lib (MSVC++) int WINAPI WinMain( HINSTANCE, HINSTANCE, PSTR, int )
|
Das Einbinden der Datei WinInet.lib (Bezeichnung des MSVC++)
ist für Einsteiger nicht unkompliziert.
Beim MSVC++ geht man über das Menü "Projekt -
Einstellungen -
Linker" und fügt dann bei "Objekt-/Bibliothekmodule" die
Bibliothek "wininet.lib"
hinzu.
Verwendet man den kostenlosen Dev-Cpp, so wählt man den Weg
"project options - parameters - linker" und fügt "-lwininet" ein.
Sie haben bisher den Header windows.h und
die Einstiegsfunktion WinMain(...) als wesentliche Elemente der
Windows-Programmierung kennen gelernt. Die
Basis ist also:
#include <windows.h>
int WINAPI WinMain( HINSTANCE, HINSTANCE, PSTR, int )
|
Wie Sie bereits festgestellt
haben,
verwendet man in der WinAPI-Programmierung eigene Bezeichnungen
für
Datentypen. Die nachfolgende Zusammenstellung zeigt wenige
ausgewählte
Beispiele und die Zusammenhänge zwischen WinAPI und C/C++:
Bezeichnung | in C / C++ | Bemerkung |
---|---|---|
TRUE | 1, true | |
FALSE | 1, false | |
NULL | 0 | wird insbesondere für Pointer auf "Nichts" verwendet |
UINT | unsigned int | 16bit / 32bit |
BYTE | unsigned char | 8bit |
WORD | unsigned short | 16bit |
DWORD | unsigned long | 32bit |
LONG | long | 32bit |
VOID | void | z.B. als Rückgabewert von Funktionen |
LPSTR | char* | Pointer auf einen String (char-Array) |
LPCSTR | const char* | Pointer auf einen konstanten String (char-Array) |
HANDLE | void* | Handle für verschiedenste Windows-Elemente |
HWND | - | Handle eines Fensters |
PASCAL | pascal | WINAPI = FAR PASCAL |
WPARAM | unsigned int | 16bit / 32bit |
LPARAM | long | 32bit |
LRESULT | long | 32bit |
HINSTANCE | - | Handle einer Instanz |
Nachfolgend basteln wir unser eigenes Fenster. Jetzt geht's richtig los! Volle Konzentration.
weiter zu Teil 2: Eigene Fenster erzeugen
zurück zur Startseite