MASM32-SDK (Windows Konsolapplikationen):
Programmbeispiel: Frage-Programm mit GetCL und ein Vergleich mit PureBasic

Dies ist ein 32-bit-Nachbau des 16-bit-Programms FRAGE.COM.

Beide Versionen leisten etwa das gleiche. Allerdings unterscheiden sie sich etwas im Format des Aufrufs.

Die hier gezeigte 32-bit-Version verwendet die im MASM32-SDK empfohlene Methode der Parameterabfrage mittels „GetCL”.

Das 32-bit-Programm Frage.exe ist 2569 Byte groß. Wenn es für die Fehlersuche mit eingeschaltetem DEBUG assembliert wurde, ist es 4608 Bytes groß. Die 16-Bit Version FRAGE.COM ist mit 216 Bytes Größe deutlich kleiner.

Für eine weiteren Vergleich habe ich ein funktionsähnliches Programm in der Programmiersprache Pure Basic erstellt. Das daraus erstellte ausführbare 32-bit-Programm Frage.exe kommt auf 12899 Bytes Größe.

Die Funktion von Frage.exe lässt sich anhand der Steuerdatei Ask.bat demonstrieren:

@echo off
if %1 == 3 goto:frage3
if %1 == 2 goto:frage2
if %1 == 1 goto:frage1

echo Stapeldatei: Parameter 1, 2 oder 3 eingeben!
goto raus

:frage1
frage "Bitte 'j' zur Antwort Ja (jn)" nj
goto returncode

:frage2
frage "Umlaute: abcÄäÖöÜüßz"
goto returncode

:frage3
frage

:returncode
if errorlevel 99 goto aufruffehler
if errorlevel 2 goto ja
if errorlevel 1 goto nein

echo Aufruf mit nur einem Parameter
goto raus

:aufruffehler
echo Der Aufruf war fehlerhaft
goto raus

:ja
echo Die Antwort war "Ja"
goto raus

:nein
echo Die Antwort war "Nein"
:raus

Ablaufbeispiel
Links wird die Scriptdatei Ask.bat gezeigt. Unterhalb sind die etwas abgeschnittenen Bildschirmanzeigen von vier Durchläufen zu sehen.

C:\masm32\Uebung\Konsole>fragetest 1
Bitte 'j' zur Antwort Ja (jn)>
Die Antwort war "Ja"
C:\masm32\Uebung\Konsole>fragetest 1
Bitte 'j' zur Antwort Ja (jn)>
Die Antwort war "Nein"

C:\masm32\Uebung\Konsole>
C:\masm32\Uebung\Konsole>fragetest 2
Umlaute: abcÄäÖöÜüßz
Aufruf mit nur einem Parameter
C:\masm32\Uebung\Konsole>fragetest 3
Der erste Parameter 'prompt' fehlt!
Aufruf nur mit einem oder zwei Parametern möglich!

Aufruf:   FRAGE.exe prompt [key-list]
Beispiel: frage.exe "Weitermachen (jn)" jn
Ohne key-list kann mit einer beliebigen Taste
geantwortet werden Prompt ggf. in Gänsefüßchen! --- C:\masm32\Uebung\Konsole>

Befehlsbeschreibung von GetCL gemäß MASM32 Library reference

GetCL proc ArgNum:DWORD, ItemBuffer:DWORD

GetCL übergibt das in seinem Aufruf spezifizierte Kommandozeilenargument im spezifizierten Pufferbereich (buffer). Name und Pfad des aufrufenden Programms haben dabei die Argumentennummer 0. Der erste Parameter hat die Nummer 1.

In Gänsefüßchen eingekleidete Kommandozeilenargumente werden ohne Gänsefüßchen in den Pufferbereich übertragen.

Parameter:
1. ArgNum Argumentennummer
2. ItemBuffer Adresse des Empfangspuffers

