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



Links

Copyright-Bestimmungen: siehe Über diese Seite
Classic Computer Magazines
[ Join Now | Ring Hub | Random | << Prev | Next >> ]