Wem geht es nicht ab und an so: Man hat ein schönes Programm, das ebenso schöne Grafiken im Vektorformat erzeugt und daher ewig langsam im Ausdruck ist?
Oder man programmiert für seine serielle Schnittstelle eine neue, extrem schnelle Übertragungsroutine, die aber nicht ganz richtig funktioniert. Leider braucht man zum Testen einen zweiten Rechner, und der Freund ist ganz schön sauer ob der für ihn nicht nutzbaren Zeit.
Wie schön wäre es, könnte man den Drucker oder die serielle Schnittstelle durch die Tastatur oder eine Datei oder ... ersetzen. Der erfahrene ST-COMPUTER-Leser wird sagen : Dafür gibts doch die GEM-DOS-Funktion $4b (Fforce). Stimmt ja auch, aber...
An dieser Funktion störten mich schon lange drei Mängel:
Was lag näher, als sich den Assembler zu nehmen und sich der Sache anzunehmen? Aber zunächst einmal zu den bekannten Sachen, sprich Fforce.
Diese GEMDOS-Funktion bewirkt wie bekannt das Umlenken von Geräten in andere Geräte oder Dateien. Ein kurzes Beispiel in C:
#define CON_IN 0
#define CON_OUT 1
#define AUX 2
#define PRN 3
/* womit wir auch schon alle behandelbaren Geräte erwähnt hätten */
main()
{
int han;
han=Fcreate("test.dat",0);
Fforce(PRN,han);
Cprnout('a');
Fclose(han);
return 0;
}
Es passiert im Programm nichts weiter, als daß die Datei TEST.DAT geöffnet und der Drucker dorthin umgelenkt wird. Wenn nun ein Zeichen auf den Drucker ausgegeben wird, landet es nicht auf diesem, sondern in unserer Datei, und man kann in der Datei nach Beenden des Programmes den Buchstaben ‘a’ bewundern. So weit, so gut. Jetzt weiß ich also, wie ich in meinem eigenen Programm den Drucker „verbiege“. Das hilft mir aber beim Grafikprogramm nicht weiter. Schließlich habe ich das nicht selbst geschrieben und der Hersteller wird wohl auch kaum den Quelltext verschicken. Die Hilfe ist einfach: Diese „Geräteverbiegung“ kann weitervererbt werden. Ersetzt man also das
Cprnout('a')
im Beispiel durch z.B.
Pexec("graphik.prg", command, "",0)
so wird GRAPHIK.PRG gestartet und die Grafik, die sonst auf dem Drucker gelandet wäre, liegt dann in unserer Datei, wo sie ja gut aufgehoben ist. Nachdem unser Programm beendet wurde, ist wieder alles in bester Ordnung, das Betriebssystem sorgt selbst dafür, daß der Drucker druckt und nicht mehr „dateit“. Und wie drucke ich dann das ganze aus? Ganz einfach: vom Desktop aus die Datei zweimal anklicken und die Frage ansehen drucken Abbruch mit „drucken“ beantworten. Dann geht die Post ab, und der Drucker wirft die schönste Grafik aufs Papier. Damit hätten wir also die einfache Lösung kurz nochmal abgeschnitten. Aber wie gesagt, das funktioniert sofort nicht mehr, wenn unser Grafikprogramm die Grafik mittels BIOS ausgibt. Dann müssen wir nach wie vor eine halbe Stunde Rechenzeit in Kauf nehmen. Und für andere Anwendungen bestehen immer noch die oben genannten Mängel. Und hier setzt mein BIOS.PRG an.
Was eigentlich das Programm bewirken soll, ist ja nun klar. Und weil ich Fforce nicht für so schlecht halte, sollte die Wirkung auch ähnlich sein. Ich habe also extra für dieses kleine Programm eine neue BIOS-Funktion implementiert, die bei mir Bforce heißt und die Dateiumlenkung auf BIOS-Ebene bewirkt und im Gegensatz zu Fforce auch noch zwischen Ein- und Ausgabe unterscheidet. Folglich gibt es drei Parameter:
Um das Kapitel der Benutzung gleich ganz abzuhaken, nun noch das Schema des Aufrufs der Funktion in C und Assembler:
#define Bforce(a,b,c)
bios(132,a,b,c)
Bforce(device,newdevice,in_out);
move.w in_out,-(sp)
move.w newdev,-(sp)
move.w device,-(sp)
move.w #132,-(sp)
trap #13
addq.l #8,sp
Die Parameter haben dabei folgende Bedeutung:
in_out: 0 - Eingabe umlenken 1 - Ausgabe umlenken
newdev: BIOS- bzw. Datei-Handle des Umlenkungszieles
device: BIOS-Handle des umzuleitenden Kanales. Ist newdev größer oder gleich sechs, handelt es sich um ein Datei-Handle, sonst um eines der folgenden BIOS-Handles.
0 Drucker
1 serielle Schnittstelle
2 Konsole
3 MIDI
4 IKBD
5 RAW-Konsole
Datei-Handles sind automatisch größer als sechs, darüber braucht man sich also keine Sorgen zu machen. Wen jetzt die technischen Feinheiten nicht interessieren (sog. Nutzer), der kann aufhören zu lesen und anfangen, das Listing abzutippen. Allerdings sollte er sich die Warnungen am Ende des Textes noch durchlesen.
Dem geehrten Leser stehen natürlich noch alle anderen Wege offen. Aber so funktioniert es in etwa: Mit einer Funktion namens install klinke ich mich in den BIOS-Vektor (XBRA) und fange erstmal alle BIOS-Aufrufe ab. Danach werden alle mich interessierenden Funktionen ausgefiltert, als da wären
Name | Nummer |
---|---|
1. Bconstat | 0 |
2. Bconin | 2 |
3. Bconout | 3 |
4. Bcostat | 8 |
5. Bforce | 132 |
Das sind alle BIOS-Funktionen, die mit Ein-und Ausgabe auf Geräte zu tun haben. 1. und 4. sind Erkundigungsfunktionen, die den Status von Geräten feststellen, 2. und 3. sind die Ein- und Ausgabefunktionen, und 5. schließlich ist hier gerade im Entstehen. Zuerst beschäftigen wir uns mit 5.
Wird diese Funktion auf gerufen, wird anhand von device in die Ein- oder Ausgabetabelle der Wert von newdev eingetragen. device wird dabei als Offset in der Tabelle verwendet.
Wird nun allerdings 1., 2., 3. oder 4. erkannt, liegt als Funktionsparameter auch immer die device-Nummer auf dem Stack. Diese wird vom neuen BIOS-Handler als Offset in die Ein- oder Ausgabetabelle verwendet und die dort stehende Nummer anstelle des alten Wertes auf den Stack gelegt. Kurz gesagt, wird die Funktionsnummer ausgetauscht. Danach wird der BIOS-Aufruf weitergeleitet an den ursprünglichen BIOS-Handler, der dann ganz unbekümmert mit dem untergemogelten Gerät weitermacht, als wäre nichts passiert. Das Programm, das den Aufruf tätigt, bekommt auch nichts von der kleinen Manipulation mit.
So wie bis hier dargestellt, funktioniert das ganze aber nur für Umleitungen innerhalb der sechs BIOS-Kanäle. In Dateien kann man damit aber noch nichts schreiben. Da aber Datei-Handies gut zu erkennen sind (>=6), wird in diesem Fall wie folgt vorgegangen:
Für Ein- oder Ausgabefunktionen wird die in der Tabelle gefundene neue Gerätenummer als Datei-Handle erkannt und zur weiteren Bearbeitung das GEMDOS genutzt. Der zur Ausgabe vorgesehene Wert wird also in einen Ein-Byte-Puffer übertragen und mittels Fwrite in die ausgewählte Datei ausgegeben. Bei der Eingabe wird der einzulesende Wert mittels Fread in den Puffer eingelesen und danach in D0 übertragen, wo das BIOS alle Funktionswerte zurückgibt. Die Erkundigungsfunktionen sind in diesem Fall nur Dummy-Funktionen, da sie immer wahr zurückgeben. Es gibt also hier einen „Rückschritt“ vom BIOS zum GEMDOS, der allerdings nicht ganz problemlos funktioniert, da das GEMDOS auf das BIOS zurückgreift. Wenn nämlich ein BIOS-Aufruf des GEMDOS erfolgt, und dieses wiederum das GEMDOS konsultiert, werden die internen Speicher des GEMDOS gnadenlos überschrieben, und man kriegt das große Grübeln, wo plötzlich die ganzen Bomben herkommen. Aber man kann sich ja helfen. Die Lösung ist zwar nicht so ganz elegant, funktioniert aber. Es wird nämlich auch noch der GEMDOS-Vektor überwacht, und sobald eine Funktion (außer Pexec) angesprungen wird, ignoriert unsere BIOS Routine alle Umleitungen in Dateien, so daß das GEMDOS ungestört arbeiten kann. Dadurch ist zwar die Umleitung nicht mehr ganz so mächtig, aber dafür sicherer.
Das war’s im Prinzip auch schon. Als Zugabe ist noch ein kleines Programm dabei, das die BIOS-Routine resident im Speicher ablegt. Und zum Schluß nun noch die versprochenen Warnungen.
Als wichtigstes und allgemeinstes: am BIOS führt (fast) kein Weg vorbei. Eine Umlenkung von Kanälen wirkt sich also überall aus, selbst bei solchen Sachen wie der Fileselectbox! Also Vorsicht bei der Anwendung, insbesondere bei Umlenkung der Standardeingabe. Des weiteren ist zu beachten, daß nicht alle BIOS-Funktionen für alle Geräte implementiert sind. Die hier vorgestellte Routine kümmert sich um solche Feinheiten nicht, aber die Original-BIOS-Fehlermeldungen kommen zurück. Das gilt nicht bei der Umlenkung in Dateien. Man sollte also immer sehen, daß die Dateiarbeit reibungsfrei ablaufen kann. Des weiteren macht das Betriebssystem die Veränderung der Kanäle nicht selbständig wieder rückgängig, sondern der Nutzer muß das selbst machen [mit Bforce(device,device,in out)]. Sonst bleibt alles bestehen bis zum nächsten Reset. Die Funktionen install und exstall, sollte sie jemand in einem eigenen Programm nutzen wollen, müssen im Supervisormodus aufgerufen werden. Ich hoffe, ich habe nun alle Gefahren ertappt, die auf den Nutzer warten, wenn ich natürlich dafür auch keine Garantie übernehmen kann. Getestet wurden die Routinen übrigens auf einem STE mit 1 MByte.
GLOBAL install,exstall
bios EQU $B4 ;adr des bios-
; vektors
gemdos EQU $84 ;adr des gemdos-
; vektors
anfang: bra schluss
DC.B "XBRABFOR"
oldbios:DC.L 1
mybios:
movem.l D0-D1/A0-A1,-(SP)
lea 22(SP),A0 ;funktionsnum-
; mer auf superstack
move.w -6(A0),D0 ;Statusregister
btst #13,D0 ;alter Status
; == superviser?
bne.s aktiv ;nein->zu aktiv
move USP,A0 ;sonst usp
; laden
aktiv:
move.w (A0),D0 ;funktionsnum-
; mer laden
cmpi.w #132,D0 ;meine umlenk-
; funktion?
beq newnumb ;ja->
; neuumlenkung
cmpi.w #1,D0 ;nein, Bconin?
beq.s input ;ja ->
; inputfunktion
cmp.w #2,D0 ;Bconstat?
beq.s input ;ja
cmpi.w #3,D0 ;Bconout?
beq.s outputs ;ja
cmpi.w #8,D0 ;Bcostat?
beq.s outputs ;ja
back: ;eigene arbeit erledigt
movem.l (SP)+,D0-D1/A0-A1 ;weiter im
; bios
move.l oldbios(PC),-(SP)
rts
input:
move.w 2(A0),D0 ;device in dO
add.w D0,D0 ;2* wegen word
lea itab(PC),A1 ;inputtab in a1
cmpi.w #6,0(A1,D0.w) ;wert in tab<6?
bcs.s biosin
; ja->
; umlenkung bleibt
; im bios
; ab hier umlenkung in gemdos
tst.b isdos ;aufruf aus
; gemdos?
bne.s back ;ja, dann weiter
cmpi.w #1,(A0) ;Bconstat?
beq.s info
; ja, dann wie
; Bcostat
dosin: move.w 0(A1,D0.w),D0
; nein, gemdos
; lesen:
; device in d0
pea buf(PC) ;alles klar-
; machen für
; Fread
move.l #1,-(SP)
move.w D0,-(SP)
move.w #$3F,-(SP)
trap #1
lea 12(SP),SP
inend: movem.l (SP)+,D0-D1/A0-A1
moveq #0,D0 ;wegen long-
; rückgabe
; löschen
move.b buf(PC),D0 ;ergebnis in d0
; bringen
rte
;ende der gemdosarbeit
biosin: move.w 0(A1,D0.w),2(A0) ;funktions-
; nummer aus
; tabelle
; übernehmen
bra.s back ;weiter
; im bios
outputs:
move.w 2(A0),D0 ;device in d0
add.w D0,D0 ;2* wegen word
lea otab(PC),A1 ;inputtab in a1
cmpi.w #6,0(A1,D0.w) ;wert in tab<6?
bcs.s biosout ;ja-umlenkung
; bleibt im bios
tst.b isdos ;aufruf aus
; gemdos?
bne.s back ;ja, dann weiter
cmpi.w #8,(A0) ;Bcostat?
bne.s dosout ;nein, dann
; raus damit
info: movem.l (SP)+,D0-D1/A0-A1 ;register
; zurück
move.l #-1,D0 ;wert
; zurückgeben
rte
dosout: move.w 0(A1,D0.w),D0 ;gemdos:
; newdev in d0
move.w 4(A0),D1 ;zeichen-
move.b D1,buf ;argument in
; buffer bringen
pea buf(PC) ;alles klar-
move.l #1,-(SP) ;machen für
move.w D0,-(SP) ;Fwrite
move.w #$40,-(SP)
trap #1
lea 12(SP),SP
bra.s exit
biosout:move.w 0(A1,D0.w),2(A0) ;funktions-
; nummer aus
; tabelle
; übernehmen
bra back ;weiter im bios
newnumb:move.w 2(A0),D0 ;device
add.w D0,D0 ;2* wegen word
move.w 4(A0),D1 ;neue device-
; nummer
lea itab(PC),A1 ;tabelle für
; input
tst.w 6(A0) ;input
; gewünscht?
beq.s umleit ;ja->dann los
lea otab(PC),A1 ;sonst Output-
; tabelle
umleit:
move.w D1,0(A1,D0.w) ;neue num-
; mer eintragen
exit: movem.l (SP)+,D0-D1/A0-A1
rte
install::
move.l A0,-(SP)
movea.l bios.w,A0
cmpi.l #"BFOR",-8(A0) ;schon
; installiert?
beq.s fast_ende
move.l A0,oldbios ;alten bios
; retten
move.l #mybios,bios.w ;neuen ein-
; setzen
fast_ende: ;nochmal das ganze mit gemdos
movea.l gemdos.w,A0
cmpi.l #"BFOR",-8(A0)
beq.s ende
move.l A0,dosvek
move.l #mydos,gemdos.w
ende: movea.l (SP)+,A0 ;und
rts ;zurück
;hier kann man alles wieder rückgängig machen
exstall::cmpi.l #mybios,bios.w
bne.s fastbye
move.l oldbios(PC),bios.w
fast_bye:
cmpi.l #mydos,gemdos.w
bne.s bye
move.l dosvek(PC),gemdos.w
bye: rts
;einklinken in GEMDOS-Vektor
DC.B "XBRABFOR"
dosvek: DC.L 1
mydos:
movem.l D0/A0,-(SP)
move.w 14(SP),D0 ;funktionsnummer
btst #5,8(SP) ;Statusregister
bne.s ok ;supervisor
move USP,A0 ;sonst user
move.w (A0),D0 ;funktionsnummer
ok:
cmp.w #$4B,D0 ;pexec?
beq.s dosend ;ja, dann ok
move.l 10(SP),saveptr ;sonst
move.l #dosret,10(SP) ;einklinken
st isdos ;bin in gemdos
dosend: movem.l (SP)+,D0/A0 ;weiter im
move.l dosvek(PC),-(SP) ;gemdos
rts
dosret: clr.b isdos ;zurück aus
move.l saveptr(PC),-(SP) ;gemdos
rts
saveptr:DC.L 1
itab: ;tabelle der
DC.W 0 ;eingabe-
DC.W 1 ;leitung
DC.W 2
DC.W 3
DC.W 4
DC.W 5
otab: DC.W 0 ;und der
DC.W 1 ;Ausgabe-
DC.W 2 ;umleitung
DC.W 3
DC.W 4
DC.W 5
buf :
DS.B 1 ;puffer für
; Fread
isdos: DS.B 1 ;semaphore für
; gemdos-erkennung
EVEN
;ein kleines installationsprogramm
schluss:pea install(PC) ;Supexec
move.w #38,-(SP)
trap #14
addq.l #6,SP
clr.w -(SP) ;Ptermres
move.l #(256+schluss-anfang),-(SP)
move.w #$31,-(SP)
trap #1
END