Rückgabewerte:
1 = erfolgreiche Ausführung
2 = das spezifizierte Kommandozeilenargument ist nicht vorhanden
3 = die Gänsefüßchen passen nicht zueinander (d.h. deren Anzahl ist ungerade)
4 = zwischen zwei Gänsefüßchen ist ist keinerlei Inhalt (empty quotation marks)

Hinweis: Der Pufferbereich sollte mit einer Länge von 128 Bytes angelegt werden. Dies ist gleichzeitig seine zugelassene Maximallänge.

Beispiel zur Übertragung des ersten Arguments in den angegebenen Puffer:

invoke GetCl, 1, ADDR buffer

Das Quellprogramm für MASM32-SDK
Eine kurze Interpretation steht unterhalb des Quellprogrammtextes.

; -----------------------------------------------
;   Build this with the "Project" menu using
;          "Console Assemble and Link"
; -----------------------------------------------
;   Versuche mit Verarbeitung der Kommandozeile
; mymasm32rt.inc - siehe fredriks.de/8086asm/masm321.php?f=2#mymasm32rt
; -----------------------------------------------
debugFT equ FALSE
include  c:\masm32\include\mymasm32rt.inc
.LIST
.data
buffer1 db 128 dup(0)
buffer2 db 128 dup(0)

cParamFehler    db  "Aufruf nur mit einem oder zwei Parametern m",oe,"glich!",cr,lf,lf
;Hilfstextanzeige wenn Aufruf ohne Parameter
hilfe   db      "Aufruf:   FRAGE.exe prompt [key-list]",cr,lf
        db      'Beispiel: frage.exe "Weitermachen (jn)" jn',cr,lf
        db      "Ohne key-list kann mit einer beliebigen Taste geantwortet werden",cr,lf
        db      "Prompt ggf. in G",ae,"nsef",ue,sz,"chen!",lf
crlf    db      cr,lf,0         ;Zeilenwechsel  ;ergaenzt hilfe
prompt  db      '>',0           ;fast ein Promptzeichen

comment #
Umlauttabelle, enthält Winddows- und MS-Dosumlaute äöüßÄÖÜ jeweils nebeneinander
zuerst Windows, dann Notepad, dann MS-DOS
          ä  ö  ü  Ä  Ö  Ü  ß
Windows: e4 f6 fc c4 d6 dc df
notepad: f5 f7 b3 2d cd 5f af
MS-DOS:  84 94 81 8e 99 9a e1
#
umlaute db 0f5h,ae,0f7h,oe,0b3h,ue              ;äöü Notepad
        db 02dh,Ae,0cdh,Oe,05fh,Ue,0afh,sz      ;ÄÖÜß Notepad
        db 0e4h,ae,0f6h,oe,0fch,ue              ;äöü Windows
        db 0c4h,Ae,0d6h,Oe,0dch,Ue,0dfh,sz      ;ÄÖÜß Windows
        db 0,0                                  ;garantiertes Tebellenende

.DATA?              ;uninitialisierte Daten
iArgs           dword   ?       ;Anzahl Argumente +1
iArgszaehler    dword   ?       ;Schleifenzaehler Anzahl Argumente

.code                   ; Tell MASM where the code starts

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

start:  SetConsoleCaption    "Frage ergibt Returncode"
                            ; The CODE entry point to the program
    call    main            ; branch to the "main" procedure

    push    eax             ; sichert den ermittelten Errorlevel
;   inkey                   ; keinen Tastendruck abwarten bei Programmende
    pop     eax
    exit    eax             ; Errorlevel wurde in eax mitgegeben
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««

main proc
    DumpMem offset umlaute, 32, "Umlauttabelle bei Programmstart"

    invoke  GetCL,1,ADDR buffer1
    cmp     eax, TRUE
    je      @F
    print   chr$("Der erste Parameter 'prompt' fehlt!",13,10)
    call    help
    ret
  @@:
    mov     iArgs,2         ;iArgs enthält Parameteranzahl +1
    DumpMem offset iArgs,8, "1.Argument"

    invoke  GetCL,2,ADDR buffer2
    cmp     eax, TRUE
    jne     @F
    mov     iArgs,3         ;iArgs enthält Parameteranzahl +1
  @@:
    DumpMem offset iArgs, 8, "2.Argument"

    mov     eax,iArgs   ;iArgs enthält Parameteranzahl +1
    dec     eax

    DumpMem offset buffer1, 32, "buffer1 vor Codewandlung"

    mov     iArgszaehler,eax  ;Schleifenzaehler, Inhalt 1 oder 2
