Kommandozeilenparameter

Das Thema Kommandozeilenparameter oder -argumente kommt vielleicht, verglichen mit sonstigen Tutorials, etwas früh, aber da in späteren Beispielen mit diesen Argumenten gearbeitet wird, kommt die Erklärung schon jetzt und nicht später mittendrin. Zumal dieses Thema ohne Fenster und sonstiges grafisches Beiwerk auskommt.

Kommandozeilenparameter sind bei Windows-GUI-Programmen (Programme mit grafischer Bedienoberfläche) etwas in den Hintergrund getreten, da Dateien und Programmoptionen meist durch entsprechende Dialoge ermittelt werden können.

In Hochsprachen werden Kommandozeilenparameter auf verschiedene Weise bereit gestellt. Aus C kennen sie vielleicht die Programmhauptfunktion int main(int argc, char *argv[])
oder aus Pascal die beiden Funktionen ParamCount und ParamStr aus der Unit System. In Windows-C-Programmen gibt es die Funktion int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow).

In den C-Programmen wird bei Erzeugung der exe-Datei Programmcode eingefügt, der die Funktionen main() oder WinMain() aufruft. Dieser Code führt Initialisierungen aus, ermittelt die Kommandozeile und übergibt im Fall von main() die Anzahl der Argumente als Parameter argc und die Argumente selbst als Zeiger argv auf ein Zeichenarray. Im Falle von WinMain() wird nur der Zeiger auf die Zeichenkette übergeben.

In Assembler für Windows gibt es diese main- oder WinMain-Funktion nicht. Um an die Kommandozeile zu kommen, kann die API-Funktion GetCommandLine() verwendet werden. Damit entfällt schonmal das hantieren mit dem PSP wie unter DOS. GetCommandLine() wird durch kernel32.dll bereit gestellt, die Funktionsdefinition in MASM32 durch die Includedatei kernel32.inc. In kernel32.inc wird durch GetCommandLine equ <GetCommandLineA> erreicht, dass beim kompilieren des Quellcodes die ANSI-Variante der Funktion verwendet wird.

In den Nachfolgern von Windows NT 3.5 (also NICHT Windows 95/98/Me) gibt es die Funktion CommandLineToArgvW(), exportiert von shell32.dll. Das "W" am Ende sagt schon aus, dass es sich um eine Funktion handelt, die mit Unicode arbeitet. Die Definition in C sieht so aus:

 LPWSTR *CommandLineToArgvW(LPCWSTR lpCmdLine,int *pNumArgs).

lpCmdLine ist dabei die Kommandozeile im Unicode-Format, *pNumArgs die Adresse einer dWord-Variablen, in die die Anzahl der Parameter geschrieben wird. Als Ergebnis liefert die Funktion die Adresse eines Arrays, in dem wiederum die Adressen der einzelnen Argumente enthalten sind. pNumArgs sollte nach dem Funktionsaufruf mindestens den Wert 1 enthalten, da der Programmname immer mitzählt. Der Programmname wurde u. U. auf den kompletten Verzeichnispfad erweitert.

Um diese Funktion nutzen zu können, muss die Kommandozeile also in der Unicode-Variante geliefert werden. Dies erledigt GetCommandLineW().

Das Array, das GetCommandLineToArgvW() erzeugt und dessen Adresse die Funktion liefert, kann man sich so vorstellen:

  ArrayAdr = GetCommandLineToArgvW(pCmdLine,pArgv)
  ArrayAdr + 0 = Adresse der Zeichenkette für Parameter 0 (Programmname)
  ArrayAdr + 4 = Adresse der Zeichenkette für Parameter 1 usw.

In einem Programm sieht es dann folgendermaßen aus.

;cmdlnarg - Kommandozeilenargumente
.586
.MODEL flat,stdcall
OPTION casemap:none

include c:\masm32\include\windows.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\shell32.inc
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\shell32.lib

.DATA
cArgument db "A",0,"r",0,"g",0,"u",0,"m",0,"e",0,"n",0,"t",0,0,0
cParamTitle db "Parameterfehler",0
cParamFehler db "Unzureichende Anzahl Parameter",0

.DATA?			;uninitialisierte Daten
hInstance HINSTANCE ?
pCmdline LPSTR ?
pArgs dword ?           ;Zeiger auf ein Array
iArgs dword ?           ;Anzahl Argumente

.CODE
start:
  call mymain
  invoke LocalFree,pArgs
  invoke ExitProcess,0


mymain PROC
  
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke GetCommandLineW    ;Verwendung der Unicode-Version
  mov pCmdline,eax
  invoke CommandLineToArgvW,eax, ADDR iArgs
  mov pArgs,eax
  cmp iArgs,2           ;gibt es mindestens ein Programmargument
  jge @@weiter
  invoke MessageBox,0,ADDR cParamFehler,ADDR cParamTitle,MB_OK
  ret                   ;wenn nicht, dann Abbruch mit Meldung
  @@weiter:
  mov eax,pArgs         ;Startadresse des Arrays nach EAX
  mov ebx,[eax + 4]     ;Startadresse des ersten Programmarguments ermitteln
  invoke MessageBoxW,0,ebx,ADDR cArgument,MB_OK
  dec iArgs
  add pArgs,4
  cmp iArgs,1
  jne @@weiter
  ret
mymain ENDP

END start

Dieses Programm führt keine Prüfung der Windows-Version durch, läuft also gnadenlos in die Falle, wenn CommandLineToArgvW() nicht vorhanden ist. Sie können es in einer DOS-Box z. B. so starten:
cmdlnarg 134 fa2 aasdf ds3 asdf4.
Probieren sie auch was passiert, wenn sie kein Argument angeben oder ein Argument in Anführungszeichen einschließen.

Da natürlich die Ergebnisse von CommandLineToArgvW() ebenfalls als Unicode-Zeichenketten vorliegen, muss auch MessageBox() in der Unicode-Variante aufgerufen werden. Und aus dem gleichen Grund liegt der Titel der Messagebox im Unicode-Format vor (beachten sie das doppelte Null-Byte am Ende).
Alternativ wäre die Umwandlung in ANSI-Zeichenketten zu erwägen. Befragen sie hierzu am besten ihre API-Dokumentation unter WideCharToMultiByte().

CommandLineToArgvW() reserviert intern Speicher für das Argumentarray. Dieser Speicher ist nach Beendigung der Auswertung wieder frei zu geben. Dazu wurde hier im Hauptprogramm LocalFree() verwendet.

Die Auswertung der einzelnen Parameter ist wieder eine Sache für sich. Die Komplexität steigt hier mit der Anzahl und Art der Parameter sowie deren Kombinationsmöglichkeit. Wenn sie ein fest definiertes Parameterformat haben, dann reicht obiger Code. Ansonsten ist die Verwendung von Toolfunktionen zu empfehlen, die es mit Sicherheit irgendwo zu finden gibt. Ähnlich sieht es aus, wenn diese Funktionalität auch unter den Nicht-NT-Nachfolgern geboten werden soll.