#285 – Tastatureingaben unter DOS 3.30

„Gott sei Dank!“ Ich kann die Rufe von einigen von euch nur erahnen, aber ich könnte mir vorstellen, dass ihr verdammt froh seid, dass wir beim letzten Mal das Thema „adb 8088-Computer“ endlich abgeschlossen haben. Aber freut euch nicht zu früh – heute wird es noch langweiliger! 😛

Keine Panik – ich werde versuchen, das zugegeben etwas trockene Thema halbwegs interessant rüber zu bringen. Drückt mir die Daumen! 😉 Also, wo waren wir? Ach ja – beim letzten Mal hatte ich ja gegen Ende des Beitrags ein kleines Batchskript erstellt, mit welchem sich jedes auf der Festplatte des adb-PCs installierte Spiel starten lassen soll:

Klingt gut, wo ist das Problem? Tja, das ist schnell erklärt: Das Skript läuft nicht! 😀 Doch woran liegt das? Nun, für die Tastaturabfrage – also welcher Buchstabe vom Benutzer eingegeben wurde – wird die „CHOICE.COM“-Bibliothek verwendet. Das wäre erst mal kein Problem, doch leider gibt es den Choice-Befehl erst seit DOS 6.0 und auf unserem Rechner läuft DOS 3.30!

In der Vergangenheit (also z.B. in Artikel 239 beim DTK Tech-1260 oder Artikel 258 beim Sysline SLT450) hatten wir das Problem auch schon und haben uns immer damit beholfen, die CHOICE.COM von einer Installationsdiskette einer moderneren DOS-Version (6.0) einfach in das Systemverzeichnis von MS-DOS 5.0 zu kopieren. Beim adb 8088 Computer funktioniert diese Lösung leider nicht, denn scheinbar wird mindestens MS DOS 4.0 zur Ausführung des Programms benötigt. Und jetzt? 🙁

Ich befürchte, wir kommen nicht drum herum, uns selbst etwas zu basteln. Letztendlich bräuchten wir ja „nur“ eine Möglichkeit, um abfragen zu können, welche Taste vom Benutzer gedrückt wurde. Die Frage, die wir uns dabei stellen müssen, ist: Wie werden Tastaturuntergaben unter DOS und DOS-ähnlichen Systemen eigentlich generell eingelesen? Wie fast immer in der Informatik gibt es mehrere Möglichkeiten.

Wer schon mal etwas mit der „Eingabeaufforderung“ (CMD) unter Windows zu tun hatte und vielleicht sogar schon Batch-Skripte z.B. zum Automatisieren von kleineren Aufgaben geschrieben hat, wird wissen, dass man recht einfach über den SET-Befehl mit der Option „p“ Benutzereingaben in Variablen speichern kann:

Zur Laufzeit des Skripts wird dann auf eine Benutzereingabe gewartet. Der eingegebene Wert kann in einer Variable gespeichert und im Skript weiterverarbeitet werden:

Schön und gut, aber leider hilft uns das für unser Problem nichts, denn unter MS-DOS-basierten Betriebssystemen bietet der SET-Befehl leider noch nicht die Möglichkeit, Benutzereingaben einzulesen. Den „erweiterten“ SET-Befehl (mit Option „p“) gibt es erst seit Windows 2000.

Fun Fact: Für ältere Windows-Versionen (z.B. 95, 98 und NT) gäbe es eine verrückte Lösung, bei welcher der Eingabewert der Ja/Nein-Abfrage des Formatbefehls über ein zweites Skript gelesen und als Wert für die Entscheidung im ersten Skript verwendet wird – mega abgefahren und aus heutiger Sicht maximal nutzlos! 😉

Natürlich bin ich nicht der einzige, der sich über das Thema Gedanken gemacht hat. In diversen Softwaresammlungen finden sich kleine Tools, wie z.B. „GETKEY.EXE“. Das Programm tut prinzipiell genau das, was wir wollen, doch leider wird die vom Benutzer eingegebene, bzw. eingelesene Taste in einer Systemvariable gespeichert. Das ist zwar eine pfiffige Lösung, meiner Meinung nach aber auch recht fehleranfällig. Außerdem ist es bescheuert, wenn man dann bei jedem PC, bzw. bei jeder MS-DOS-Neuinstallation immer wieder eine dedizierte Umgebungsvariable definieren muss.

Die Frage bleibt also – wie können wir Tastatureingaben unter DOS 3.30 einlesen? Da es keinen geeigneten DOS-Befehl gibt, haben wir auch keine Möglichkeit, einfach ein entsprechendes Programm in einer Stapelverarbeitungsdatei (.BAT) aufzurufen. Das Beste wäre es wohl, wenn wir uns selbst ein kleines Programm, schreiben könnten, was genau das tut. Problematisch ist nur, dass uns keine höhere Programmiersprache zur Verfügung steht. Im Endeffekt werden wir wohl oder übel auf x86-Assembler zurückgreifen müssen. Puh, nicht gerade „Benutzerfreundlich“! 😀