codewandlung_parameter:
    mov     ebx,offset buffer2         ;Startadresse des Arrays nach EAX
    cmp     iArgszaehler,1
    jne     T1
    mov     ebx,offset buffer1         ;Startadresse des Arrays nach EAX

T1: mov     esi,ebx         ;von = nach
T2: mov     al,[esi]        ;fertig konvertiert ?
    cmp     al,0
    je      T3              ;fertig konvertiert !
    call    todos           ;wandelt äöüÄÖÜß von Windwos- in DOS-ASCII
    mov     [esi],al
    inc     esi
    jmp     T2

T3: dec     iArgszaehler    ;Stringende erkannt
    cmp     iArgszaehler,0
    jne     codewandlung_parameter  ;zur Codewandlung des nächsten Parameters

    DumpMem offset umlaute, 32, "Umlauttabelle nach Codewandlung"
    DumpMem offset buffer1, 32, "buffer1 nach Codewandlung"

;Codewandlung ist beendet. Der erste Parameter (der Text) wird angezeigt
    print   offset buffer1
;Falls nur ein Aufrufparameter angegeben war, nun Ende mit Errorlevel 0

    DumpMem offset iArgs, 8, "Argumente plus 1"

    cmp     iArgs,3
    je      antw5           ;Sprung, wenn key-list angegeben war
    print   offset crlf     ;Ein Zeilenvorschub
    mov     eax,0           ;Es war nur ein Parameter!
    ret

antw5:
    mov     ebx, offset buffer2     ;Startadresse der Antwortmoeglichkeiten

    write   ">"                     ;> als Prompt. Das print-macro
;   invoke StdOut,offset prompt     ;verträgt print ">" nicht. deshalb write
;                                      ;oder direktes invoke StdOut

    call    ret_key         ;wait for a keystroke and return its scancode in EAX

    mov     ecx,1           ;Errorlevel 1 fuer 1. Zeichen der key-list
antw6:
    cmp     al,cr
    je      antw5           ;cr wird ignoriert

;   and     al,5fh          ;al enth. das Zeichen in Grossschrift

    mov     ah,[ebx]        ;Ende des Vergleichsstring erreicht?
    cmp     ah,0
    je      antw5           ;ja, aber Eingabe war unpassend

    cmp     [ebx],al        ;passt die Eingabe?
    je      antw9           ;ja

    inc     ebx             ;nein. Dann Einstellen auf nächsten Vergleich
    inc     ecx
    jmp     antw6

antw9:
    push    ecx             ;ecx enthaelt nun den Errorlevel
    print   offset crlf     ;Zeilenvorschub
    pop     eax             ;eax enthaelt nun den Errorlevel
    ret
main endp

; Unterprogramme
; ---------------------------------------------------
;Unterprogramm Codewandlung Umlaut von Windows- in DOS-Codierung (ASCII)
;Das zu untersuchende Zeichen steht in al
; -----
todos proc
        mov     cx,14           ;Tabelle Umlaute ist 2*14 Zeichen lang
        mov     ebx, offset umlaute

todos1: cmp     byte    ptr[ebx],al
        je      todos9
        inc     ebx             ;adressieren naechsten Tabellenplatz
        inc     ebx

        dec     cx
        jnz     todos1

        ret                     ;Unterprogrammende ohne Wandlung

todos9: inc     ebx             ;Codewandlung des Zeichen und
        mov     byte ptr al,[ebx]        ;Unterprogrammende mit Wandlung
        ret
todos endp

