erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net) - C++ und MFC  (Stand: 19.07.2003)

zurück zur Startseite

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()
{
 std::cout << "Hallo\" << std::endl;
 std::cin.get();
 return 0;

}

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)
{
 MessageBox(NULL, "Hallo", "", MB_OK);
 return 0;
}

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.
 
 

1. WinMain(...) und windows.h

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;
}

Siehe auch:
http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/Q104/0/94.asp&NoWebContent=1


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 )
{
  MessageBox( NULL, "Inhalt", "Überschrift", MB_OK );
  return 0;
}

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.



Infos aus MSDN zu WinMain(...):

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 )
{
  MessageBox( NULL, "Inhalt", "Überschrift", MB_OK );
  return 0;
}

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 )
{
  MessageBox( NULL, "Inhalt", "Überschrift", MB_OK );
  return 0;
}

oder:
 
#include <windows.h>

int APIENTRY WinMain( HINSTANCE, HINSTANCE, PSTR, int )
{
  MessageBox( NULL, "Inhalt", "Überschrift", MB_OK );
  return 0;
}

oder:
 
#include <windows.h>

int FAR PASCAL WinMain( HINSTANCE, HINSTANCE, PSTR, int )
{
  MessageBox( NULL, "Inhalt", "Überschrift", MB_OK );
  return 0;
}

oder:
 
#include <windows.h>

int far pascal WinMain( HINSTANCE, HINSTANCE, PSTR, int )
{
  MessageBox( NULL, "Inhalt", "Überschrift", MB_OK );
  return 0;
}

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>
 WinMain( HINSTANCE, HINSTANCE, PSTR, int ) { MessageBox( NULL, "", "", 0 ); }

 Sie erhalten dann z.B. entsprechende Warnungen:
 'WinMain' : '__stdcall' muss angegeben werden
 'WinMain' : Funktion sollte einen Wert zurueckgeben; Ergebnistyp 'void' angenommen

 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 ) 

  for( int x=0; x<5; x++ )
  { 
    MessageBeep(0); 
    for( int y=0; y<100000000; y++ ); 
  } 
  return 0; 
}

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 ) 

  WinExec("calc.exe",    SW_NORMAL);
  WinExec("notepad.exe", SW_NORMAL);
  return 0; 
}


 
  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 )
{
 DWORD dwFlags; 
 BOOL RetVal = InternetGetConnectedState(&dwFlags,0); 
 if(RetVal == TRUE) MessageBox(NULL, "Verbunden", "Internet", 0); 
 else               MessageBox(NULL, "Getrennt",  "Internet", 0);
 return 0;
}

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 ) 

  //Source-Code
  return 0; 
}


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