Einführung

Inhaltsverzeichnis

Einführung
Ein Basisprogramm
Namenskonventionen und Ungarische Notation

Der Windows-Kern kennt drei Formen: Win16, Win32 und Win32s.

In diesem Tutorial wird ausschließlich die Win32-Programmierung behandelt.

Alle Windows-Versionen, die auf dem 32-Bit-Kern basieren, laufen im Protected Mode.
Win32 kennt nur noch ein einziges Speichermodell: FLAT. Das bedeutet, das jedem Programm die virtuelle Speichermenge von 4 GB als ein Segment zur Verfügung gestellt wird. In diesem einen Segment befinden sich sowohl Code als auch Daten des Programms.

Die Adressierung erfolgt über die 32-Bit-Register des Prozessors. Adressangaben sind dann in aller Regel Offsets zum Beginn des 4-GB-Segments. Mit einem 32-Bit-Zeiger/Register kann jedes Byte innerhalb des Segments adressiert werden. Dadurch entfällt das Jonglieren mit Segmenten. Die Segmentregister sind jedoch noch nicht sinnlos. In ihnen befinden sich sogenannte Selektoren, die auf Deskriptoren verweisen. In einem Deskriptor wird, wie der Name schon andeutet, ein Segment beschrieben. Dabei geht es um die Anfangsadresse im physikalischen Speicher, Größe des Segments und gewisse Zugriffsrechte, die als Speicherschutzmechanismen dienen sowie einige andere Daten.

Die Kommunikation mit dem Betriebssystem erfolgt nicht wie unter DOS über Interrupts, sondern über Funktionen, die vom Windows-API (Application Programming Interface) zur Verfügung gestellt werden.

Für die Funktionen gibt es abgesehen von einer Ausnahme nur noch eine verbindliche Aufrufkonvention: STDCALL.
Dies ist eine Mischform der Aufrufkonventionen C und Pascal. Bei der C- Aufrufkonvention werden die Parameter von rechts nach links auf den Stack gelegt und der Aufrufer muss den Stack bereinigen. Bei Pascal ist es genau umgekehrt: die Parameter von links nach rechts auf den Stack und die Prozedur bereinigt den Stack. Bei STDCALL werden die Parameter von rechts nach links auf den Stack gelegt, aber die aufgerufene Funktion ist für die Stackbereinigung verantwortlich. Die einzige Funktion, bei der die C-Aufrufkonvention verwendet werden muss, heißt wsprintf (wsprintf() kann eine variable Parameteranzahl verarbeiten, weiß aber nicht im Voraus wieviele es sein werden und kann daher auch nicht die Stackbereinigung vornehmen).

Bei der Programmierung in Windows ist zu beachten, dass Windows intern die Register EBX, EDI, ESI und EBP verwendet. Sie können diese Register in Ihren Prozeduren verwenden, sollten aber darauf achten, dass die Inhalte dieser Register nach dem Funktionsaufruf die gleichen wie vor dem Funktionsaufruf sind.

Ein mögliches Rahmenprogramm könnte so aussehen:
.386
.MODEL FLAT, STDCALL
.DATA
.DATA?
.CONST
.CODE
  label
END label

Auch wenn es keine verschiedenen Segmente mehr gibt, so kann das Programm doch in logische Abschnitte unterteilt werden.
Im DATA-Abschnitt werden die initialisierten Daten abgelegt, im DATA? die uninitialisierten Daten. Der Unterschied bei diesen beiden Abschnitten besteht darin, dass durch Datendefinitionen im DATA?-Abschnitt die EXE-Datei nicht vergrößert.
Die Anweisung msg db 10000 dup (?) sorgt dann dafür, dass zur Laufzeit diese Speichermenge zur Verfügung gestellt wird, macht die EXE-Datei aber nicht um 10000 Byte größer.

Bei Windows handelt es sich um ein objektorientiertes Betriebssystem. Es wurde allerdings nur begrenzt mit objektorientierten Programmiersprachen entwickelt. Der Großteil von Windows wurde in C geschrieben, in den systemnahen und zeitkritischen Bereichen (Interruptbehandlung, Taskmanagement) wurde auch ein wenig mit Assembler gearbeitet. Die COM-Objekte (OCX), die teilweise durch Windows, teilweise durch Anwendungsprogramme bereitgestellt werden, sind mit hoher Wahrscheinlichkeit durch OOP erzeugt worden.

