Home

PROC, ENDP, PROT,
INVOLVE und APIs

Programmbeispiel:
Frage-Programm

MASM32-
Infoseite

MASM32 SDK (Windows Konsole):
Testprogramm zum Einlesen der Programmaufrufparameter

Beim Starten eines Programms können Parameter mitgegeben werden. Das Programm kann so programmiert werden, dass die Parameter ausgewertet werden. Dadurch kann beispielsweise der Programmablauf beeinflusst werden.

Es gibt zwei API-Funktionen zum Einlesen der Parameter. Die eine Funktion stellt den gesamten Programmaufruf als Zeichenkette (String) so zu Verfügung, wie er im Konsolmodus eingetippt wurde. Falls das Programm im Windowsmodus gestartet wurde, wird der Eintrag gemeldet, der bei den Programmeigenschaften hinterlegt ist.

In beiden Fällen werden ggf. Gänsefüßchen um den Programmpfad/Programmnamen ergänzt.

Die andere Funktion meldet lediglich die Parameter und deren Anzahl. Wenn man sie benutzt, braucht man die Parameter aus dem Programmaufruf nicht mehr zu extrahieren, denn das ist bereits geschehen. Diese Funktion wird durch dass hier vorgestellte Programm getestet. Es galt, Lücken in der mir vorliegenden Funktionsbeschreibung zu schließen.

Das Testprogramm ist ein wenig provisorisch geschrieben. Man erkennt es an der hier unpassenden Bedienungsanleitung hinter der Fehlermeldung „Unzureichende oder falsche Anzahl Parameter”. Auch die Textausgabe „Hi, I am in the 'main' procedure” deutet an, dass ich mir keine besondere Mühe mit Erstellen des Testprogramms gegeben habe.

Davon abgesehen, habe ich einige Dinge realisiert, die auf der vorhergehenden Webseite erwähnt wurden:

  • Unterprogramme ohne PROC ... ENDP Direktivenpaar
  • Anonyme Labels
  • Benutzung von INVOLVE zum Aufruf einer API
Die mit US-englischen Kommentaren versehenen Texte stammen aus den Beispielen im MASM32 SDK. Das eingesetzte Wissen über die Verarbeitung der Kommandozeilenparameter stammt aus der Lektion Kommandozeilenparameter des Assemblertutorials - Windows-Programmierung von André Müller. Deshalb haben einige Programmzeilen eine auffallende Ähnlichkeit mit dem dortigen Beispiel.

Eine andere Möglichkeit, die Parameter der Kommandozeile auszuwerten, läuft über invoke GetCL. Man findet ein passendes Makro in der Datei /masm32/macros/mascros.asm wenn man nach „GetCL” sucht. Ich habe dies Verfahren jedoch nicht ausprobiert.

Ablaufbeispiele
Hi, I am in the 'main' procedure
Unzureichende oder falsche Anzahl Parameter
Aufruf:   FRAGE.exe prompt <key-list>
Beispiel: frage.exe "Weitermachen" jn
Prompt ggf. in Gänsefüßchen!
Press any key to continue ...
Links: Das Program wurde von Explorer aus (d.h., im Windows-Modes) gestartet. Parameter wurden keine mitgegeben.
0:07:43:59 C:\masm32\Uebung\Konsole>proc
Hi, I am in the 'main' procedure
Unzureichende oder falsche Anzahl Parameter
Aufruf:   FRAGE.exe prompt <key-list>
Beispiel: frage.exe "Weitermachen" jn
Prompt ggf. in Gänsefüßchen!
Press any key to continue ...
Links: Das Program wurde im Konsolmodus gestartet. Parameter wurden keine mitgegeben. Es erscheint die gleiche Fehlermeldung wie im Windows-Modus.
0:07:44:14 C:\masm32\Uebung\Konsole>proc 1.Argument 2. Argument "3. Argument" '4. Argument'
Hi, I am in the 'main' procedure
1.Argument
2.
Argument
3. Argument
'4.
Argument'
Press any key to continue ...
Der Härtetest erfolgte im Konsolmodus. Das 1.Argument und das 3. Argument wurden so verarbeitet, wie gedacht. Durch den Zwischenraum zwischen 2. und Argument wurden daraus zwei Parameter gemacht. Die einfachen Hochkommata bei '4. Argument' wurden nicht zur Zusammenfassung des Arguments genutzt, so dass auch hier zwei Parameter erkannt wurden. Bei der Einfassung in Gänsefüßchen beim "3. Argument" wurde dies als ein Parameter erkannt. Die Gänsefüßchen wurden von der API-Funktion entfernt.
Das Quellprogramm
Eine kurze interpretation steht unterhalb des Quellprogrammtextes.
;   Build this with the "Project" menu using
;          "Console Assemble and Link"
; -----------------------------------------------
;   Versuche mit Verarbeitung der Kommandozeile 
; -----------------------------------------------
    .586                                    ; create 32 bit code
    .model flat, stdcall                    ; 32 bit memory model
    option casemap :none                    ; case sensitive

