Zum letzten Mal möchte ich heute Ihre geschätzte Aufmerksamkeit auf den erweiterten Emulator lenken. Wie in der dritten Folge erwähnt, steht ja noch eine ESCape-Sequenz aus, mit deren Hilfe Grafiken ohne den lästigen Umweg über das VDI ausgegeben werden können. Zwei kleinere Änderungen, die die Kompatibilität des Emulators erweitern, sowie ein paar allgemeine Anmerkungen zum Thema Geschwindigkeitssteigerung von Programmen komplettieren diese Folge und beschließen damit gleichzeitig die Serie.
Der xVT52-Emu kann Bilder im unkomprimierten Doodle-Format anzei-gen, in dem jeder Bildpunkt von genau einem Bit repräsentiert wird. Die Breite des Bildes ist beliebig, muß aber eine ganze Anzahl von Spalten betragen. Das bedeutet folglich, daß die Breite - gemessen in Pixels - ein Vielfaches von acht betragen muß. Bezüglich der Höhe gibt es dagegen keine Beschränkungen; sie kann eine beliebige Anzahl von Pixels betragen. Gezeichnet wird immer nur der sichtbare Teil, d.h. über den Bildschirm hinausreichende Teile der Grafik werden unterdrückt. Die auslösende Sequenz hat folgendes Format:
ESC r ncol nrowh nrowl data
Dabei bedeutet ncol die Anzahl der von der Grafik benötigten Spalten plus 32 (sie erinnern sich noch an den Abschnitt über Terminal-Emulatoren?). Die beiden folgenden Bytes geben Aufschluß über die Höhe der Grafik: nrowh ist die Anzahl der vollen Hunderter plus 32, nrowl enthält den um den üblichen Offset von 32 erhöhten BCD-Wert der Zehner und Einer; data schließlich sind die Grafikdaten persönlich. Das klingt viel komplizierter als es ist, darum zum besseren Verständnis ein
Nehmen wir an. Sie hätten ein Programm geschrieben und möchten als Begrüßungs-Bildschirm eine Grafik anzeigen. Diese Grafik können Sie nun mit einem beliebigen Malprogramm erzeugen und von diesem aus im Doodle-Format abspeichern (sollte das Programm dieses Format nicht kennen, so gibt es bereits eine Reihe von Accessories, mit deren Hilfe man den Bildschirminhalt als “Hardcopy” in beliebigen Formaten auf Diskette oder Platte abspeichern kann). Der Bildschirm - und damit auch die Hardcopy desselben - hat eine Auflösung von 640 mal 400 Pixels. Mit Hilfe eines kleinen Progrämmchens (z.B. dem in Listing 2) sorgt man nun einfach dafür, daß am Anfang der Hardcopy-Datei folgende Bytes eingefügt werden:
chr$(27)"r"chr$(80+32)
chr$(4+32)chr$(0+32).
Das Byte 80+32 bedeutet: Die Grafik ist 80 Spalten breit (also 640 Pixels = volle Bildschirmbreite); 4+32 steht für 400 Pixels Höhe und 0+32 bedeutet, es gibt bei der Höhe weder Zehner noch Einer. Hätte die Grafik eine Höhe von z.B. 457 Pixels, so müßte die 57 folgendermaßen kodiert werden: chr$(&H57+32). Beachten Sie bitte die hexadezimale Darstellung (&H57), weil die Zehner und Einer wie erwähnt als BCD-Ziffern übergeben werden müssen (BCD = Binary Coded Decimal). Wenn Sie die solchermaßen erweiterte Hardcopy-Datei vom Desktop anzeigen lassen, ergibt dies einen typischen Aha!-Effekt. Da die Grafik immer an der momentanen Cursor-Position ausgegeben wird, sollten Sie vor der Ausgabe sicherstellen, daß sich der Cursor auch wirklich da befindet, wo die linke obere Ecke der Grafik liegen soll (z.B. mittels ESC Y oder ESC H). Die Position des Cursors bleibt bei Grafikausgaben erhalten; ist die inverse Darstellung (ESC p) aktiv, wird die Grafik genau wie “normaler” Text invertiert!
Der im ST implementierte VT52-Emulator stellt ein Terminal-Programm dar und ist somit für Datenfernübertragung (DFÜ) geeignet. Wenn Sie also über geeignete Software verfügen, die Bildschirmausgaben nicht nur auf den Bildschirm, sondern auch auf die V.24 (bzw. RS232C) ausgibt, dann können Sie natürlich alle Ausgaben auch per Modem oder Standleitung (“Null-Modem”) an einen anderen Rechner übertragen. Um z.B. zu verhindern, daß ein Rechner Daten sendet, die der andere wegen Systemüberlastung gar nicht verarbeiten kann, benötigt man ein Handshake-Protokoll. Über dieses wird angezeigt, ob ein Rechner an-kommende Daten verarbeiten kann, ob er senden möchte etc.
Zur Protokoll-Realisierung gibt es zwei Möglichkeiten: Entweder man erledigt die Sache per Hardware, und zwar über zwei Leitungen, die bei der V.24 RTS und CTS genannt werden, oder aber per Software, indem man die Pegel der Leitungen über zwei spezielle Signale (Bitkombinationen) XON und XOFF simuliert. Diesen beiden Signalen sind die ASCII-Codes 0x13 bzw. 0x17 zugeordnet. Wenn Sie also Daten über die serielle Schnittstelle transportieren möchten, erledigt die Treiber-Software das “Hand-shaking”, sprich das Protokoll, automatisch entweder durch Setzen der entsprechenden Pegel an den RTS-/CTS-Leitungen oder durch Übertragen der XON/ XOFF-Bytes. Gewöhnlich darf man nur einfache (ASCII-) Texte per DFÜ übermitteln, weil die ASCII-Daten unterhalb 0x20 eine besondere Bedeutung haben. Wenn Sie mit dem xVT52 Grafik-Daten übertragen möchten, müssen Sie bei aktivem XON/XOFF-Protokoll dafür sorgen, daß alle 0x13/0x17-Bytes durch (z.B.) 0x15 bzw. 0x1e ersetzt werden, weil ansonsten die Übertragung abgebrochen würde. Dies gilt natürlich weder für den Fall eines Hardware-Protokolls über RTS/CTS noch für direkte Bildschirmausgaben.
Die im xVT52-Emulator für die Grafik-Ausgabe verantwortlichen Routinen finden Sie am Ende des Listings des dritten Teils dieser Serie unter den Namen GRAPHMODE, GET_WIDTH, GET_HEIGHT_H, GET_HEIGHT_L und GRAPHICS, wobei die letztgenannte Routine die eigentliche Grafik-Ausgabe, die anderen die Vorbereitungen hierzu übernehmen.
Beim Testen des neuen Emulators zeigten sich in Verbindung mit bestimmten Programmen etliche Unstimmigkeiten, die sich zumeist in zwei- bis vierbömbigen System-Verabschiedungen äußerten. Eine daraufhin vorgenommene Analyse zeigte, daß ein bestimmter Compiler Code generierte, bei dem das Datenregister DO nach einem Aufruf des GEMDOS zur Ausgabe einer Zeichenkette (“Cconws”) direkt weiterverwendet wird. Dies bedeutet, daß nicht zuerst auf evtl, aufgetretene Fehler geprüft, sondern einfach unterstellt wird, in DO befände sich nach dem Aufruf eine Null als Langwort... Als Konsequenz wurde in der Routine UPDATE_END (1. Teil des Listings in Heft 4/88, S.129) zwischen den Zeilen 465 und 466 der Befehl “clr.l d0” eingefügt; damit liefern jetzt alle \VT52-Funktionen (außer ESC g/h) eine lange Null zurück und die von o.g. Compiler produzierten Programme sind wieder glücklich und zufrieden.
Wenn man einmal damit anfängt, Betriebssystem-Funktionen zu patchen, (zumal in einer so tiefgehenden Weise), sind nachträgliche Zusatz-Patches schon so gut wie vorprogrammiert.
Da der GEMDOS-Trap-Handler für die Textausgabe-Funktionen neugeschrieben wurde, klappt die Geschichte mit der Ein-/Ausgabeumlenkung “natürlich” nicht mehr. Das bedeutet z.B., daß bei Commandline-Interpretern (CLI) der Befehl "dir>prn:”die Dateien des aktuellen Verzeichnisses auf den Bildschirm statt auf den Drucker ausgibt, weil xVT52 nicht auf eventuelle Dateiumlenkungen prüfen kann. Um diesem Mißstand abzuhelfen, wurde noch eine weitere Funktion des GEMDOS abgefangen: FORCE(). Programme, die Ein-und Ausgabekanäle umlenken, bedienen sich hierzu dieser Funktion. Im Listing 3 sehen Sie den geänderten GEMDOS-Trap-Handler, den Sie bitte anstelle des im ersten Teil dieser Serie abgedruckten einsetzen (S. 127. Zeilen 90-143).
Da laut Murphy jede Fehlerbeseitigung zwei neue Fehler impliziert, hat es mich nicht sonderlich gewundert, daß (fast) alle von mir getesteten Programme auch mit dieser Version verträglich waren - bis auf die, die mit dem o.g. Compiler geschrieben wurden. Der Grund diesmal: Adreßregister AL Obwohl nie offiziell bestätigt wurde, daß bei GEMDOS-Aufrufen nur die Register D0/A0 zerstört werden, erzeugt der Compiler einen Code, der es als größte Selbstverständlichkeit betrachtet, daß außer diesen beiden Registern alle sonstigen Werte nach einem Trap #1 erhalten bleiben... GRRRrrrrrhhhü! Nun denn, die eine hierdurch notwendige Änderung (das Retten von A1) ist bereits im geänderten Trap-Handler enthalten; die andere müssen Sie noch vornehmen, und zwar im ersten Teil des Listings (ST 4/88, S. 127): Ändern Sie bitte die Zeilen 73 und 80 ab in “movem.l d1-d7/al-a6,-(a1)”, bzw. “movem.l (a1)+,d1-d7/a 1 -a6”. Wenn Sie jetzt noch Zeile 86 (“ds.w 23,0”) abändern in “ds.w 31,0”, ist die Welt wieder in Ordnung (es werden jetzt wirklich alle Register außer D0/A0 gerettet). Man darf gespannt sein, ob die nächste TOS-Ver-sion, die ATARI ja schon für den Herbst angedroht hat, dieselben Register bei GEMDOS-Aufrufen rettet wie in den derzeitigen Versionen. Wenn nicht, läuft kein einziges Programm mehr, das durch diesen ominösen Compiler “durchgenudelt” wurde... (wenn Sie jetzt wissen möchten, um welchen Übersetzer es hier geht, muß ich Sie enttäuschen: ich haue nur sehr ungern jemanden in die Pfanne. Es steht Ihnen natürlich frei, die Änderungen nicht vorzunehmen und erst mal zu prüfen, welche Programme unter xVT52 nicht mehr laufen).
Wo wir gerade bei Änderungen sind, bietet sich noch eine kleine Sequenz an, die nur etwa 30 Bytes zusätzlich belegt, etwas kompliziert ist und dem Emulator eine recht nützliche Eigenschaft verleiht: sie macht ihn nämlich AUTO-Ordner-und damit bootfähig.
Wenn Sie das Programm ohne die folgenden Änderungen in den AUTO-Ordner des Boot-Laufwerks legen würden, könnten Sie zwar den Emulator verwenden, müßten gleichzeitig aber auf den Cursor verzichten. Der Grund liegt hier wieder einmal in der leidigen Tatsache , daß Programme in diesem speziellen Ordner eher zur Ausführung gebracht werden als das Desktop und damit vor Installieren des GEM. Der Trap-#2-Vektor, der für VDI- und AES-Aufrufe zuständig ist, zeigt bis zur endgültigen GEM-Installation auf einen RTE-Befehl. Da xVT52 diesen Vektor rettet, würde er später alle GEM-Aufrufe ins Nirwana schicken...
Um dieses Problem zu umgehen, wird der GEM-Vektor jetzt nicht mehr direkt beim Installieren von xVT52 umgebogen, sondern erst nach der GEM-Installation. Um den richtigen Zeitpunkt zu erwischen, muß die Cursor-Interrupt-Routine (CRS_IRR) bei jedem Durchlauf prüfen, ob der Vektor immer noch auf die gleiche Adresse zeigt wie zum Zeitpunkt der Aktivierung von x VT52. Wenn ja, ist das GEM noch nicht initialisiert, andernfalls wird der nun aktive Vektor gerettet und durch den xVT52-eigenen ersetzt. Mit den folgenden paar Änderungen wird diese Vorgehensweise realisiert; sie beziehen sich alle auf den ersten Teil des Listings, wie er in der April-Ausgabe der ST-Computer (S. 126-128) zu finden ist.
1 INSTALL-Routine:
Die beiden ersten Änderungen sorgen dafür, daß der erste VBI-Slot übersprungen wird, da dieser für GEM reserviert ist; die dritte Änderung verhindert die frühzeitige Installation des eigenen Vektors.
2 CRS_IRR: - zwischen Zeilen 309 und 310 Listing 4 einfügen
Mit diesen paar Befehlen wird sichergestellt, daß der eigene Vektor erst nach Installieren des GEM eingesetzt wird.
Das war’s denn auch schon fast; zwei Bemerkungen muß ich noch los werden, nämlich daß der so modifizierte Emulator nur noch, d.h. ausschließlich, will sagen exklusiv, heim Booten aus dem AUTO-Ordner gestartet werden darf. Im "hochgefahrenen'' Zustand führt der Versuch, xVT52 nachträglich zu starten, garantiert zu saftigen Abstürzen. Außerdem sollte der Emulator als letztes Programm im AUTO-Ordner stehen; speziell in Kombination mit weiteren System-Patches - insbesondere des neuerdings von ATARI vertriebenen TURBO-DOS-Programmes - kann es sonst leicht passieren, daß der Boot-Erfolg ausbleibt und - bei Verwendung einer Hard-Disk -selbige “von Hand zu Fuß” nachträglich per AHDI o.ä. hochgezogen werden muß...
...ist das noch lange nicht dasselbe. Diese Binsenweisheit gilt auch für den erweiterten Emulator. Mit den eben vorgestellten Änderungen kann man aber von einer sehr hohen Kompatibilität zum eingebauten VT52-Emu!ator ausgehen. Sogar die Font-Umschaltung (Text 8/16) in GFA-Basic bereitet keinerlei Schwierigkeiten; überhaupt wird in diesem Interpreter von allen Möglichkeiten zur Text-Ausgabe Gebrauch gemacht (GEMDOS, VDI und BIOS), so daß man sich hier sehr leicht von der Kompatibilität überzeugen kann. Beim Anklicken eines Zeichens (im BASIC-Editor) mit der Maus wird dieses u.U. “unregelmäßig” invertiert; der Grund liegt darin, daß beim xVT52 die Cursor-Routine erst nach der Maus-Routine des GEM aktiviert wird, während es normalerweise umgekehrt ist. Um dieses Problemchen zu beheben, müßte man die VBL-Interrupt-Routine neuschreiben und die Cursor-Routine an der entsprechenden Stelle einklinken. Wer will, kann’s ja mal versuchen (ist nicht besonders schwierig, bloß etwas aufwendig).
Ansonsten kann es nur hin und wieder Vorkommen, daß der Cursor nach dem Verlassen von TOS-Anwendungen bei der Rückkehr ins Desktop stehenbleibt. Der Grund hierfür dürfte irgendwo in den Tiefen des VDI verborgen liegen, weil nicht ausgeschlossen werden kann, daß Teile des VT52-Emulators (vor allem dessen Kontroll-Variablen) vom VDI direkt angesprochen werden. Wenn es jemandem gelingen sollte, die genaue Ursache zu eruieren, wäre ich über eine entsprechende Mitteilung sehr dankbar. Überhaupt würde ich mich freuen, wenn mir die Atarianer im Lande ihre Erfahrungen, Vorschläge etc. bezüglich des erweiterten VT52-Emus zukommen ließen.
Wie versprochen, möchte ich Ihnen jetzt noch ein paar Tips verraten, mit denen Sie zeitkritische Programme auf Trab bringen. Es ist natürlich eine ziemlich triviale Feststellung, daß die Geschwindigkeit eines Programmes entscheidend vom verwendeten Algorithmus abhängt. Wenn Sie 50000 Daten per Bubblesort sortieren, können Sie alle nur erdenklichen Tricks an wenden und liegen trotzdem noch um Größenordnungen über der Laufzeit eines Quicksorts. Ebenso trivial ist die Aussage, daß ein in Assembler geschriebenes Programm nicht zwingenderweise viel schneller sein muß als ein in einer höheren Sprache geschriebenes.
Schnelle Programme lassen sich gezielt nur in Assembler realisieren, weil man in Interpreter- und Compilersprachen so gut wie keinen Einfluß auf den tatsächlich erzeugten Code hat. Die Sprache C führt hier ein Zwitterdasein, weil sie einerseits zwar typische Merkmale einer Hochsprache aufweist, andererseits aber nur einen Makro-Assembler darstellt. Gerade hier gilt: auch wenn man sich alle Mühe gibt, möglichst viele ineinandergeschachtelte Befehle zu einem einzigen zusammenzufassen: ob das noch jemand lesen kann, ist die eine Frage; ob der Compiler diese Komprimierung auch in effizienten Code umsetzen kann, bleibt dahingestellt. Aus diesen Gründen möchte ich Ihnen hier nur solche “Kniffe” näherbringen, die auch wirklich die Laufzeit optimieren, ohne das Programm in Spaghetti-Code zu verwandeln. Allerdings sollten Sie über einige C- und/oder Assembler-Kenntnisse verfügen, da die Möglichkeiten, die ich Ihnen vorstellen möchte, in erster Linie Besonderheiten des im ST verwendeten Prozessors ausnutzen.
Und in den STs wird nun mal der MC68000 eingesetzt, der (allen Aprilscherzen zum Trotz) hier mit acht Megahertz getaktet wird. In dieser Version packt er immerhin so Daumen mal Pi etwa 300000 Befehle pro Sekunde. Geschickt programmiert, kann man damit locker etwa dreieinhalb Megabyte Speicher pro Sekunde adressieren.
Was aber ist geschickte Programmierung? Hierzu ein paar Beispiele. Wie in allen Rechnern der sogenannten “Von-Neumann-Architektur” stehen Daten wie auch Programme gleichberechtigt im Hauptspeicher. Das bedeutet, daß der Prozessor sich seine Befehle Stück für Stück aus dem Speicher holen muß, wozu er logischerweise genausolange benötigt, als ob er irgendein Datenwort lesen würde. Die Befehle, die der MC68000 versteht, sind immer zwischen zwei und zehn Bytes lang; es versteht sich von selbst, daß der Prozessor pro Zeiteinheit wesentlich mehr zwei Byte lange Befehle verarbeiten kann als solche der Länge zehn. Wenn Sie xVT52 einmal disassemblieren, können Sie erkennen, daß an allen zeitkritischen Bereichen vorzugsweise die kurzen Befehle zum Einsatz kommen. Bei Verschiebe- und Löschroutinen findet man dagegen viele MOVEM.L-Befehle. Diese benötigen zwar sechs Bytes, manipulieren hier aber (es lebe die Mikro- und Nanoprogrammierung) bis zu vierzig Bytes auf einmal - viel schneller geht's nimmer!
Ein weiterer prozessorspezifischer Punkt ist die Verwendung möglichst vieler Register. Variablen, die innerhalb einer Routine oft benötigt werden, sollten immer in Registern gehalten werden. Warum? Der Zugriff auf den Hauptspeicher (RAM) ist in jedem Fall wesentlich langsamer als der auf die prozessorinternen Register. In höheren Programmiersprachen hat man in der Regel keine Möglichkeit, den Compiler zu “zwingen", bestimmte Variablen in Registern abzulegen; eine Ausnahme bilden C-Compiler, bei denen man Variablen durch Voranstellen des Schlüsselwortes “register” vor die Deklaration in Register legen lassen kann. Allerdings: Ob der Compiler auch tatsächlich auf diesen frommen Wunsch eingeht oder er es dabei bewenden läßt, entscheidet er höchstselbst und nicht etwa der Programmierer...
Da es sich Programme in der Regel nicht aussuchen können, an welche Speicheradresse sie zur Laufzeit vom Betriebssystem geladen werden, müssen sie praktisch an jeder (geraden) Adresse lauffähig sein. Um dies zu erreichen, gibt es beim ST zwei Möglichkeiten, von denen die eine schnell ist, dafür aber Restriktionen an die Programmlänge stellt, während der anderen Methode zwar die Länge egal ist, sie dafür aber langsamer arbeitet.
Wie die meisten Prozessoren kennt auch der MC68000 die Adressierungsart “absolut”. Damit ist er in der Lage, auf jede Speicherzelle innerhalb des adressierbaren Bereiches zuzugreifen, indem er ihre “absolute” Adresse verwendet. Diese jedoch ist vier Bytes lang und kostet somit eine Menge Speicher und folglich auch Ladezeit. Für einen Befehl, der den Inhalt einer Speicherzelle in eine andere verschiebt und dabei absolut adressiert, werden volle zehn Bytes benötigt. Diese Methode bläht also Programme auf, verlangsamt sie und - was am wichtigsten ist - läßt sie generell nur an einer Adresse laufen. Denn wenn man auf eine Adresse 4711 zugreift, ist das unabhängig davon, wo das Programm steht!
Der Trick, mit dem man solchermaßen adressierende Programme trotzdem überall zum Laufen bringen kann, funktioniert folgendermaßen: Das Programm wird so kompiliert, daß es nur an Adresse Null läuft (witzig, nicht?). Im Anschluß an das eigentliche Programm steht eine Tabelle, in der alle “absoluten” Adressen aufgelistet sind; außerdem wird im Programm-Header das sogenannte Relozier-Flag gesetzt. Wird das Programm nun in den Rechner geladen und ist das Flag gesetzt, so wird auf alle gekennzeichneten Adressen die Adresse aufaddiert, an die das Programm tatsächlich geladen wurde. Verwirrt? Beispiel. Ein Programm möchte auf eine Variable zugreifen, die 5000 Bytes vom Programmanfang entfernt ist. Da der Programmstart definitionsgemäß auf Adresse 0 liegt, erhält unsere Variable die absolute Adresse 5000. Die Stelle im Programm, an der ein Befehl (z.B. MOVE) auf diese Adresse zugreift, wird in der Relo-Tabelle vermerkt. Der Befehl selbst ist jedoch für die absolute Adressierung ausgelegt und lautet z.B. MOVE.B 5000,D0 (bringe Inhalt der Speicherzelle 5000 ins Datenregister 0). Wird das Programm geladen (z.B. an Adresse 4712), addiert ein spezielles Programm des TOS (der Relocator) auf die Adresse des Befehls den Lade-Offset von 4712, so daß er nunmehr lautet: MOVE.B 9712,D0. Jetzt klarer? Diese Programme benötigen beim Laden mehr Zeit als solche der anderen Kategorie. Das ist zu verschmerzen. Sie sind aber auch länger als andere. Das wäre auch noch genehm. Sie sind langsamer als die anderen - jetzt reicht’s!
Klar geht es auch anders. Anstatt nämlich auf Kosten von Speicherplatz und Performance “relativ absolut” zu adressieren, kommt man durch “pure Relativität” schneller und einfacher ans Ziel. Der “Trick” diesmal klappt wie folgt. Man sagt nicht mehr: “Adressiere ABSOLUT die Speicherzelle, die x Bytes RELATIV vom Programmstart liegt”, sondern “Adressiere RELATIV die Speicherzelle, die x Bytes ABSOLUT vom momentanen Programmzählerstand entfernt ist”! Für den MOVE-Befehl liest sich das dann so, daß er den Inhalt der Speicherzelle holen soll, die x Bytes vor oder hinter der Adresse des MOVE-Befehls selbst liegt. Diese Methode der “relativen Adressierung” kennt der MC68k nämlich auch. Sie hat zwei Vor- und zwei Nachteile. Zum einen benötigt sie weniger Speicherplatz für die Befehle und arbeitet wesentlich schneller als die absolute. Zum anderen sind PC-relative Adressen bei vielen Befehlen nur als Quelloperanden erlaubt (man kann also nur den Inhalt lesen, nicht aber irgendwas reinschreiben), und der Offset, der zum aktuellen PC-Stand addiert wird, beträgt 16 Bit mit Vorzeichen. Damit kann man nur Adressen erreichen, die 32 KByte vor oder hinter dem aufrufenden Befehl liegen (verstehen Sie jetzt, warum einige Compiler Restriktionen an die Länge einer Prozedur, einer Variablendeklaration, eines Moduls etc. stellen?).
Da xVT52 aber weniger als fünf KB “verbrät”, stellt dies faktisch kein Problem dar. Damit allein konnte die Geschwindigkeit schon merklich gesteigert werden. Allerdings: Diesen Kniff können Sie nur in Assembler voll ausnutzen; Compiler (Interpreter sowieso) sind nur sehr schwer davon zu überzeugen, daß man gerne effizienten Code hätte. Da xVT52 völlig PC-relativ geschrieben wurde, wollte ich diese Möglichkeit trotzdem erwähnen. Man erkennt die PC-relative Adressierung bei konventionellen 68000-Assemblern am nachgestellten (PC)-Symbol (z.B. MOVE.L DATUM(PC),D0).
Einen Punkt möchte ich noch erwähnen, der die Ausführung von Schleifen beschleunigt. Hierzu eine kleine praktische Vorbetrachtung: Wenn Sie zu Hause Ihr Plattenregal säubern möchten (kann ja mal Vorkommen), greifen Sie höchstwahrscheinlich nicht eine Platte nach der anderen aus dem Regal, um sie “zwischenzulagern”. Anschaulich ist es klar, daß man schneller mit der Arbeit fertig ist, wenn man jedesmal ein paar Platten auf einmal anpackt und auf die Seite legt. Genauso ist es bei der Programmierung von Wiederholungsanweisungen (Schleifen). Wenn man eine Anweisung zehnmal ausführen möchte, kann man dafür zwar schreiben “for i= 1 to 10 anw”, dann muß aber außer der eigentlichen Anweisung der Schleifenkonstrukt “mitgeschleppt” werden: nach jedem Durchlauf muß die Laufvariable inkrementiert und auf die Ende-Bedingung überprüft werden. Daß so etwas u.U. sehr zeitintensiv ist, versteht sich von selbst. Aus diesem Grund sollte man besser die Anzahl der Schleifendurchläufe verringern und dafür die auszuführende Anweisung ein paarmal hintereinander schreiben (also etwa “for i=1 to 2 {anw; anw; anw; anw; anw}”). Bei xVT52 finden in allen zeitkritischen Routinen (etwa zum Darstellen/Invertieren von Zeichen) nur die “verkürzten” Schleifen Verwendung. Das sind zwar bei weitem nicht alle Möglichkeiten, die zur Beschleunigung von Programmen zur Verfügung stehen, aber es sind zumindest sehr effiziente Methoden, die auch im erweiterten Emulator eingesetzt wurden. Und daß dieser recht flott arbeitet, belegen die Benchmarks der letzten Folge - gell?
So, nachdem ich Sie nun vier Monate mit meinem Programm bei Laune gehalten habe, wird es höchste Zeit, mich zu verabschieden. Ich hoffe, daß Sie regen Gebrauch von den neuen Ausgabefähigkeiten machen werden und Sie im Verlauf dieser Serie das eine oder andere dazugelernt und sich vielleicht auch etwas Appetit für eigene (Assembler-)Projekte geholt haben. Sollten Sie zum Programm oder den im Verlauf dieser Serie angesprochenen Themen Fragen und/oder Antworten haben, können Sie mir ja mal schreiben (via ST-Redaktion). Fänd’ ich nett.
Zum guten Schluß habe ich Ihnen in Listing 5 noch ein kleines GFA-Progrämmchen vorbereitet, mit dessen Hilfe Sie ausprobieren können, ob Sie den Emulator auch richtig eingegeben haben; es erzeugt eine kleine Datei, die Sie sich bei installiertem xVT52 vom Desktop aus anzeigen lassen sollten...
Viel Spaß und - tschüß!
MS
; *************************************
; * >EXTENDED VT52-TERMINAL EMULATOR< *
; *===================================*
; * Entwickelt mit PROFIHAT-Assembler *
; * M.Schumacher, (p) 12/87 *
; *************************************
FSCR_LEFT: ; ESC 'T'
move.l 6(a4),a1 ; abs. Cursorposition
suba.w (a0),a1 ; akt. 5palte=‘Zeilenanfang
adda.w 38(a0),a1 ; +Bytes/Textzeile=^Anfang
nächster Textzeile move.w 36(a0),d2 ; Zeichenhöhe (Pixels)
subq.w #1,d2 ; in dbra-Zähler wandeln
\lp1:
moveq #3,d1 ; 4*10 Worte verschieben
move.w #0,CCR ; SR-Flags auf Null
btst #7.-80(a1) ; Pixel ganz links gesetzt?
beq.s \lp2 ; nein
move.w #16.CCR ; sonst X-Bit setzen
\lp2:
roxi -(a1)
roxi -(a1)
roxi -(a1)
roxi -(a1)
roxi -(a1)
roxi -(a1)
roxi -(a1)
roxi -(a1)
roxi -(a1)
roxi -(a1)
dbra dl,\lp2
dbra d2,\lpl ; nächste Pixelzeile verschieben
rts
FSCR_RIGHT: ; ESC 'U'
move.l 6(a4),a1 ; abs. Cursorposition
suba.w (a0),a1 ; -akt. Spalte=‘Zeilenanfang
move.w 36(a0),d2 ; Zeichenhöhe (Pixels)
subq.w #1.d2 ; in dbra-Zähler wandeln
\lp1:
moveq #3,d1 ; 4*10 Worte verschieben
move.w #0,CCR ; SR-Flags auf Null
btst #0.79(a1) ; Pixel ganz rechts gesetzt?
beq.s \lp2 ; nein
move.w #16,CCR ; sonst X-Bit setzen
\lp2:
roxr (a1)+
roxr (a1)+
roxr (a1)+
roxr (a1)+
roxr (a1)+
roxr (a1)+
roxr (a1)+
roxr (a1)+
roxr (a1)+
roxr (a1)+
dbra d1,\lp2
dbra d2,\lp1 ; nächste Pixelzeile verschieben
rts
FDSCRUP: ; ESC 'W'
move.w 6(a0),d1 : max. Zeile
sub.w 2(a0),d1 ; - aktuelle
move.l 6(a4),a1 ; abs. Cursorposition
suba.w (a0),a1 ; - akt. Spalte * ‘Zeilenanfang
bra.s FSCRUP.ENTRY ; schieben*löschen
F5CRUP: ; ESC 'X' Bildschirminhalt incl. der
; akt. Zeile um eine Zeile pixelweise nach
; oben schieben
move.l LOGBASE,a1 ; ^Video-RAM
move.w 2la0),d1 ; aktuelle Zeile
FSCRUP_ENTRY: ; Einsprung für ESC ’U'
move.w d1,d0 ; Zeile retten
addq.w #1,d0 ; plus 1 = Anzahl Textzeilen
move.w 36(a0),d1 ; Zeichenhöhe in Pixels
mulu d1,d0 ; Anzahl Textzeilen mal Zeichenhöhe
subq.w #1,d0 ; 1 Zeile abziehen
lsr.w #1.d0 ; diu 2 (es werden immer 2 Pixelzeilen verschoben)
subq.w #1,d0 ; in dbra-Zähler wandeln
move.w d0,d2 ; und merken
subq.w #1,d1 ; Zeichenhöhe in dbra-Zähler wandeln
movem.l a0/a4/a6.-(a7) ; Register retten
move.l al.-(a7) ; Startadresse merken
\lp_scr:
movea.l (a7),a1 ; Ziel
movea.l a1,a2
adda.w #80.a2 ; Duelle liegt 1 Pixelzeile tiefer
move.w d2,d0 ; Anzahl zu scrollender Pixelzeilen-1
\scr_ln: ; Bildschirminhalt um 1 Pixel nach oben scrollen
movem.l (a2)+,REGISTER
movem.l REGISTER,(a1)
movem.l (a2)+.REGISTER
movem.l REGISTER,40(a1) ; 2 Pixelzeilen verschieben
movem.l (a2)+,REGISTER
movem.l REGISTER,80(a1)
movem.l (a2)+,REGISTER
movem.l REGISTER,120(a1)
adda.w #160,a1
dbra d0,\scr_ln ; die nächsten beiden Pixelzeilen verschieben
movem.l (a2)+.REGI5TER ; die letzte Pixelzeile verschieben
movem.l REGISTER,(a1)
movem.l (a2)+.REGISTER
movem.l REGISTER,40(a1)
movem.w ZEROES,REGISTER ; Register löschen
movem.l REGISTER.80(a1) ; und letzte Pixelzeile damit löschen
movem.l REGISTER.120(a1)
dbra d1,\lp_scr ; Bildschirm erneut um 1
Pixel nach oben scrollen
addq.l #4,a7 ; Startadresse wegwerfen
movem.l (a7)+,a0/a4/a6 ; Register zurück
rts
ZEROES:
ds.l 5,0 ; 5 Null-Langworte zum Löschen der Register
FDSCRDN: ; ESC 'Z'
move.w 2(a0),d1 ; akt. Zeile
move.l 6(a4),a1 ; abs. Cursorposition
suba.w (a0),a1 ; ^Anfang der Textzeile
adda.w 38(a0),a1 ; ^Anfang der nächsten Textzeile
suba.w #80,a1 ; ^Anfang der letzen Pixelzeile der Textzeile (Puh!)
bra.s FSCRDN.ENTRY ; schieben+löschen
FSCRDN: ; ESC 'V' Bildschirminhalt incl. der akt. Zeile
; um eine Zeile pixelweise nach unten schieben
move.l LOGBASE,a1 ; ^Video-RAM
adda.l #32000-80,a1 ; ^letzte Pixel-Zeile
move.w 6(a8),d1 ; max. Zeile
sub.w 2(a0),d1 ; -aktuelle Zeile - Anzahl zu scrollender Zeilen-1
FSCRON_ENTRY: ; Einsprung für ESC 'Z'
addq.w #1,d1 ; +1=Anzahl zu scrollender Textzeilen
move.w d1,dB ; Anzahl Zeilen retten
move.w 36(a0),d1 ; Zeichenhöhe in Pixels
mulu d1,d0 ; mal Anzahl Textzellen
subq.w #1,d0 ; minus 1 Pixelzelle
lsr.w #1,d0 ; div 2 (es werden immer 2 Pixelzeilen verschoben)
subq.w #1,d0 ; in dbra-Zähler wandeln
move.w d0,d2 ; und merken
subq.w #1,d1 ; Zeichenhöhe in dbra-Zähler wandeln
movem.l a0/a4/a6,-(a7) ; Register retten
move.l a1,-(a7) ; Startadresse merken
\lp_scr:
movea 1 (a7),a1 ; Ziel
movea.l a1,a2
suba.w 880,a2 ; Quelle liegt 1 Pixelzeile höher
move.w d2,d0 ; Anzahl zu scrollender Pixelzeilen-1
\scr_ln: ; Bildschirminhalt um 1 Pixel nach unten scrollen
movem.l <a2)+.REGISTER
movem.l REGISTER,(a1)
movem.l (a2),REGISTER
movem.l REGISTER,40(a1) ;2 Pixelzeilen verschieben
suba.w #120,a2
movem.l (a2) + ,REGISTER
movem.l REGISTER.-80(a1)
movem.l (a2),REGISTER
movem.l REGISTER,-40(a1)
suba.w #120,a2
suba.w #160,a1
dbra d0.\scr_ln ; die nächsten beiden Pixelzeilen verschieben
movem.l (a2)+,REGISTER ; die letzte Pixelzeile verschieben
movem.l REGISTER,(a1)
movem.l (a2),REGISTER
movem.l REGISTER,40(a1)
mouem.w ZEROES,REGISTER ; Register löschen
mouem 1 REGISTER,-80(a1); und letzte Pixelzeile damit löschen
movem.l REGISTER,-40(a1)
dbra d1,\lp_scr ; nächste Textzeile
addq.l #4,a7 ; Startadresse wegwerfen
movem.l (a7)+,a0/a4/a6 ; Register zurück
rts
DIR_CRS: ; ESC 'Y'
lea VEC_BASE,a1 ; Vektor auf Holen
lea ESC_Y_X,a0 ; der Zeile
move.l a0,(a1) ; umbiegen
rts ; fertig
ESC_Y_X:
lea TCB,a0 ; ^TerminalControlBlock
lea UEC_BASE,a1 ; Vektor
lea ESC_Y_Y,a2 ; auf Y-Angabe
move.l a2,(a1) ; umschalten
subl.w #32,d0 ; Offset abziehen
bmi.s \zurück ; zu klein, ignorieren
cmp.w 6(a0),d0 ; größer als größte Zeile?
bgt.s \zurück ; ja, ignorieren
move.w d0,2(a0) ; sonst Zeile setzen
\zurück:
rts ; fertig
ESC_Y_Y:
lea TCB,a0 ; ^TerminalControlBlock
lea VEC_BASE,a1 ; Vektor wieder
lea STD_VEC,a2 ; auf normale Ausgabe
move.l a2,(a1) ; umschalten
bsr UPDATE_CRS ; Cursor ausschalten
subi.w #32,d0 ; Offset von Spaltenwert abziehen
bmi.s \zurück ; zu klein, ignorieren
cmpi.w #80,d0 ; größer als größte Spalte?
bpl.s \zurück ; ja, ignorieren
move.w d0,(a0) ; sonst Spalte setzen
\zurück:
bsr UPDATE_END ; und Cursor wieder einschalten
rts ; fertig
; ************************************
; * VDI-INTERFACE FÜR xVT52-EMULATOR *
; ************************************
VDI_ENTRY:
move.l d1,-(a7) ; AParameterBlock retten
bsr UPDATE_CR5 ; Cursor ausschalten
move.l (a7)+,a3 ; AParameterBlock
lea TCB,a0 ; ^TerminalControlBlock
move.l (a3),a2 ; ^Contrl-Array
move.w 10(a2),d0 ; Funktionsnummer holen
cmpi.w #101,d0 ; VDI ESC 101?
bne.s \v102 ; nein
bsr TOP_OFFSET ; sonst Offset zum Bildschirm-Anfang setzen
bra UPDATE_END ; Cursor freigeben und zurück
\v102:
cmpi.w #102,d0 ; VDI ESC 102?
bne.s \vdi_norm ; nein
bsr INIT_FONT ; sonst Font initialisieren
bra UPDATE_END ; Cursor freigeben und zurück
\vdi_norm:
add.w d0,d0 ; Funktionsnummer*2 (Word-Pointer)
lea DUMMY,a1 ; Adresse der 1. Routine
lea VDI_ROUTS,a2 ; Adresse der Adress-Tabelle
move.w 0(a2, d0.w),a2 ; rel Adresse der Routine
adda.l a1,a2 ; +Offset=abs. Adresse der Routine
jsr (a2) ; selbige zur Ausführung bringen
bra UPDATE_END ; Cursor freigeben und zurück
VDI_ROUTS: ; NR. FUNKTION
dc.w 0 ; 0. unbelegt
dc.w GET_SIZE-DUMMY ; 1. inquire screen size
dc.w EXIT_A-DUMMY ; 2. exit alpha mode (ESC 'f' +ESC 'E')
dc.w ENTER_A-DUMMY ; 3. enter alpha mode (ESC 'E'+ESC 'e')
dc.w CRS_UP-DUMMY ; 4. Cursor up (ESC 'A')
dc.w CRS_DOWN-DUMMY ; 5. Cursor down (ESC 'B')
dc.w CRS_RIGHT-DUMMY ; 6. Cursor right (ESC 'C')
dc.w CRS_LEFT-DUMMY ; 7. Cursor left (ESC 'D')
dc.w HOME-DUMMY ; 8. home (ESC 'H')
dc.w DEL_FROM_CRS-DUMMY ; 9. erase to end of screen (ESC 'J')
dc.w L_TO_END-DUMMY ; 10. erase to end of line (ESC 'K')
dc.w GOTOXY-DUMMY ; 11. set Cursor Position
dc.w WR_TXT-DUMMY ; 12. write text
dc.w INV_ON-DUMMY ; 13. inverse mode on (ESC p')
dc.w INV_OFF-DUMMY ; 14. inverse mode off (ESC q)
dc.w GET_CRSPOS-DUMMY ; 15. inquire Cursor Position
DUMMY: ; Leer-Routine für nicht benutzte Sequenzen
rts
GET_SIZE: ; VDI ESC 1
move.l (a3),a5 ; ACONTRL-Block
move.w #2,8(a5) ; 2 Rückgabewerte in INTOUT
move.l 12(a3),a5 ; AINTOUT-Block
move.w #80,2(a5) ; max. Spalte
move.w 6(a0),(a5) ; max. Zeile
addq.w #1,(a5) ; +1
rts
EXIT_A: ; VDI ESC 2
bclr #3,(a4) ; Cursor ausschalten
bra CLS ; Bildschirm löschen und zurück
ENTER_A: ; VDI ESC 3
bsr CLS ; Bildschirm löschen
bset #3,(a4) ; Cursor einschalten
rts
GOTOXY: ; VDI ESC 11
move.l 4(a3),a5 ; AINTIN-Array
move.w (a5),d1 ; Zeile
subq.w #1,d1 ; -Offset
bmi.s \col ; <0, ignorieren
cmp.w GlaBl.dl ; zu groß?
bgt.s \col ; ja, ignorieren
move.w dl,2(a0) ; sonst übernehmen
\col:
move.w 2(a5),d1 ; Spalte
subq.w #1,d1 ; -Offset
bmi.s \zurück ; <0. ignorieren
cmpi.w 879.dl ; >79?
bgt.s \zurück ; ja. ignorieren
move.w d1,(a0) ; Spalte übernehmen
\zurück:
rts
WR_TXT: ; VDI ESC 12
move.l (a3).a5 ; ^CONTRL-Bock
move.w 6(a5),d1 ; Anzahl auszugebender Zeichen
subq.w 81,d1 ; in dbra-Zahler wandeln
bmi.s \zurück ; <0, auf hören
move.l 4(a3),a5 ; AINTIN-Block
\lp:
move.w (a5)*,d0 ; Zeichen holen
movem.l d1/a5,-(a7) ; Register retten
bsr CON_ENTRY ; Zeichen ausgeben
movem.l (a7)*,d1/a5 ; Register zuruck
dbra d1,\lp ;nächstes Zeichen ausgeben
\zurück:
rts
GET_CRSPOS: ; VDI ESC 15
move.l (a3),a5 ; ^CONTRL-lock
move.w #2.8(a5) : 2 Rückgabewerte in INTOUT
move.l 12(a3),a5 ; ^INTOUT-Block
move.w 2(a0),(a5) ; akt. Zeile
addq.w #1,(a5)+ ; +Offset
move.w (a0),(a5) : akt. Spalte
addq.w #1,(a5) ; +Offset
rts
TOP_OFF5ET: ; VDI ESC 101
move.l 4(a3),a5 ; ^INTIN-Block
move.w (a5),d0 ; Offset in Pixelzeilen holen
mulu 38(a0),d0 ; *Bytes/Textzeile
move.l LINE_A,a2 ; ^Line A-Block
move.w d0,-$1E(a2) ; Byte-Offset in ODI-Blotk eintragen
rts
INIT_FONT: ; VDI ESC 102
move.l 4(a3),a5 ; ^INTIN-Block
move.l LINE-A,a6 ; ^LINE A-Block
move.l (a5),a5 ; ^Font-Header (INTINE0.13
move.w 82(a5),d0 ; Zeichenhöhe in Pixel
move.w d0,36(a8) ; im eigenen und
move.w d0.-$2E(a6) ; im VDI-Block merken
moveq #80,d1 ; Bytes pro Pixelzeile
mulu d0,d1 ; 80 x Zeichenhöhe
move.w d1,38(a0) ; = Bytes pro Textzeile
move.w d1,-$28(a6) ; im VDI-Block merken
move.l #400,d1 ; 400 Pixelzeilen/BiIdschirm
diuu d0,d1 ; diu Zeichenhöhe
subq.w #1,d1 ; - 1
move.w d1,6(a0) ; = größte Zeilennummer
move.w d1,-$2A(a6) ; im VDI-Block merken
move.l #640,d1 ; 640 Pixels pro Pixelzeile
divu 52(a5),d1 ; div Zeichenbreite in Pixels
subq.w #1,d1 ; - 1
move.w d1,-$2C(a6) ; = größte Spaltennummer
move.l 72(a5),-$A(a6) ; Zeiger auf Offset-Tabelle
move.w 80(a5),-$E(a6) ; Breite eines Zeichens (MÜSS 8 Bit betragen!!!)
move.w 36(a5),-$10(a6) ; ASCII-Code des ersten darstellbaren Zeichens
move.w 38(a5),-$12(a6) ; ASCII-Code des letzten dar. Zeichens im Font
move.l 76(a5),a5 ; ^Fontdaten
move.l a5,-$16(a6) ; im VDI-Block merken
move.l a5,32(a0) ; und zum aktuellen OT52-Font machen
subq.w #8,d0 ; 8x8-Font?
bne.s \8x16 ; nein
move.l a5,28(a0) ; A8x8-Fontdaten
bra.s \init_crs ; Cursor initialisieren
rts
\8x16:
subq.w #8,d0 ; 8x16-Font?
bne.s \zurück ; nein, Chaos-Format (nicht darstellbar...)
move.l a5,24(a0) ; A8x16-Fontdaten
\init_crs:
clr.l (a0) ; Cursor in Home-Position bringen
clr.w 10(a0) ; gemerkte Cursorpositionen löschen
\zurück:
rts
data
REDIR: de w 0 ; Flag für Umlenkung (0=nein)
AES.OK: dc.w 0 ; Flag, ob AES installiert (0=nein)
TRAPS: ; Adressen der Original-GEMOOS-Trap-Handler
dc.l 0 ; 8(TRAPS) GEMDOS
dc.l 0 ; 4(TRAPS) AES/UDI
dc.l 0 ; 8(TRAPS) BIOS
dc.l 0 ; 12(TRAPS) XBIOS
LINE_A: dc.l 0 ; ^LINE A-Block
PBLOCK: dc.l 0 ; Speicher für VDI-Parameterblock
VEC_BASE: dc.l 0 ; ^Verarbeitungsroutine für Zeichen
BEL_ADR: dc.l 0 ; Speicher für Adresse der Bimmel-Routine
TCB: ; Terminal Control Block
dc.w 0 ; 0(TCB) akt. Spalte
dc.w 0 ; 2(TCB) akt. Zeile
dc.w 79 ; 4(TCB) größte Spalte
dc.w 24 ; 6(TCB) größte Zeile
dc.b %0000 ; 8(TCB) Bit 0=1: Invertieren eingeschaltet
; Bit 1=1: Wrapping eingeschaltet
; Bit 2=1: Unterstrich eingeschaltet
; Bit 3=1: Halbe Helligkeit eingeschaltet
dc.b 0 ; 9(TCB) Flag für Grafik (0=aus, -1=ein)
dc.w 0 ; 10(TCB) Anzahl gespeicherter Cursorpositionen
dc.w 0.0 ; 1. gespeicherte Position (x,y)
dc.w 0,0 ; 2. gespeicherte Position (x,y)
dc.w 0,0 ; 3. gespeicherte Position (x,y)
dc.l 0 ; 24(TCB) ^8x16 Font (GEM)
dc.l 0 ; 28(TCB) ^8x8 Font (GEM)
dc.l 0 ; 32(TCB) ^aktueller Font
dc.w 16 ; 36(TCB) Höhe eines Zeichens in Pixels
dc.w 16*80 : 38(TCB) Bytes pro Textzeile
; *** GRAFIK-VARIABLEN: ***
dc.w 0 ; 40(TCB) Anzahl möglicher horizontaler Bytes
dc.w 0 ; 42(TCB) Anzahl möglicher Pixelzeilen
dc.w 0 ; 44(TCB) Grafikbreite (in Bytes)
dc.w 0 ; 46(TCB) Grafikhöhe (in Pixels)
dc.w 0 ; 48(TCB) Zähler (horizontal)
dc.w 0 ; 50(TCB) Zähler (Pixelzeilen)
dc.l 0 ; 52(TCB) linker Offset (abs.) für Pixelzeile
TABS: ds.w 5,$8080; Bitvektor für Tabulatoren
CCB: dc.b %0010 ; Cursor Control/Status Block
; Bit 0=1: Cursor enable (für IRR)
; Bit 1=1: Cursor darf blinken
; Bit 2=1: Cursorposition invertiert
; Bit 3=1: Cursor eingeschaltet
dc.b 0 ; 1(CCB) Anzahl gespeicherter CUR_0FFs (ESC T)
dc.w 20 ; 2(CCB) Blinkrate
dc.w 20 ; 4(CCB) Zähler für Blinkrate
dc.l 0 ; 6(CCB) Cursorposition (absolut)
end
Listing 1: Der vierte Teil des xVT52-Emulators (Ende)
' *** xVT52/DOODLE-Konuerter ***
'
e$=CHR$(27)
a$=SPACE$(32000)
BLOAD "eindat.doo",VARPTR(a$)
a$=e$*"f"+e$+"rp$ "+a$+e$+"a"
BSAVE "ausdat.dat",VARPTR(a$),LEN(a$)
'
' Für ’eindat.doo' bzw. ausdat.dat'
' sind die entsprechenden Namen der
' gewünschten Dateien zusetzten.
Listing 2: Dieses Progrämmchen wandelt DOODLE-Hardcopies für xVT52 um, so daß sie direkt vom Desktop angezeigt werden können.
GEMDOS:
movea.l a7,a0 ; A0=SSP:
btst #5,(a0) ; if (Aufruf aus S-Mode)
beq.s \from_user
addq.l #6,a0 ; then Offset addieren (PC.L+SR.W)
bra.s \test_pline
\from_user:
move.l USP,a0 ; else User Stack benutzen;
\test_pline:
movem.1 a0,-(a7) ; A0 retten
lea REDIR,a0 ; Zeiger auf Flag für Ausgabe-Umlenkung
tst.w (a0) ; gesetzt?
movem.l (a7)+,a0 ; AB zurück, keine CCR-Beeinflussung
beq.s \test_gdos ; keine Umlenkung ~> auf Ausgabefunktionen prüfen
cmpi.w #$46,(a0) ; FORCE-Aufruf?
bne \orig ; nein
cmpi.w #1,2(a0) ; Umlenkung auf Stdout?
bne \orig ; nein
lea REDIR,a0 ; sonst Flag wieder
clr.w (a0) ; auf 0 setzen
bra \orig ; ab ins GEMDOS
\test_gdos:
cmpi.w #9,(a0) ; if (Funktion == PRINT LINE)
beq.s \pline ; then eigene Funktion benutzen
cmpi.w #64,(a0) ; else if (Funktion == WRITE)
beq.s \write ; then auf Ausgabe-Kanal prüfen
cmpi.w #2,(a0) ; else if (Funktion == CCONOUT)
beq.s Vcconout ; then eigene Funktion benutzen
cmpi.w #6,(a0) ; else if (Funktion != CRAUIO)
bne.s \test_force ; then auf force() testen
cmpi.b #-1,3(a0) ; else if (Zeichen == 255)
beq.s \orig ; then Original aufrufen
\test_force:
cmpi.w #$46,(a0) ; FORCE-Aufruf?
bne.s \orig ; nein
cmpi.w #1,2(a0) ; wird auf Stdout umgelenkt?
bne.s \orig ; nein
lea REDIR,a0 ; sonst Umlenkungs-Flag
st (a0) ; setzen
bra.s \orig ; und GEMDOS aufrufen
\cconout:
bsr SAVE_REGS ; Register retten
bsr CON_OUT ; Zeichen ausgeben
bra REST_REGS ; Register zurück, fertig
\pline:
bsr SAVE_REGS ; Register retten
bsr WRITE ; String ausgeben
bra REST_REG5 ; Register zurück, fertig
\orig:
move.l TRAPS,a0 ; else Originalroutine
jmp (a0) ; benutzen
\write:
cmpi.w #1,2(a0) ; Ausgabe auf CON:?
bne.s \orig ; nein, harn wa nix mit zu tun
addq.w #2,a0 ; sonst Anzahl auszugebender Zeichen
move.l 2(a0),d1 ; holen
beq.s \retour ; kein Zeichen ist definitiu zu wenig!
bsr SAVE_REGS ; ansonsten Register retten
addq.w #4,a7 ; (a7) = String-Pointer
move.l d1,-(a7) ; Anzahl auf Stack retten
move.l 4(a7),-(a7) ; String-Pointer auf Stack legen
\lp:
move.l (a7),a0 ; String-Pointer holen
addq.l #1,(a7) ; auf nächstes Zeichen zeigen lassen
move.b (a8),d1 ; Zeichen holen
and.w #$FF,d1 ; nur L5B beachten
move.w d1,-(a7) ; Zeichen auf Stack legen
bsr CON_OUT ; und ausgeben
addq.w #2,a7 ; Stack korrigieren
subq.l #1.4(a7) ; Anzahl Zeichen dekrementieren
bne.s \lp ; und nächstes Zeichen ausgeben
addq.l #8,a7 ; Zähler und Pointer loschen
bra REST_REGS ; Register restaurieren und zurückspringen
\retour:
rte
Listing 3: Neuer GEMDOS-Trap-Handler für xVT52 zum Abfangen von Ein-/Ausgabeumlenkungen
;CRS_IRR: ; folgende Zeilen bitte einfügen (s. Text)
lea AES_OK,a1 ; ^Flag laden
tst.w (a1) ; AES aktiv?
bne.s \aes_ok ; ja, normal weitermachen
lea TRAPS+4,a2 ; ^alter AES/VDI-Vektor
move.l $88,a0 ; aktueller AES/VDI-Uektor
cmpa.l (a2),a0 ; noch derselbe?
beq.s \irr_end ; ja, GEM noch nicht initialisiert
move.l a0,(a2) ; sonst Originaladresse AES/VDI merken
st (a1) ; Flag setzen (für: AES aktiv)
lea AES_VDI,a0 ; und eigene Routine
move.l a0,$88 ; installieren
bra.s \irr_end ; fertig, ab jetzt geht's normal weiter
\aes.ok:
; ab hier geht es wieder mit Zeile 310 ff ("move.l $88,d1") weiter
Listing 4: Der “GEM-Schlüssel” zum AUTO-Ordner-Problem
' *** xVT52-Demobild-Generator ***
'
e$=CHR$(27)
d$="*****************"
a$=e$+"f"+e$+"E"+e$+"Y"+CHR$(32+11)+CHR$(32+52)+e$+"p"+d$
a$=a$+e$+"Y"+CHR$(32+12)+CHR$(32)
a$=a$+"* "+e$+"RxVT52"+e$+"S *"
a$=a$+e$+"Y"+CHR$(32+13)+CHR$(32*56)
a$=a$+"* "+e$+"G"+e$+"j---------------------"
a$=a$+e$+"k"+CHR$(10)+"® 1988 M.Schumacher"+e$+"F *" a$=a$+e$+"Y"+CHR$(32+14)+CHR$(32+4)+d$+e$+"q"
FOR i=1 TO 56*8
a$=a$+e$+"Y"+CHR$(32+11)+CHR$(32)+e$+"U"
IF EVEN(i)
a$=a$+e$+"Y"+CHR$(32+12)+CHR$(32)+e$+"U" a$=a$+e$+"Y"+CHR$(32+13)+CHR$(32)+e$+"T"
ENDIF
a$=a$+e$+"Y"+CHR$(32+14)+CHR$(32)+e$+"T"
NEXT i
a$=a$+e$+"Y"*CHR$(32+24)+CHR$(32)
a$=a$+STRING$(14,e$+"Z")+e$+"a"
OPEN "O",#1,"demobild.dat"
PRINT #1,a$;
Listing 5: “Testbild”-Generator für xVT52