Jedes Fenster, jeder Button und jedes andere optisch greifbare Element ist ein Objekt. Dabei sind im Objekt Fenster eine Reihe weiterer Objekte enthalten. Jedes Objekt das andere Objekte beinhaltet ist für diese Objekte das Elternobjekt (Parent). Die enthaltenen Objekte werden als Kindobjekte (Child-Objekt) bezeichnet.

Der Objektbegriff zieht sich auch durch die nicht sichtbaren Bereiche des Systems. So gibt es Dateiobjekte, Prozessobjekte, Speicherobjekte usw.

Bei der Windows-Programmierung muss zwischen Anwendungen im Nutzermodus und Anwendungen im Kernelmodus unterschieden werden. Programme, die im Kernelmodus laufen haben vollen Zugriff sowohl auf die Hardware als auch auf die Kerneldatenstrukturen und Kernelfunktionen. Beispiele für Programme im Kernelmodus sind Gerätetreiber. Durch die Systemnähe fallen hier Programmierfehler besonders schwer ins Gewicht, weil sie das gesamte System zum Absturz bringen können.

In diesem Tutorial wird es nur um die Entwicklung von Nutzermodusanwendungen gehen. Programmfehler werden hier insofern weniger übel genommen als sie i. d. R. nur die jeweilige Anwendung abstürzen lassen, aber nicht das gesamte System mit in den Abgrund reißen. Das soll natürlich nicht andeuten, dass man bei Anwendungsprogrammen weniger Sorgfalt walten lassen kann.

Für Kernelmodusanwendungen gibt es von Microsoft das DDK (Driver Development Kit) kostenfrei zum Download. Dieses enthält Dokumentationen, Header-Dateien (für die Programmiersprache C) und Beispiele zur Treiberentwicklung.

Wie schon weiter oben gesagt kommuniziert man unter Win32 über Funktionsaufrufe. Die wichtigsten dieser Funktionen für Programme im Nutzermodus befinden sich in den Dateien user32.dll (Objektbereitstellung zur Interaktion mit dem Nutzer), kernel32.dll (Prozess- und Speichermanagement) und gdi32.dll (Graphic Device Interface, Interaktion mit Ein/Ausgabegeräten [Tastatur, Bildschirm, Drucker]), die von Windows bereitgestellt werden. Diese Dateien stellen die grundsätzliche Schnittstelle zu Windows zur Verfügung. Multimedia, Netzwerkzugriffe, spezielle grafische Objekte und Funktionen für Gerätetreiber werden durch andere DLLs oder OCXe bereitgestellt.

Durch das GDI wird für jedes grafische Objekt ein Device Context bereitgestellt. Über diesen Device Context kann auf die grafischen Eigenschaften (Farbe, Font etc.) des Objekts zugegriffen werden. Jedes Objekt, grafisch oder nicht-grafisch, wird über ein Handle eindeutig identifiziert.

Bei der Verwendung der API-Funktionen kommen jede Menge Strukturen und Konstanten zum Einsatz. Bei MASM32 werden diese Angaben hauptsächlich in der Datei windows.inc hinterlegt. Diese Datei unterliegt einer ständigen Pflege und Erweiterung durch die beiden Hauptautoren von MASM32.

Desweiteren sollte eingestellt werden, dass der Quelltext case-sensitiv kompiliert wird (Beachtung der Groß/Kleinschreibung). In der Standardeinstellung sind die beiden Bezeichner hwnd und HWND identisch. Unter Windows ist HWND ein Datentyp (Handle eines Fensters). Oft wird die Variable dieses Typs hwnd genannt, was bei nicht case-sensitiver Interpretation zwangsweise zu Problemen führt. Dieses Vorgehen ist an vielen anderen Stellen identisch.