.XLIST 
    include \masm32\include\windows.inc     ; always first
    include \masm32\macros\macros.asm       ; MASM support macros

; include files that have MASM format prototypes for function calls
; -----------------------------------------------------------------
    include \masm32\include\masm32.inc
    include \masm32\include\gdi32.inc
    include \masm32\include\user32.inc
    include \masm32\include\kernel32.inc
    include c:\masm32\include\shell32.inc
    include c:\masm32\include\msvcrt.inc
  
; Library files that have definitions for function
; exports and tested reliable prebuilt code.
; ------------------------------------------------
    includelib \masm32\lib\masm32.lib
    includelib \masm32\lib\gdi32.lib
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib
    includelib c:\masm32\lib\shell32.lib
    includelib c:\masm32\lib\msvcrt.lib
.LIST

; Konstanten und Macros
; ------------------------------------------------
bel    	equ     7       ;Klingel
bs	equ	8       ;Backspace
ht	equ	9       ;Horizontaler Tabulator
lf      equ     0ah	;Linefeed
vt	equ	0bh	;Vertikaler Tabulator
ff	equ	0ch	:Formfeed
cr      equ     0dh	;Carriage Return

.data
cArgument db    "A",0,"r",0,"g",0,"u",0,"m",0,"e",0,"n",0,"t",0,0,0
argument  db    80 dup (0)
cParamFehler    db  "Unzureichende oder falsche Anzahl Parameter",cr,lf
;Hilfstextanzeige wenn Aufruf ohne Parameter oder nur Spaces als Parameter
hilfe     db    "Aufruf:   FRAGE.exe prompt <key-list>",cr,lf
          db    'Beispiel: frage.exe "Weitermachen" jn',cr,lf
          db    "Prompt ggf. in G",84h,"nsef",81h,0e1h,"chen!"
crlf      db    cr,lf,0         ;Zeilenwechsel  ;ergaenzt hilfe
comment #
Umlauttabelle, enthält Winddows- und MS-Dosumlaute äöüßÄÖÜ jeweils nebeneinander
zuerst Windows, dann MS-DOS
          ä  ö  ü  Ä  Ö  Ü  ß
Windows:  e4 f6 fc c4 d6 dc df
MS-DOS:   84 94 81 8e 99 9a e1
#
umlaute db 0e4h,84h,0f6h,94h,0fch,81h,0c4h,08eh ;äöüA 
        db 0d6h,99h,0dch,9ah,0dfh,0e1h          ;ÖÜß

.data?			;uninitialisierte Daten
hInstance HINSTANCE ?
pCmdline LPSTR ?
pArgs dword ?           ;Zeiger auf ein Array
iArgs dword ?           ;Anzahl Argumente um 1 erhöht


.code                   ;Tell MASM where the code starts

; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

start:                          ; The CODE entry point to the program
        call    main            ; branch to the "main" procedure
        inkey                   ; Tastendruck abwarten bei Programmende

        mov     eax,2           ; testweise auf Errorlevel 2    
        exit    eax

; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

main:   print   chr$("Hi, I am in the 'main' procedure",13,10)

        invoke  GetCommandLineW         ;Verwendung der Unicode-Version
        mov     pCmdline,eax
        invoke  CommandLineToArgvW,eax, ADDR iArgs
        mov     pArgs,eax
        cmp     iArgs,2                 ;gibt es mindestens einen Aufrufparameter ?
        jge     zeige_parameter

        print   offset  cParamFehler    ;wenn nicht, dann Abbruch mit Meldung
        ret                  

zeige_parameter:
        mov     eax,pArgs         ;Startadresse des Arrays nach EAX
        mov     ebx,[eax + 4]     ;Startadresse des Aufrufparameters ermitteln
        push    ebx               ;Startardesse wird noch benoetigt

comment #
Statt WideCharToMultiByte() werden nur die zusaetzlichen Nullen entfernt

int WideCharToMultiByte(
    UINT CodePage,	          // code page 
    DWORD dwFlags,	          // performance and mapping flags 
    LPCWSTR lpWideCharStr,	  // address of wide-character string 
    int cchWideChar,	          // number of characters in string 
    LPSTR lpMultiByteStr,	  // address of buffer for new string 
    int cchMultiByte,	          // size of buffer 
    LPCSTR lpDefaultChar,	  // address of default for unmappable characters  
    LPBOOL lpUsedDefaultChar 	  // address of flag set when default char. used 
   )
Vereinfachter Aufruf mit fester Länge bei cchMultiByte   
invoke  WideCharToMultiByte,CP_ACP,0,ebx,20,offset argument,80,0,0 
#
        mov     edi,ebx         ;nach 
        mov     esi,ebx         ;von