Fangen wir von vorne an – als erstes müssen wir uns entscheiden, welche Art von „ausführbarem Programm“ wir erstellen möchten. Unter DOS steht uns neben dem bekannten Dateityp .EXE auch die Dateiendung .COM für einfache Programme zur Verfügung. Während EXE-Dateien nach einem bestimmten Format (Header, Ladeanweisungen, Sektionen, etc.) aufgebaut sein müssen, handelt es sich bei COM-Dateien um einfache, unstrukturierte binäre Maschinenbefehle, welche eins zu eins in den Arbeitsspeicher (an die Adresse ‚100’h, also nach den ersten 256 Bytes) geladen und ausgeführt werden. Hier die wesentlichen Unterschiede zwischen den beiden Formaten:

Fun Fact: Das COM-Format ist vielleicht das einfachste Format ausführbarer Dateien überhaupt. Es enthält keine Metadaten – nur Code und Daten. Das nenne ich mal effizient! 😉

Die ganzen technischen Daten sollen uns jetzt erst mal nicht weiter interessieren. Wichtig ist nur, dass ein COM-Programm max. knapp 64 kB groß sein kann und beim Aufruf kein neuer Prozess gestartet, sondern die Maschinenbefehle einfach direkt im Speicher ausgeführt werden. Da ich das Tastaturabfrageprogramm so einfach (und klein) wie möglich halten möchte, sollten wir versuchen, so ein COM-Programm zu erstellen. Soviel zum Plan. Und wie geht das jetzt?

Fun Fact: Heutzutage kommen im Windows-Umfeld fast ausschließlich EXE-Dateien zum Einsatz. Zu DOS-Zeiten wurden viele Befehle (alle, die sich nicht in der COMMAND.COM befanden) über eigene COM-Programme entwickelt. Dazu gehören so Dinge wie z.B. CHKDSK.COM oder FORMAT.COM.

Um ein Assemblerprogramm überhaupt kompilieren, bzw. assemblieren zu können, benötigen wir einen „Assembler“. Ich habe mich für „NASM“ entschieden. Das Tool läuft auch unter DOS (wobei das prinzipiell egal wäre) und angeblich lassen sich damit auch COM-Programme erstellen.

Soweit so gut – jetzt müssen wir ja „nur noch“ das Programm schreiben. Kann jemand von euch x86-Assembler? 😛 Ich leider nicht und dementsprechend müssen wir uns Schritt für Schritt herantasten. Mal überlegen – was wissen wir bereits? In den ersten 256 Byte des Arbeitsspeichers (Offset ‚100’h) befindet sich der sog. „Programmsegmentpräfix“. Dieser vom Betriebssystem DOS zur Verfügung gestellte Bereich beinhaltet den aktuellen Status des geladenen Programms. Dementsprechend müssen wir dem Programm mitteilen, dass der Adresszähler auf Offset ‚100’h gestellt werden soll:

Damit sollte unser erstes Programm bereits lauffähig sein. Über den Befehl „nasm -f bin -o first.com first.asm“ können wir es assemblieren und in das binäre COM-Format umwandeln. Über „first“ (was eigentlich für first.com steht) lässt sich das Programm anschließend ausführen. Immerhin – die DOSBox, welche ich zum Test des Programms verwende, ist nicht abgestürzt und das Programm tut (wie erwartet) nichts – ein erster Erfolg! 🙂

Und wie bekommen wir jetzt einen Tastendruck eingelesen? DOS stellt zur Kommunikation mit der Hardware sog. „Interrupts“ (Unterprogramme) zur Verfügung. Gerade der Interrupt „21h“ ist sehr interessant, denn über diesen können mehr als 100 Funktionen wie z.B. das Lesen von Dateien, das Wechseln von Verzeichnissen oder eben auch das Einlesen/Ausgeben von Zeichen erledigt werden.

Fun Fact: Heutzutage würde man diese Interrupts vermutlich „API“ nennen! 😉

Wie man es vom Aufruf eines Unterprogramms aus einer Hochsprache kennt, müssen vor dem Aufruf entsprechende Parameter in bestimmten Registern gesetzt werden. Letztendlich wollen wir den Tastaturpuffer leeren (Funktion „C“), anschließend ein Zeichen einlesen und es danach sofort wieder ausgeben (Funktion „1“). Damit das klappt, müssen wir das Register A (den Übergabebereich) mit dem Wert „0C01“ füllen und anschließend das Unterprogramm via „int 21h“ aufrufen.