Im Gegensatz zu DOS kommuniziert auch das Betriebssystem mit dem Programm. Diese Kommunikation läuft über sogenannte Callback-Funktionen. Diese Funktionen werden durch Sie geschrieben. Sie unterscheiden sich dabei grundsätzlich nicht von anderen selbstdefinierten Funktionen. Oft wird aber ein spezieller Returncode erwartet, der von Windows ausgewertet wird. Da Windows die Funktion u. U. mit Parametern aufruft, muss natürlich auch in der Funktionsdefinition die Korrektheit der Parameter sichergestellt sein.

Ein Beispiel für eine Callback-Funktion ist die in praktisch jedem Windows- Programm zu findende sogenannte Window- oder Fensterprozedur. Dabei sendet Windows an das Programm zu jedem auftauchenden Ereignis (Mausbewegung, Tastendruck, Menüpunktauswahl etc) in einer Endlosschleife eine Nachricht. Es gibt eine ganze Reihe von Nachrichten, die in windows.inc als Konstanten verfügbar sind. Im Abschnitt Ein Basisprogramm finden sie eine Anwendung mit Fensterprozedur.

In MASM32 wird sehr viel mit Funktionsprototypen gearbeitet. Gleichzeitig bietet MASM32 mit der Anweisung invoke das notwendige Mittel, um einen Funktionsaufruf gegen seinen Prototypen abzugleichen. Die Prototypendefinitionen für die einzelnen DLL-Funktionen liegen in Include-Dateien, die vom Dateinamen die Beziehung zur DLL erkennen lassen (kernel32.inc, gdi32.inc etc). Diese Dateien müssen selbstverständlich vor Verwendung der entsprechenden Funktion ins Programm eingebunden sein.

Auch wenn es in diesem Tutorial um MASM gehen soll, wird das erste Programm, eine einfache Message-Box auch in einer TASM-Variante vorgestellt.

TITLE Win32 MessageBox erzeugen
IDEAL
P386
MODEL FLAT,STDCALL
EXTRN MessageBoxA:PROC
EXTRN ExitProcess:PROC
DATASEG
cTitle db "Meine Messagebox",0
cMessage db "Klick mich bis ich verschwinde",0

CODESEG
  start:
        call MessageBoxA,0,offset cMessage,offset cTitle,67
        call ExitProcess,0
END start
Kompiliert wird dieses Programm durch die beiden folgenden Aufrufe:
tasm32 /ml msgbox
tlink32 /Tpe /aa /c msgbox.obj,msgbox,,import32

Der Schalter /ml teilt TASM32 mit, das alle Symbole case-sensitiv behandelt werden sollen. Der Aufruf von TLINK32 ist deutlich umfangreicher als unter DOS: mit /Tpe wird angegeben, dass eine .exe-Datei erzeugt werden soll, keine DLL. /aa bedeutet, dass eine echte Windows-Anwendung erzeugt werden soll. /c ist wieder für das case-sensitive Linken verantwortlich. Im Anschluss folgen noch die Angaben Objektdatei,EXE-Datei,MAP-Datei(hier leer) und die Import-Bibliothek.

Die Version für MASM sieht so aus:
TITLE Win32 MessageBox erzeugen
.386
.MODEL FLAT,STDCALL
option casemap:none
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib

MessageBoxA PROTO :Dword, :Dword, :Dword, :Dword
ExitProcess PROTO :Dword

.DATA
cTitle db "Meine Messagebox",0
cMessage db "Klick mich bis ich verschwinde",0

.CODE
  start:
        invoke MessageBoxA,0,ADDR cMessage,ADDR cTitle,67
        invoke ExitProcess,0
END start
Dieses Programm wird folgendermaßen kompiliert:
ml /c /coff msgbox.asm
link /Subsystem:windows msgbox

ML erzeugt normalerweise direkt aus der Quelldatei eine EXE-Datei. Bei Windows- Programmen sind allerdings noch andere Angaben notwendig. Mit dem Parameter /c wird ml.exe angewiesen, nur eine .obj-Datei zu erzeugen. Link.exe bekommt dann die Information, dass es sich um ein Programm für die Windows-Umgebung handelt.

