Extended VT52-Emulator Teil 4

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.

Gut geDOODLEt - Grafik per ESCape-Sequenz

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

Beispiel

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!

Grafik und DFÜ

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.

Trouble-Shooting

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.

Patch as patch can...

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).

/AUTO/XVT52.PRG

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ß...

Wenn zwei das gleiche tun...

...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.

Rev it up...

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.

MOTOROLA - Moto-Roller

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!

Alle Register ziehen

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...

Relativitätstheorie

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).

Weniger ist mehr

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?

Bye bye

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



Aus: ST-Computer 07 / 1988, Seite 58

Links

Copyright-Bestimmungen: siehe Über diese Seite