; -----
; Unterprogramm Hilfstext ausgeben
; -----
help proc                       ;Parameteranzahl muss 1 oder 2 sein
        print   offset  cParamFehler    ;falsche Parameteranzahl
        mov     eax,99                  ;Errorlevel 99 und Schluss!
        ret
help endp

end start                       ; Tell MASM where the program ends


Interpretation des Quellprogramms

Konstanten: Die Symbolisierungen für die deutschen Umlaute in DOS-ASCII verbessern die Lesbarkeit des Quellprogramms.

buffer1, buffer2: Die beiden Pufferbereiche zur Aufnahme der Kommandozeilenparameter sind bei Programmstart mit hexadezimal 00 vorbelegt. Dadurch wird ein Markierung des Stringendes sichergestellt.

Hilfe: Der zur Anzeige bestimmte Text wurde angepasst.

Umlauttabelle: Das Programm wurde unter Windows 10 Version 1809 entwickelt. Dabei zeugte sich, dass zwei Umschlüsselungen erforderlich sind. Beim Programmaufruf vom Konsolfenster (CMD) aus, wirkt die Umschlüsselung Windows auf MS-DOS. Beim Aufruf von einer mit dem Editor NOTEPAD.EXE erstellten Stapeldatei (.BAT) beinhaltet die Stapeldatei den erwarteten Code gemäß Windows 1252. Bei Ablauf kommt jedoch ein davon abweichender Code für die Umlaute im Frage-Programm an. Die Entdeckung dieser Eigenschaft hat mir bei der Programmentwicklung erhebliche Schwierigkeiten gebracht. Deshalb sind die verschiedenen Aufrufe von DumpMem als Überreste von der Programmentwicklung im Programmtext enthalten. Sie alle werden durch zwei Zeilen DBGWIN_DEBUG_ON = 0 und DBGWIN_EXT_INFO = 0 bei der Assemblierung als Kommentare behandelt.

Bei start unter .code wird nun vor der Anforderung zur Tastenbetätigung beim Programmende der Inhalt von eax gesichert und nach der Eingabe wieder hervorgeholt. Grund: Vom Unterprogramm main wird der Errorlevel dem Hauptprogramm in eax übergeben. Für den Ablauf des Frageprogramms unter einer Stapeldatei ist die Tastenbestätigung eher störend. Sie ist deshalb auskommentiert.

invoke GetCL,1,ADDR buffer1
cmp eax, TRUE:
TRUE ist mit 1 belegt, FALSE ist mit 0 belegt. Die hier erfolgte Fehlerauswertung betrachtet die Rückgabewerte 0, 2 und 3 als Fehler.

Mit der in iArgszaehler eingestellten Anzahl der Aufrufparameter wird bewirkt, dass nur der bzw. die vorhandenen Aufrufparameter eine Codewandlung erfahren.

;Codewandlung ist beendet: Die Programmzeilen print offset buffer1 hinter diesem Kommentar zeigt den Prompt auf dem Bildschirm an.

Falls nur ein Aufrufparameter angegeben war, wird ein Zeilenvorschub ausgegeben und das Unterprogramm wird mit Errorlevel 0 beendet.

antw5 bis zum ret hinter antw6: Diese Zeilen sind an 32-bit-Programmierung angepasste Zeilen aus dem oben genannten 16-bit-Programm. Statt print ">" steht write ">". Das print-Makro kommt mit dem > nicht klar. Alternativ kann der nachfolgende auskommentierte invoke-Aufruf anstelle von write verwendet werden.

Der auskommentierte Befehl and al,5fh würde bei seiner Aktivierung eine Wandlung der eingetippten Buchstaben a bis z in Großschrift durchführen. Ob man es macht, ist Entscheidungssache. Wenn man es ordentlich machen möchte, müsste man sich auch um äöü kümmern.


Eine individuelle Überschrift für das Konsolfenster
Mit der Zeile
start:  SetConsoleCaption       "Frage ergibt Returncode"
kann man dem Konsolfenster die angegebene Überschrift geben:
MASM32, individuelle Überschrift für das Konsolfenster