Fun Fact: Theoretisch könnte man das Register A auch einzeln über die Befehle „mov ah,0Ch“ (die ersten/obersten acht Bits) und „mov al,01h“ (die letzten/untersten acht Bits) befüllen. Mit dem „mov ax,0C01h“-Befehl erledigen wir das gleich in einem Aufwasch! 🙂

Funktioniert! Das von uns eingegeben Zeichen wird eingelesen und sofort wieder ausgegeben:

Doch das war noch nicht alles. Durch den Unterprogrammaufruf wird das eingelesene Zeichen als dezimaler Wert im Register A in den unteren acht Bits (AL) zurückgegeben. Damit wir den Wert des Zeichens auch außerhalb des COM-Programms in einer Batchdatei verarbeiten können, müssen wir das Programm über den Befehl „mov ah, 4Ch“ und einem anschließenden „int 21h“ sauber beenden. Letztendlich wird dadurch der Return-Code unserer READK.COM auf den Zeichenwert gesetzt.

Auf den ersten Blick sieht es nicht so aus, als hätte sich beim Aufruf des Programms etwas geändert:

Durch unsere Anpassung wurde allerdings die ERRORLEVEL-Variable mit dem dezimalen Zeichenwert bestückt. Jetzt können wir in unserem Menü-Batchskript anstatt der „CHOICE-Abfrage“ das „READK.COM“-Programm einbinden und anschließend den dezimalen Wert des eingelesenen Zeichens mit Hilfe der ERRORLEVEL-Variable überprüfen und entsprechend darauf reagieren.

Not so fun Fact: Leider ist die Handhabung der ERRORLEVEL-Variable nicht ganz trivial, denn entgegen der klaren Schreibweise „IF ERRORLEVEL n goto xyz“ bedeutet die Abfrage im Klartext „IF Return-Code >= n“. So müssen wir quasi „doppelt prüfen“, um sicherzustellen, dass z.B. auch nur bei Gedrückter „a“-Taste, die Auswahl „a“ getätigt wird. Unschön, aber so tickt das System leider! 🙂

Gibt man einen richtigen Buchstaben ein, wird nun das entsprechende Spiel gestartet. Das Ganze funktioniert in meiner DOSBox natürlich nicht, da die Spieldateien nicht in den entsprechenden Verzeichnissen liegen. Das ist aber auch egal, denn uns ging es ja nur um die Erstellung eines Menüs, bzw. um die Abfrage der Tastatureingabe! 😉

Gibt man einen „unbekannten“ (also nicht definierten) Buchstaben ein, wird das Menü beendet:

Die dezimalen Werte der Tasten entsprechen denen der Codepage-850, also des von MS-DOS verwendeten Zeichensatzes zur Darstellung von Zeichen. Das kleine „a“ hat z.B. den Wert „61“ hexadezimal, was einer „97“ im Dezimalsystem bedeutet.

Not so fun Fact: Ich gebe es zu – es ist nicht gerade komfortabel, immer die dezimalen Werte für die Buchstaben in einem Batchskript via ERRORLEVEL abzufragen, aber eine etwas komfortablere Lösung lässt sich mit so einfachen Mitteln leider nicht realisieren. Sorry! 😛

Um unser READK-Programm noch etwas weiter zu verbessern, können wir anstelle des „0C01“-Befehls auch einfach einen „0C07“ ausführen. Dieser verzichtet auf die Ausgabe des eingegebenen Zeichens. So sieht das Menü, bzw. die Ausgabe davon noch etwas schöner aus! 🙂

Um unser selbstgeschriebenes – letztendlich „READKB“ getauftes – Programm nun z.B. auf dem adb PC nutzen zu können, macht es Sinn, die Datei in ein Verzeichnis auf der Festplatte zu kopieren…

…und in der AUTOEXEC.BAT die Path-Variable entsprechend auf das Verzeichnis, in dem sich das Programm befindet, zu setzen. So lässt sich unserer READKB.COM von überall (also aus jedem Verzeichnis auf der Festplatte heraus) aufrufen! 🙂

Jetzt aber: Ende gut, alles gut. Damit ist unser Spiele-Auswahl-Menü auch unter MS-DOS 3.30 einsatzbereit – und das auch ganz ohne CHOICE-Befehl! 😉

Ist es nicht abgefahren, was man mit einer Hand voll Assembler-Befehlen alles so zaubern kann? 🙂 Zugegeben – die Sprache ist alles andere als zugänglich, aber wenn man es dann tatsächlich schafft, etwas damit zu realisieren, fühlt es sich verdammt gut an! 😀

Fun Fact: Wer das Programm nicht selbst entwickeln möchte kann es hier samt Beispielskript mit Hinweisen zur Nutzung herunterladen! 🙂

In diesem Sinne – bis die Tage, ciao!