@@:     mov     al,[esi]        ;fertig konvertiert ?
        cmp     al,0
        je      @F              ;fertig konvertiert !   

        call    todos           ;wandelt äöüÄÖÜß von Window- in DOS-ASCII

        mov     [edi],al        ;zusammenschieben
        inc     edi
        inc     esi
        inc     esi
        jmp     @B

@@:     mov     [edi],al        ;Stringende markieren
        pop     ebx             ;Startadresse des Parameters

        print   ebx             ;entspricht print offset argument 
        print   offset crlf
        dec     iArgs
        add     pArgs,4
        cmp     iArgs,1
        jne     zeige_parameter ;zur Anzeige des nächsten Parameters
   
        ret                     ;nun sind alle Parameter abgearbeitet

; Unterprogramme
; ---------------------------------------------------
;Unterprogramm Codewandlung Umlaut von Windows- in DOS-Codierung (ASCII)
;Das zu untersuchende Zeichen steht in al
; -----
todos:  mov     cx,7            ;Tabelle Umlaute ist 14 Zeichen lang
        lea     ebx, umlaute    ;zur Adressierung
        .while  cx >0
        cmp     [ebx],al
        je      @F
        inc     ebx             ;adressieren naechsten Tabellenplatz
        inc     ebx
        dec     cx
        .endw
        ret

@@:     inc     ebx             ;Codewandlung des Zeichens und 
        mov     al,[ebx]        ;Unterprogrammende        
        ret
      
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

end start                       ; Tell MASM where the program ends

Interpretation des Quellprogramms

cArgument unter .data: Die Zeichenkette wird nicht verwendet. Sie zeigt jedoch den grundsätzlichen Aufbau der von Windows genutzten Unicode-Codierung: Jedes Windows-ASCII-Zeichen wird um ein Nullbyte ergänzt. Die später verwendete API-Funktion GetCommandLineW gibt einen Adresszeiger auf den Inhalt der Kommandozeile in Unicode zurück.

argument unter .data: Das 80 Byte lange Feld wird nicht verwendet. Es würde Verwendung finden, wenn man die API-Funktion WideCharToMultiByte zur Wandlung von Unicode in Windows-ASCII genutzt hätte. Ein solcher Aufruf wird weiter unten im Quelltext als Kommentar beschrieben.

Anzeigetexte unter .data: Ab cParamFehler schließt sich der Text eines Fehlerhinweises mit einem anschließenden Benutzungshinweis an. Die beiden folgen Labels sind so gesetzt, dass auch nur der Benutzungshinweis oder nur ein Zeilenwechsel ausgegeben werden kann.

umlaute ist eine Tabelle. Sie ermöglicht die Übersetzung der deutschen Umlaute von Windows- in MS-DOS-Codierung. Die Tabelle wird von dem Unterprogramm todos genutzt.

hInstance unter .data? wird nicht genutzt, denn dies Programm verwendet keine zusätzlichen Handles. Die Datei /masm32/include/windows.inc stellt über das dort definierte Schlüsselwort sicher, dass hInstance ein Doppelwort (dword) ist.

pArgs und iArgs unter data? gehören zum Aufruf von GetCommandLineW.

Bei start unter .code beginnt das vier Befehle kurze Hauptprogramm. Es handelt sich um den Aufruf des Unterprogramms main. Nach der Rückkehr daraus wird mit Inkey eine Texteingabe angefordert. Dadurch wird erreicht, dass das Programm erst nach einer zusätzlichen Tastenbetätigung beendet wird. Würde das Program unter Windows gestartet, wäre es ohne eine solche Zwangsbremse so schnell durchgelaufen, dass der Benutzer es auf dem Bildschirm nicht gesehen hätte. Testweise wird das Programm mit dem Errorlevel 2 beendet.

Das Unterprogramm main führt die Verarbeitung durch. Zuerst wird aus Testgründen die Zeile „Hi, I am in the 'main' procedure” auf den Bildschirm geschrieben. Danach wird mit der API-Funktion GetCommandLineW die Adresse der Kommandozeile, mit der das Programm gestartet wurde, ermittelt und im Doppelwort unter pCMDline gespeichert. Tatsächlich hätte man in diesem Programm auf diese Speicherung verzichten können, denn pCMDLine wird im Programm an keiner weiteren Stelle verwendet!

Der anschließende Aufruf der API-Funktion CommandLineToArgvW gibt in EAX die Startadresse der Kommandozeile zurück. Sie wird in pARGs gesichert. Die Anzahl der Parameter hatte die API-Funktion eigenständig in iArgs eingetragen.