Dazu sind jedoch weitere Schritte erforderlich. Welche es sind, wird unter SetConsoleCaption in der Hilfestellung des MASM32-SDK beschrieben. Hier ist der Weg zur Beschreibung von der MASM32-SDK Entwicklungsumgebung aus:

  • Reiter „Help”
  • MASM32 Macro High Level Reference
  • Console Mode Macros unter „Macro Categories”
  • SetConsoleCaption

Das Frageprogramm mit PureBasic
Für einen weiteren Vergleich habe ich ein funktionsgleiches Programm in der Programmiersprache Purebasic erstellt. Das daraus ertellte ausführbare 32-bit-Programm Frage.exe kommt auf 12800 Bytes Größe. Dessen Quellprogramm ist allerdings kürzer und deutlich übersichtlicher als das in Assembler geschriebene Quellprogramm:
OpenConsole("Frage ergibt Returncode")

;Zähle Parameter
AnzahlParameter.i = CountProgramParameters()
;Add all command line parameters to a linked list
Global NewList Parameter.s()
;PrintN("Anzahl Aufrufparameter: " + StrU(AnzahlParameter))

Select AnzahlParameter
  Case 1
    AddElement(Parameter()) : a$=ProgramParameter() : PrintN (a$)
    ExitCode=0      ;Errorlevel 0 wenn Aufruf mit nur einem Parameter

  Case 2
    AddElement(Parameter()) : prompt$=ProgramParameter() : Print(prompt$ + "> ")
    AddElement(Parameter()) : antworten$=ProgramParameter()
    ;PrintN ("ExitCode=" + StrU(ExitCode))

    Repeat      ;Vergleich der Eingabe ohne Beachtung der Groß-/Kleinschreibung
      KeyPressed$ = Inkey()
        If KeyPressed$ <> ""
          Position = FindString(antworten$, KeyPressed$,1,#PB_String_NoCase)
          Debug position
        Else
          Delay(100) ; Wir verbrauchen nicht die gesamte
                     ; CPU-Leistung, da wir in einem Multitasking-OS
		     ; laufen
        EndIf

        If KeyPressed$ = Chr(27)  ;Abbruch mit ESC-Taste wird ermoeglicht
          Position=99
        EndIf

    Until Position<>0      ;Bis gültige Eingabe oder ESC-Taste
    ExitCode=Position      ;Errorlevel 99 kennzeichnet Fehler oder Abbruch

  Default
    PrintN ("Aufruf:   FRAGE.exe prompt [key-List]")
    PrintN ("")
    PrintN ("Beispiel: frage.exe " + Chr(34) + "Weitermachen (jn)" + Chr(34) + " jn")
    PrintN ("Ohne key-List kann mit einer beliebigen Taste geantwortet werden")
    PrintN ("Prompt ggf. in Gänsefüßchen!")
    ExitCode=99      ;Errorlevel 99 kennzeichnet Fehler
EndSelect

;PrintN ("ExitCode=" + StrU(ExitCode))
PrintN("")
PrintN("Press any key To Continue ...")
Input()
End ExitCode

Als Reste von der Programmentwicklung enthält das Quellprogramm an zwei Stellen auskommentierte Anzeigebefehle für den erzeugten Rückgabecode:
;PrintN ("ExitCode=" + StrU(ExitCode))

Weiterhin ist der Befehl

Debug position
vorhanden. Dieser ruft eine eingebaute Testhilfe auf. Bei der Compilierung in das ausführbaren Program Frage.exe wird dieser Befehl wie ein Kommentar behandelt. Somit verlängert er das ausführbare Programm nicht.

Laut der Produktbeschreibung für PureBasic soll der erzeugte ausführbare Programmcode verhältnismäßig kompakt sein. In diesem Fall ist er jedoch gut viermal umfangreicher als sein mit purem Assembler erzeugtes Gegenstück.

Letztes Upload: 24.03.2023 um 11:35:13 • Impressum und Datenschutzerklärung