Abgesehen von der MASM-eigenen Form der Direktiven fallen fünf Dinge ins Auge.
Anders als bei TASM wird die Beachtung der Groß/Kleinschreibung nicht durch Kommandozeilenparameter angegeben sondern mit der Direktive option casemap:none (obwohl auch ein Kommandozeilenschalter bei ml.exe möglich ist: /Cp).
Mit includelib werden die Importbibliotheken angegeben, die die beiden Funktionen MessageBoxA (user32.lib) und ExitProcess (kernel32.lib) enthalten.
Kurz danach werden die beiden Prototypen für die Funktionen erzeugt. Wie Sie sehen, werden nur die Datentypen angegeben, keine konkreten Variablennamen.
Der Aufruf der Funktionen erfolgt nicht mit CALL sondern über INVOKE. Dabei hängen die Prototypendefinition und INVOKE unmittelbar zusammen. INVOKE funktioniert nur, wenn die Prototypen vorhanden sind. Der Aufruf würde auch mit CALL funktionieren, dann müssten aber die Parameter einzeln auf den Stack gepusht werden und zweitens wäre die mit INVOKE mögliche Parameterprüfung außer Kraft gesetzt.
Der fünfte Unterschied besteht in der Verwendung von ADDR statt OFFSET. ADDR funktioniert nur im Zusammenhang mit INVOKE. Wenn Sie einer Variablen oder einem Register die Adresse eines Labels zuweisen wollen dann müssen Sie weiterhin OFFSET verwenden. ADDR kann ebenso nicht mit Vorwärtsreferenzen umgehen, man kann also nicht die Adresse eines erst später definierten Labels ermitteln. Dafür kann man ADDR mit lokalen Variablen z. B. in Prozeduren verwenden. OFFSET würde hier den Offset zum Segmentanfang ermitteln, ADDR den Offset innerhalb des Stacks.

INVOKE und ADDR sind MASM-spezifisch und ebenso wie PROTO nicht mit Prozessorbefehlen zu verwechseln. Der TASM kann auch im MASM-Modus nichts mit diesen Direktiven anfangen. Bei TASM können Sie die erweiterte Form des Befehls CALL verwenden, statt ADDR bietet sich OFFSET bzw. der Assemblerbefehl lea an. Zur Prototypendefinition können Sie auf PROCDESC ausweichen.

Vergessen Sie nicht, dass es sich bei Windows um ein System mit dem ANSI- Zeichensatz handelt. Sollten Sie ihre Programme weiterhin mit einem DOS-Editor bzw. im ASCII-Zeichensatz schreiben, so werden Sie bei Sonderzeichen wie bspw. den Umlauten zu einer fehlerhaften Zeichenanzeige kommen.

Gemessen am ersten Programm im Grundlagentutorial (Hello World) war dieses Programm nicht nur kürzer sondern erlaubte sogar noch einen gewissen Teil an Nutzerinteraktion, auf die freilich nur mit Beenden des Programms reagiert wurde. Trotzdem konnte die Messagebox sowohl mit der Maus als auch der Tastatur bedient werden, ohne auch nur eine Zeile zur Maus- und Tastatursteuerung zu schreiben.

Das übliche Windows-Programm verfügt über ein Hauptfenster mit einem Menü, einer Titelleiste mit einigen Schaltflächen sowie, ganz wichtig, einem Zweck.

Ein Basisprogramm

Im Folgenden stelle ich Ihnen ein Programm vor, das durchaus als Basis für Ihre eigenen Entwicklungen dienen kann. Es wird ein einfaches Fenster mit der üblichen Systemschaltfläche links oben und den drei bekannten Standardschaltflächen zur Fenstergrößenänderung rechts oben erzeugt.

;fenster.asm
.386
.MODEL flat,stdcall
option casemap:none

INCLUDE c:\masm32\include\windows.inc
INCLUDE c:\masm32\include\user32.inc
INCLUDE c:\masm32\include\kernel32.inc
INCLUDELIB c:\masm32\lib\user32.lib
INCLUDELIB c:\masm32\lib\kernel32.lib

WndProc PROTO :dword, :dword, :dword, :dword

.DATA
szCaption db "Das erste Fenster",0
szClassName db "My_Class",0

.DATA?			;uninitialisierte Daten
hInstance HINSTANCE ?
hIcon HANDLE ?
hCursor HANDLE ?
hWnd HWND ?
cCmdline LPSTR ?

.CODE
start:
  call mymain
  invoke ExitProcess,0


