DOS API Aufrufe

Heute bringen die meisten Programmiersprachen “alles” mit, was notwendig ist um alle möglichen Formen von Programmen, UIs und Spielen zu bauen. Doch zu DOS Zeiten, war man auf die C Standardbibliothek und einfache Text Ein- und Ausgaben beschränkt.

Den Spielraum konnte man sich mit direkten Aufrufen von Interrupt Service-Rountinen erweitern, darunter vor allem der int 21h mit allen DOS Funktionen.


Leider war das alles andere als “standardisiert” und trotzdem verfolgten die Sprachen ähnlichen Ansätze.

Integration von Inline-Assembler

C und Pascal Compiler erlaubten im normalen Programmfluss Assembler Codes einzuschleusen. Man öffnete einen asm Block und schrieb dort die Intel Syntax der X86 Anweisungen, oder sogar direkt den Bytecode des Prozessors.

Doch diese Non-Standard Erweiterung änderte sich stets und vor allem die Interaktion zwischen Variablen und Konstanten der eigentlichen Sprache war unterschiedlich. Ich weiß bis heute nicht auswendig, wo man nun Variablen in eckige Klammen setzen muss und wo nicht, oder ob Stack-Variablen und globale anders behandelt werden mussten.

Und wenn dann noch Turbo C den Code an den externen Turbo Assembler auslagerte, war fraglich, ob der überhaupt installiert war.

Aufruf von Interrupts über Standard-Funktionen

Ein Alternative musste her, und diese war eine spezielle Funktion, die die Assembler-Magie im Hintergrund druchführte. Man befüllte eine Struktur deren Member die CPU-Register abbildeten und rief ebendiese Funktion auf, die DOS oder andere Interrupt nutzte und das Ergebnis war ein neuer Satz an Register-Variablen in der zurückgegebenen Struktur.

In den nachfolgenden Beispielen nutze ich die DOS Funktion ah=0x30, die die DOS-Version im Register AX zurückgibt, und zwar AL mit der Hauptversion und AH mit der Nebenversion:

Turbo C, OpenWatcom

Im dos.h Header befindet sich die Funktion intdos(in_regs, out_regs) um DOS Funktionen mit union REGS Parametern aufzurufen.
Zusätzlich existiert noch int86(int_nr, in_regs, out_regs) um jeden beliebigen Softwareinterrupt aufzurufen.

 1#include <dos.h>
 2
 3void getDOSversion(int* major, int* minor)
 4{
 5  union REGS in_regs, out_regs;
 6  in_regs.h.ah = 0x30;
 7  in_regs.h.al = 0x00;
 8  /*in_regs.x.ax = 0x3000;*/
 9  intdos(&in_regs, &out_regs);
10  *major = (int)out_regs.h.al;
11  *minor = (int)out_regs.h.ah;
12}
13
14int main()
15{
16  int major, minor;
17  getDOSversion(&major, &minor);
18  printf("DOS v%d.%d\n", major, minor);
19  return 0;
20}

Turbo Pascal

Turbo Pascal stellte in seinem DOS Modul die Funktion MsDos(regs) für DOS Funktionen bereit und mit Intr(intNo, regs) kann man jeden beliebigen Interrupt nutzen. Hier gibt es nur einen Registers-Parameter, der gleichzeitig Input ist und danach zum Output wird.

 1PROGRAM dosver;
 2USES dos;
 3
 4PROCEDURE getDOSversion(var major: WORD; var minor: WORD);
 5VAR
 6  regs: REGISTERS;
 7BEGIN
 8  regs.ah := $30;
 9  regs.al := $00;
10  {regs.ax := $3000;}
11  MsDos(regs);
12  major := regs.al;
13  minor := regs.ah;
14END;
15
16VAR
17  maj, min: WORD;
18  text: STRING;
19BEGIN
20  getDOSversion(maj, min);
21  write('DOS v');
22  str(maj, text);
23  write(text);
24  write('.');
25  str(min, text);
26  writeln(text);
27END.

QuickBasic DOS Interrupt Aufruf

Startet man QuickBasic (4.5+) mit QB.EXE /L, stehen Zusatzbibliotheken zur Verfügung, die man über das Metakommando
REM $INCLUDE: 'file' einbinden kann.
Und QB.BI definiert die Funktion INTERRUPT und den RegType Typ, der Registerzustände an die Funktion leiten soll, die wiederum einen Softwareinterrupt damit aufruft. So kann man jeden Interrupt aufrufen, und natürlich auch 21h.

 1REM $INCLUDE: 'QB.BI'
 2DECLARE SUB getDOSversion (major AS INTEGER, minor AS INTEGER)
 3
 4DIM major AS INTEGER
 5DIM minor AS INTEGER
 6
 7getDOSversion major, minor
 8
 9PRINT "DOS v";
10PRINT USING "#"; major;
11PRINT ".";
12PRINT USING "#"; minor
13
14SUB getDOSversion (major AS INTEGER, minor AS INTEGER)
15  DIM inreg AS RegType
16  DIM outreg AS RegType
17  inreg.ax = &H3000
18  CALL INTERRUPT(&H21, inreg, outreg)
19  major = outreg.ax AND &HFF
20  minor = CINT(outreg.ax / 256)
21END SUB

Der mit DOS mitgelieferte QBASIC.EXE Interpreter unterstützt CALL INTERRUPT nicht. Theoretisch gäbe es aber die Möglichkeit mit POKE Assembler-Bytes in den Speicher zu schreiben und dann per
CALL ABSOLUTE die Funktion aufzurufen … aber soviel DOS-Hackerei tut sich heute keiner mehr an.

Fazit

Der große Nachteil dieser direkten Interrupt-Aufrufe liegt in der Unmöglichkeit der Portierbarkeit. Denn die Register sehen ja auf jeder CPU anders aus.
Definierte APIs sind die Lösung und davon haben wir heute nur 2 große Vertreter: die WinAPI und POSIX parallel zur C-stdlib.

Tja, deshalb brauche ich eine Zeitmaschine, um mein heutiges Wissen 25 Jahre in die Vergangenheit zu senden. Denn oft habe ich mühsam ganze Programme in Assembler verfasst, nur weil ich ein paar Interrupt bzw. DOS Funktionen aufrufen wollte. Dass man das auch einfacher tun kann, lernte ich erst, als ich DOS nicht mehr wirklich brauchte und auch nicht mehr damit programmierte.

Aber … Wissen schadet nicht.
Im Gegenteil, es ist interessant zu sehen, wie sehr sich die 3 “Hauptsprachen” C, Pascal und Basic ähneln was Interrupts unter DOS anbelangt.


Meine Dokus über:
 
Weitere externe Links zu:
Alle extern verlinkten Webseiten stehen nicht in Zusammenhang mit opengate.at.
Für deren Inhalt wird keine Haftung übernommen.



Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!