Wichtige Windows Typen für C/C++-Programmierer
(Erstellungsdatum: 2013 Jun 15 - Links zuletzt geprüft: 2017 Mai 10 – alle okay)

Diese Typen sind vor allem dann wichtig, wenn Sie direkt mit der Windows API programmieren (mit Message Loop und so). Sollten Sie Klassenbibliotheken wie MFC, WPF oder .NET verwenden, benötigen Sie das wahrscheinlich nicht.

NEU Was ist _Null_terminated_ (früher __Nullterminated oder __nullterminated oder ...), sehen Sie hier unter der Überschrift Source Annotation Language (SAL)

CALLBACK, WPARAM, LPARAM, LRESULT, FAR, PASCAL, ...

Bei den ganzen Typen von Windows blickt man irgend wann nicht mehr durch. Insbesondere wenn man auch noch (z. B. wegen Portierung) 16-Bit-Windows Programme berücksichtigen muss. Man findet zwar alles über Google, aber es ist aufwendig. Daher habe ich mal die wichtigsten auf einer Seite zusammengefasst. Eine gute Übersicht über die Windows-Datentypen findet man auf:
msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx

CALLBACK

#define  CALLBACK  __stdcall
// WINAPI und APIENTRY sind Synonyme für CALLBACK:
#define  WINAPI  __stdcall
#define  APIENTRY  WINAPI
msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx


__stdcall

Die  __stdcall-Aufrufkovention wird benutzt um Win32 API Funktionen aufzurufen (vermutlich auch Win64). Die aufgerufene Funktion räumt den Stack auf (ist in C/C++ normalerweise genau anders herum).
msdn.microsoft.com/en-us/library/zxk0tw93(v=vs.71).aspx


WPARAM, LPARAM, LRESULT:
typedef  UINT_PTR  WPARAM;
typedef  LONG_PTR  LPARAM;
typedef  LONG_PTR  LRESULT;


UINT_PTR:

#if  defined(_WIN64)
    typedef  unsigned  __int64  UINT_PTR;
#else
    typedef  unsigned  int  UINT_PTR;
#endif


__int64 (__int8, __int16, __int32):

Unterstützung von Funktionen für C/C++ Microsoft sortierte ganzzahlige Typen. Sie können 8, 16 -, 32 - oder 64-Bit-Ganzzahl-Variablen deklarieren, indem Sie den Typspezifizierer __intn verwenden, wobei n 8, 16, 32 oder 64 ist. Für Portabilität, z. B. wäre ein int 16, 32 oder 64 Bit auf einem 16 Bit, 32 Bit oder 64 Bit-Windows mit __intN kann man sicher stellen, dass sie auf allen Systemen die angegebene Größe haben.
http://msdn.microsoft.com/de-de/library/vstudio/29dh1w7z.aspx dieser Link funktioniert leider nicht mehr. Versuchen Sie diesen:
https://msdn.microsoft.com/de-de/library/29dh1w7z.aspx


LONG_PTR:

typedef  __int3264  LONG_PTR;


__int3264

Spezifiziert einen Typ mit folgenden Eigenschaften (laut Microsoft):
It is 32-bit on 32-bit platforms
It is 64-bit on 64-bit platforms
It is 32-bit on the wire for backward compatibility. It gets truncated on the sending side and extended appropriately (signed or unsigned) on the receiving side.
Beispiel:
[ signed | unsigned ]  __int3264  [ int ]  declarator-list;
http://msdn.microsoft.com/en-us/library/windows/desktop/aa367390(v=vs.85).aspx


HANDLE, HWND, ...

Laut msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx ist (z. B.) HWND folgendermaß definiert:

typedef void *PVOID;
typedef PVOID HANDLE;
typedef HANDLE HWND;

=> HWND entspricht void*

(Leider stimmt das nur, wenn nicht STRICT definiert ist (was anscheinend mittlerweile der Normalfall ist). Bei STRICT werden z. B. Handles (mit Ausnahme des Basistyps für Handles [HANDLE]) anders definiert, so dass der Compiler unterschiedliche Handles erkennen kann, siehe unten die Definition von HWND)