CommandLineToArgvW zählt den Programmaufruf als ersten Parameter. Wenn nur ein weiterer Parameter angegeben wird, stünde der Zähler auf 2. Sofern kein Parameter angegeben wurde, würde der unter .data ab cParamFehler hinterlegte Text auf den Bildschirm gezeigt werden und das Unterprogramm main würde vorzeitig verlassen werden.

Andernfalls wird ab zeige_parameter die zeilenweise Anzeige der Parameter auf dem Bildschirm vorbereitet. Der Programmaufruf (ggf. Pfad, auf jeden Fall aber Programmname) soll nicht angezeigt werden. Deshalb wird der im Register EBX aufgebaute Adresszeiger gleich um Doppelwortlänge (4 Bytes) vorgerückt. Danach zeigt er auf den ersten Parameter der Kommandozeile. Da sein Wert später erneut benötigt wird, wird EBX im Stack gesichert.

Der mit comment # startende Kommentar zeigt, mit welcher API-Funktion nacheinander jeder einzelne Parameter von Unicode-16 in Windows-ASCII gewandelt werden kann. Allerdings ist dies Programm keine Windows-Anwendung, sondern es ist eine Konsolanwendung. Nach der Codewandlung mit der API-Funktion hätte man den entstandenen Windows-ASCII-Text wegen der deutschen Umlaute in DOS-ASCII wandeln müssen. Ich habe deshalb auf die Verwendung der API-Funktion WideCharToMultiByte verzichtet und die Codewandlung von Unicode-16 in DOS-ASCII in den 13 Programmzeilen (Leerzeilen mitgezählt) hinter dem Kommentar untergebracht. Ohne den Unterprogrammaufruf call todos wird jedes zweite Zeichen der Unicode-16-Codierung (also die 0) entfernt und die Zeichenkette wird nach links aufgeschoben. Es passiert also:

db "T",0,"e",0,"d",0,"d",0,"y",0,0,0 wird zu     
db "T","e","d","d","y",0,"d",0,"y",0,0,0

Da bei den Strings die erste auftauchende Null als Stringende betrachtet wird, gilt der zweite String beim "y" als beendet.

Mit dem call todos wird das sich gerade behandelte einzelne Zeichen überprüft, ob es ein Umlaut in Windows-ASCII ist. Falls ja, wird es in DOS-ASCII gewandelt.

Mit pop ebx wird die Startadresse des Parameters wieder hervorgeholt und der Parameter wird sofort anschließend als Zeile auf dem Bildschirm dargestellt. Sofern weitere Parameter vorhanden sind, wird mit der Bearbeitung des nächsten Parameters begonnen. Andernfalls wird das Unterprogramm main hier verlassen.

Vor dem Abschnitt zur Beschreibung der Codewandlung seine einige Bemerkungen erlaubt.

Der Code, den ich etwas locker als Windows-ASCII bezeichne, heißt offizieller „Windows-Codepage 1252 (oder cp1252, Windows-1252, ...). Für die meisten Zeichen kann man von Windows-1252 in Unicode-8 (UTF-8) dadurch übersetzen, dass man ein Nullbyte voranstellt. Beispielsweise wird das große A in Windows-1252 als db 40h codiert. In UTF-8 lautet seine Codierung dw 0040h oder alternativ db 40h,0.

Bei einigen Sonderzeichen im Windows-1252-Bereich db 80h bis db 9fh gibt es Ausnahmen. Glücklicherweise sind die Codierungen der deutschen ÄÖÜäöüß hiervon nicht betroffen, so dass man einfach eine 0 ergänzen oder entfernen muss. Betroffen ist beispielsweise das Währungszeichen €. In Windows-1252 ist es db 80h, bei UTF-8 ist es dw 20ach bzw. db 0ach,20h.

Eine gut brauchbare Übersetzungstabelle zwischen Windows-1252 und UTF-8 ist auf www.recherche-redaktion.de/zeichensaetze/cp1252.htm zu finden.

 i Dieser Bildschirmabdruck zeigt die DOS-ASCII-Codetabelle.

Das Unterprogramm todos führt die Codewandlung der deutschen Umlaute ÄÖÜäöüß von Windows-1252 in DOS-ASCII durch. Es ist erstaunlich einfach.

Beispielhaft wird hier die Verwendung des Direktivenpaars .while ... .endw zur Codierung einer While-Schleife demonstriert. Besonders sei angemerkt, dass die sonst von der 16-bit Assemblierung gewohnte ähnliche Schleifenbildung mit

    mov cx,Schleifenzähler
Schleifenanfang:
    ... sonstige befehle ...
    loop Schleifenanfang
unter der 32-bit-Programmierung nicht funktioniert. Bei der 32-bit-Programmierung arbeitet loop mit dem ecx-register zusammen! Man müsste hier codieren:
    mov ecx,Schleifenzähler
Schleifenanfang:
    ... sonstige befehle ...
    loop Schleifenanfang

Letztes Upload: 17.10.2016 um 18:11:44