DOS API Aufrufe
« | 24 Jul 2022 | »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-Routinen 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 durchfü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.