mymain PROC
  LOCAL wwd:dword,wht:dword,wtx:dword,wty:dword,wc:WNDCLASSEX
  
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke GetCommandLine
  mov cCmdline,eax

  mov wtx,CW_USEDEFAULT ;oder konkrete Angaben: 0
  mov wty,CW_USEDEFAULT ;0
  mov wwd,CW_USEDEFAULT ;400
  mov wht,CW_USEDEFAULT ;400
  
  mov wc.cbSize,SIZEOF WNDCLASSEX
  mov wc.style,CS_BYTEALIGNCLIENT or CS_BYTEALIGNWINDOW
  mov wc.lpfnWndProc,OFFSET WndProc
  mov wc.cbClsExtra,NULL
  mov wc.cbWndExtra,NULL
  push hInstance
  pop wc.hInstance
  mov wc.hbrBackground,COLOR_BTNFACE+1
  mov wc.lpszMenuName,NULL
  mov wc.lpszClassName,OFFSET szClassName
  invoke LoadIcon,NULL,IDI_APPLICATION
  mov hIcon,eax
  mov wc.hIcon,eax
  mov wc.hIconSm,eax
  invoke LoadCursor,NULL,IDC_ARROW
  mov hCursor,eax
  mov wc.hCursor,eax
  
  invoke RegisterClassEx,ADDR wc
  
  invoke CreateWindowEx,WS_EX_LEFT,ADDR szClassName,ADDR szCaption,
                        WS_OVERLAPPEDWINDOW,wtx,wty,wwd,wht,NULL,NULL,hInstance,NULL
  mov hWnd,eax
  
  invoke ShowWindow,hWnd,SW_SHOWNORMAL
  invoke UpdateWindow,hWnd
  
  call MsgLoop
  ret
mymain ENDP
  

MsgLoop PROC
  LOCAL msg:MSG
  
  Startloop:
    invoke GetMessage,ADDR msg,NULL,0,0
    or eax,eax
    je Exitloop
    invoke DispatchMessage,ADDR msg
    invoke TranslateMessage,ADDR msg
    jmp Startloop
  Exitloop:
  mov eax,msg.wParam
  ret
MsgLoop ENDP

WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
  .IF uMsg == WM_DESTROY
    invoke PostQuitMessage,NULL
  .ELSE                     
    invoke DefWindowProc,hWin,uMsg,wParam,lParam
    ret
  .ENDIF
  xor eax,eax
  ret
WndProc ENDP

END start

Das normale DOS-Programm läuft linear in einer Schleife (z. B. warten auf Tastendruck und Tastenauswertung). Ein Windows-Programm muss/kann auf eine Vielzahl von Ereignissen reagieren: Mausbewegungen, Mausklicks, Tastatureingaben, Taskwechsel, Überdeckungen durch andere Programme. Die Information über solche Ereignisse wird über Nachrichten verschickt. Jedes Programm verfügt über eine Nachrichtenschleife (Message Loop). In diese Schleife werden von Windows alle Nachrichten eingereiht die das Programm betreffen. Innerhalb des Programms werden die Nachrichten aus der Schleife entnommen und darauf reagiert. Dabei muss man nur die Nachrichten entnehmen, die tatsächlich von Interesse sind. Nachrichten die man nicht selbst verarbeitet werden an eine Standardfunktion weitergereicht, die wiederum in standardisierter Form die Nachrichten abarbeitet.

Im obigen Programm ist die Nachrichtenschleife in der Prozedur MsgLoop enthalten. Nach der Initialisierung des Programms hält es sich nur noch innerhalb der Schleife auf. Mit GetMessage() wird die nächste wartende Nachricht aus der Schleife entnommen und in der Variablen msg gespeichert. Nur wenn der Rückgabewert von GetMessage() ungleich Null ist wird die Nachricht per DispatchMassage() an die für das betreffende Fenster zuständige Callback-Prozedur weitergeleitet.

Beim Aufruf der Funktion RegisterClassEx() wird die Adresse der Struktur wc übergeben. In dieser Struktur gibt es den Member lpfnWndProc, dem die Adresse der fensterweiten Callback-Prozedur zugewiesen wird. Im Beispiel heißt die Fensterprozedur WndProc. Diese Prozedur wird nicht durch Sie, sondern von Windows aufgerufen. In dieser Prozedur wird die Nachricht daraufhin überprüft, ob Sie für uns interessant ist. Hier interessiert uns nur die Nachricht die beim Schließen des Fensters gesendet wird.