D. h. man kann ein HANDLE an ein HWND zuweisen und umgekehrt und der Compiler hat keine Chance, zu erkennen, dass man hier unterschiedliche Handles aneinander zuweist. Dies kann zu merkwürdigen Fehlern führen (wenn man z. B. ein File-Handle an ein Windows-Handle zuweist - der Compiler gibt keine Fehlermeldung/Warnung aus und übersetzt das.

Daher werden, wenn STRICT definiert ist (was anscheinend mittlerweile der Normalfall ist) Handles mittlerweile in „winnt.h” (bei mir im Verzeichnis „windows_kits\10\Include\10.0.14393.0\um” auf dem selben Laufwerk auf dem Visual Studio installiert ist) mittels Preprozessor-Makro definiert:

typedef void *PVOID;
...
#ifdef STRICT
typedef void *HANDLE; #if 0 && (_MSC_VER > 1000)
#define DECLARE_HANDLE(name) struct name##__; typedef struct name##__ *name
#else
#define DECLARE_HANDLE(name) struct name##__{int unused;}; typedef struct name##__ *name
#endif
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name

HANDLE ist also nach wie vor als Pointer auf void (void*) definiert, andere Handles wie z. B. HWND werden jetzt aber folgendermaßen definiert (siehe in windef.h):

DECLARE_HANDLE(HWND)

D. h. HWND entspricht:

typedef struct HWND__ *HWND

Ein HWND ist jetzt also ein Pointer auf struct HWND__ ein anderes Handle ist z. B. HHOOK. Dies ist ebenfalls mit diesem Makro definiert und folglich ein Pointer auf struct HHOOK__. Dies hat den Vorteil, dass der Compiler jetzt zwischen HWND und HHOOK unterscheiden kann und bei Zuweisungen ohne Cast einen Fehler ausgeben kann.

Source Annotation Language (SAL) NEU

Einführung

Ich wollte wissen, wie ein LPSTR (PSTR, LPWSTR, PWSTR, ...) definiert ist (definiert im Header winnt.h). LPSTR (das selbe wie NPSTR und PSTR[!]) ist folgendermaßen definiert:

typedef _Null_terminated_ CHAR *NPSTR, *LPSTR, *PSTR;

Aber was zum Teufel bedeutet _Null_terminated_? Ich habe sehr lange gegoogelt, bis ich glaube(!) rausgefunden zu haben, was es bedeutet. Dass es sich um (0-terminierte) C-Strings handelt, war mir natürlich klar. Aber wo findet man eine genaue Beschreibung in der offiziellen Microsoft Dokumentation? Ich habe nichts :-( gefunden, konnte mir aber nachdem ich etliches zu SAL (bzw. dem neueren SAL2) gelesen hatte zusammen reimen, was es bedeutet.

Was ist SAL bzw. SAL2

SAL ist eine Erweiterung des Microsoft C/C++ Compilers, der es ermöglicht, dass der Compiler Informationen bekommt, die mit normalen Mitteln von C/C++ nicht möglich sind, und die man sonst nur durch Kommentare (die der Compiler nicht versteht) kennzeichnen konnte. Genau kenne ich SAL2 (die zweite Version, früher war es SAL und da hieß z. B. __Nullterminated_ bzw. __nullterminated bzw. __NullTerminated, bzw. ...) auch nicht, wenn Sie mehr darüber wissen wollen, lesen Sie bei Microsoft nach:

https://msdn.microsoft.com/en-us/library/hh916383.aspx

und

https://msdn.microsoft.com/en-us/library/hh916382.aspx

Leider konnte ich auch dort keine Beschreibung von _Null_terminated_ finden :-( Aber ich konnte es mir nach dem Durchlesen ableiten. Sicher bin ich daher nicht, aber ich denke es sollte stimmen.

_Null_terminated_ ist IMO ein Hint an den Compiler, dass die Variable bzw. der Funktionsparameter (Argument) einer Funktion ein normaler „C-String” ist. C/C++ hat keinen primitiven Datentyp String (in C++ gibt es die Klasse string die sich beinahe genau wie ein primitiver [eingebauter] Datentyp verhält). Stattdessen gilt in C/C++ die Konvention ein Array von Zeichen (char), welches am Ende ein Zeichen mit dem Wert 0 hat (bei 8-Bit-Zeichen also ein Byte mit der Bitfolge 00000000) gilt als String. Beispiel:

char myCString[] = "Hello, world!"; // Alternative const char myCString* = "..."

Dies legt im Hauptspeicher (RAM) folgendes C/C++-Array von Zeichen (char) an:

'H'|'e'|'l'|'l'|'o'|','|' '|'w'|'o'|'r'|'l'|'d'|'!'|'\0'

myCString ist ein C-Array aus Typen vom Typ char (das gab es schon bei Kernighan-Ritchie C) und kann einem const char* zugewiesen werden. Bei Kernighan-Ritchie C und eventuell auch vielen anderen Compilern, funktioniert vermutlich auch eine Zuweisung an: char*, aber ein guter Compiler sollte hier zumindest eine Warnung werfen. BTW: Es ist somit auch klar, dass man C-Strings nicht zur Bearbeitung von binären Daten nutzen kann! Sobald in einem C-String ein Null-Byte auftritt, gilt das als Ende des Strings und alle C-String-Funktionen wie strcpy, strcmp, ... beachten die Zeichen nach dem '\0' nicht mehr(!). Außerdem sieht man, dass diese (besch...) Implementierung extrem langsam ist. Z. B. zur Ermittlung der String-Länge, muss jedes mal der komplette String durchsucht werden(!). Hinweis für C#-/Java-/Python-Benutzer: Im Gegensatz zu den meisten anderen Sprachen sind Strings (egal ob C-Strings oder die C++ Klasse string [ab C++11 offizieller Standard!]) veränderlich(!). Das ist ein großer Vorteil und praktisch so wie früher in (guten) BASIC-Implementationen. Aber(!) bei C-Strings müssen Sie wissen, wie groß der Puffer (Speicherbereich) ist, in dem der String liegt. Sie dürfen beim Ändern eines C-Strings diesen höchstens bis zum Ende des Puffers-1 machen (das letzte Zeichen des Puffers wird für das abschließende Null-Byte benötigt). Falls Sie das, den C-String abschließende NULL-Byte vergessen, dann knallts (meistens), oder Ihr Programm liefert zumindest merkwürdige Ergebnisse.

Übergibt man ein solches Array (oder so einen Pointer) an eine Funktion als Parameter, so wird dieses Array (myCString) automatisch in ein const char* umgewandelt (bei Kernighan Ritchie C in ein char*[!], was zu subtilen Fehlern führen konnte; K&R C kannte noch kein const). Bei normalem C/C++ weiß aber der Compiler nicht, dass dieser Pointer auf char auf eine Speicherstelle zeigen muss, bei der irgendwann ein 0-Byte (Ende des „Strings”) kommen muss. Dank SAL kann man das dem Compiler jetzt mitteilen, indem die Benutzerin z. B. schreibt:

void myStrcpy(char _Nul_terminated_ *out, char _Nul_terminated_ *in) {
    ...

Fazit: Der Compiler weiß jetzt zumindest, dass ein Array von chars übergeben werden muss, welches ein Byte 0x00 enthalten muss und kann daher oftmals bessere Fehlermeldungen ausgeben oder besser optimieren oder...

Tipp In eigenen Programmen sollten Sie _Null_terminated_ niemals hart codieren. Wenn überhaupt, dann so:

#ifdef _MSC_VER
#define _NULL_TERMINATED_ _Null_terminated_
#else
#define _NULL_TERMINATED_
#endif
...
void myStrcpy(char _NULL_TERMINATED_ *out, char _NULL_TERMINATED_ *in) {
    ...

Dann wird _NULL_TERMINATED_ in anderen Compilern, die SAL nicht kennen, einfach durch nichts ersetzt und bei MSC _Null_terminated_ eingefügt (vom Preprozessor). Da dies bereits der Preprozessor erledigt, bevor es der eigentliche C-Compiler überhaupt zu sehen bekommt, entstehen dadurch keinerlei Performance-Nachteile (das ist ein großer Vorteil von C/C++ mit seinem Preprozessor).


16-Bit Windows

FAR PASCAL

#define  FAR  far
#define  PASCAL  pascal
"far" bedeutet dass der Code der aufgerufenen Funktion in einem anderen Code Segment als der des aufrufenden Programms liegt. Wird ab 32-Bit Windows nicht mehr benötigt.
"pascal" ist die Aufrufkovention wie __stdcall (parameter werden von links nach rechts auf den Stack übergeben, die aufgerufene Funktion räumt den Stack auf). Ab 32-Bit Windows verwendet man stattdessen __stdcall.

Hinweis: int war unter 16 Bit Windows 16 Bit, long 32 Bit


WORD

#define  WORD  unsigned  int


DWORD

#define  DWORD  unsigned  long


LONG

#define  LONG  long


LPSTR

#define  LPSTR  far  char*


[Alles für Windows 16 Bit aus "Programming Windows", Charles Petzold, Microsoft Press, ISBN 1-55615-264-7]