Im Programm wird darauf reagiert indem die Funktion PostQuitMessage() aufgerufen wird. Diese Funktion wiederum sendet die Nachricht WM_QUIT an das Fenster. Durch diese Nachricht erhält GetMessage() einen Returnwert von Null, was zum Verlassen der Nachrichtenschleife führt. Die Nachricht WM_DESTROY können Sie auch selbst senden indem Sie die Funktion DestroyWindow() aufrufen.

Die Parameter der CreateWindowEx()-Funktion sowie die Struktur WNDCLASSEX werden in der Referenz näher beschrieben.

Hier noch ein kleines Beispiel, das beliebig zu einem Systeminformationstool ausgebaut werden kann.

TITLE Durch CPUID ermittelte Informationen ausgeben
.586
.MODEL flat, stdcall
OPTION casemap:none
INCLUDE \masm32\include\windows.inc 
INCLUDE \masm32\include\kernel32.inc 
INCLUDELIB \masm32\lib\kernel32.lib 
INCLUDE \masm32\include\user32.inc 
INCLUDELIB \masm32\lib\user32.lib 

.DATA
cModel db 12 dup (?)
       db 0Dh,0Ah
       db "Anzahl Funktionen: "
cFunkt db ?,0       
cCaption db "CPUID-Infos",0

.CODE
start:
  
  mov eax,0			;hoechste bekannte Funktionsnummer und
  cpuid				;Identifikationsstring ermitteln
  
  mov dword ptr cModel,ebx
  mov dword ptr cModel + 4,edx
  mov dword ptr cModel + 8,ecx
  add al,30h			;in ein Zeichen umwandeln
  mov cFunkt,al
  
  invoke MessageBox, NULL, ADDR cModel, ADDR cCaption, MB_OK
  
  invoke ExitProcess,0
  
END start

Die Anweisung .586 ist hier notwendig, da CPUID erst ab dieser Prozessorklasse bereit gestellt wird.

Namenskonventionen und Ungarische Notation

In den Beispielprogrammen und Codefragmenten sind ihnen wahrscheinlich eine Reihe unbekannter Bezeichner aufgefallen. Die API-Dokumentation gibt die Syntax von Funktionen, den Aufbau von Strukturen und Beispielprogramme in der Mehrzahl der Fälle in der Sprache C an. Die Windows-Entwickler haben ein System erdacht, mit dem möglichst schnell erkennbar sein soll, was im Programm welches syntaktische Element darstellt (Funktion, Variable, Konstante).

Von Windows bereit gestellte Funktionen beginnen in der Regel mit einem Großbuchstaben. Setzt sich der Funktionsname aus mehreren Worten zusammen, so wird jeder Wortanfang groß geschrieben, z. B. DefWindowProc(). Ich werde dieser Konvention nur teilweise folgen - die meisten der selbstdefinierten Funktionen in den Beispielprogrammen werden mit einem Kleinbuchstaben beginnen, bereits gesehene Ausnahmen davon stellen die Funktionen MsgLoop() und WndProc() dar.

Konstanten werden komplett in Großbuchstaben geschrieben. Dieses System sollte auch in ihren Programmen beibehalten werden, da die Bezeichner sonst nur schwer von Variablennamen zu unterscheiden sind.
Konstanten beginnen oft mit einem bestimmten Präfix, der sie in einen Sachzusammenhang mit der Art der Konstante bzw. ihrer Verwendung bringt.

Präfixe bei Konstanten
Präfix Bedeutung
CS_ Class Style - Stile einer Fensterklasse, z. B. CS_BYTEALIGNCLIENT
CW_ Create Window - zur Fenstererzeugung, meist als Parameter, z. B. CW_USEDEFAULT
DT_ Draw Text - Konstanten zur Textausgabe, z. B. DT_CENTER
IDC_ ID Cursor - ID (Kennziffer) für eine Cursorform, z. B. IDC_WAIT (die Sanduhr)
IDI_ ID Icon - ID für ein Icon, z. B. IDI_APPLICATION
MB_ Message Box - Stile für Meldungsfenster, z. B. MB_YESNOCANCEL
WM_ Window Message - Nachrichten an Fenster, z. B. WM_DESTROY
WS_ Window Style - Konstanten für Fensterstile, z. B. WS_OVERLAPPEDWINDOW

Konstanten werden im MASM32-Paket zu großen Teilen in der Datei windows.inc definiert. Es gibt noch Dutzende weiterer Präfixe, z. B. für Windows-Steuerelemente (ES_, SBS_, CB_) sowie eine Art Empfehlung für selbstdefinierte Steuerelemente (IDC_ - ID Control) und Menüpunkte (IDM_).

Der grundlegende Datentyp in Win32 ist das DWORD, unter Win16 war es noch das WORD. Da den Windows-Entwicklern sicher klar war, dass die Welt nicht bei 16 Bit stehen bleibt, haben sie ihre eigenen Datentypen definiert, die im Hintergrund auf einen plattformspezifischen und in C gültigen Datentyp umgesetzt wurden. Hatte man sich in seinem 16-Bit-Windowsprogramm an diese "neuen" Datentypen gehalten, dann war der Umstieg auf die 32-Bit-Welt deutlich einfacher, unter Umständen war die Sache bereits mit einer Neukompilierung erledigt. Ein ähnlicher Umstieg ist für die 64-Bit-Versionen zu erwarten. Insofern ist der Wust neuer Datentypen wie HINSTANCE, HWND, LPARAM und WPARAM schon zu verstehen. Allerdings wird dadurch in meinen Augen auch verhindert, dass man erkennt, ob man es letztlich mit einem einfachen Datentypen oder einer Struktur zu tun hat.

In eine ähnliche Kerbe schlägt die Ungarische Notation, wenn auch auf der anderen Seite der Variablendeklaration. Benannt wurde sie nach dem Microsoft-Programmierer Charles Simonyi. Dabei ist vorgesehen, dass jeder Variablenname mit Kleinbuchstaben beginnt, die den Datentyp der Variable kennzeichnen. Ganz durchgehalten hat Microsoft diese Systematik beim Umstieg auf 32 Bit allerdings auch nicht. Beachten sie bitte, dass die Notation auf den Datentypen von C beruht, nicht auf denen von MASM.

Präfixe bei Variablen
Präfix Bedeutung
b, f BOOL (f steht für Flag; beachten sie, dass es noch bool und BOOLEAN gibt)
by byte
c char
cw wide char (Unicode)
cx, cy int (Längenangaben; das c steht für count; meist im Zusammenhang mit Koordinaten)
dw DWORD
fn Function
h Handle
i int
l LONG
lp Long Pointer
n short
p Pointer
s String
sz ASCIIZ-String, also ein String, bei dem ASCII Null das Ende anzeigt
w WORD
x, y int (Koordinaten)

In der Programmiererrealität ergeben sich dann auch schon mal Kombinationen wie lpfn - Long Pointer to Function usw.

Ob sie diese Systematik auch in ihren Assemblerprogrammen verwenden ist komplett ihnen überlassen. Vielleicht haben sie eine eigene Konvention die sie sinnvoller oder einfacher finden, oder sie möchten die Konvention verwenden, die sie aus ihrer Firma kennen. Die Kenntnis dieser Notation kann ihnen aber weiterhelfen, wenn sie Quelltexte in C für Windows lesen.
Ich selbst werde in meinen Beispielen eine etwas abweichende Notation verwenden: i für Ganzzahlen, c für Zeichenketten, h für Handles und p für Zeiger wo es mir angebracht erscheint.

Bei komplexen Datentypen (Strukturen) gibt es keine Festlegungen, wie die Variablen genannt werden sollen. Da Strukturbezeichner ebenfalls komplett in Großbuchstaben geschrieben werden, können sie die jeweilige Variable in Kleinbuchstaben schreiben oder abkürzen, z. B.

msg MSG <?>
ps PAINTSTRUC <?>
Bei Strukturmembern greift jedoch wieder die Ungarische Notation
LOCAL wc:WNDCLASSEX
mov wc.lpfnWndProc,OFFSET WndProc