★ CODING ★ Der Gläsernen CPC ★ |
Gläsernen CPC (CPC Amstrad International) |
In den bisherigen Folgen des "Gläsernen CPC” haben wir uns intensiv damit beschäftigt, die inneren Strukturen des CPC 464 zu durchleuchten, und dabei einige Möglichkeiten entdeckt, die ohnehin schon sehr umfangreichen Fähigkeiten dieses Computers noch beträchtlich zu erweitern. Dabei war uns. insbesondere das Betriebssystem behilflich, ein Maschinenprogramm, das bereits fest im CPC eingebaut ist und etwa 16 KByte ROM-Speicherplatz beansprucht. Es stellt alle notwendigen Kontakte zur Peripherie her (Bildschirm. Tastatur, Kassettenrekorder usw.) und erledigt zahlreiche interne Verwaltungsauf-gaben. Besonders für den Assembler-Programmierer stellt es deshalb eine nahezu unerschöpfliche Quelle nützlicher Unterprogramme dar. die ihm eine Menge Arbeit abnehmen - ohne eine genaue Kenntnis des Betriebssystems ist eine effektive Programmierung kaum denkbar. Neben dem Betriebssystem enthält der CPC aber noch weitere 16 KByte ROM. die vom Basicinterpreter belegt werden. Es stellt sich natürlich die Frage, ob hier nicht ebenfalls interessante oder hilfreiche Routinen zu finden sind. Und damit wären wir beim Thema dieser Folge angelangt: Wir werden erforschen, was dieser bisher recht unbekannte Bereich des CPC 464 so alles hergibt. Um die Erwartungen nicht zu hoch zu schrauben, sollen aber zunächst einige Probleme erwähnt werden, die einem unvermeidlich begegnen, wenn man in dieses sehr komplexe und umfangreiche Programm einsteigt. Während das vorbildlich strukturierte Betriebssystem des CPC über "genormte” Einsprungstellen mit genau definierten Übergabeparametern verfügt, ist der Interpreter für eine externe Benutzung nicht im geringsten vorbereitet. Dem neugierigen Programmierer bleibt es deshalb nicht erspart, sich ein möglichst gut kommentiertes ROM-Listing vorzunehmen und selber Z80-Prozessor zu spielen, bis er genau weiß, an welcher Stelle welche Informationen in welchen Registern zu finden sind. Dabei stellt sich dann schnell heraus, daß der Interpreter im Gegensatz zum Betriebssystem schon ein sehr spezialisiertes Programm ist. Es hat eben hauptsächlich die Aufgabe. Basic-Befehle zu identifizieren und sie mit Hilfe entsprechender Maschinencode-Sequenzen auszuführen. die wiederum - wie sollte es auch anders sein - zu einem großen Teil vom Betriebssystem zur Verfügung gestellt werden. Deshalb findet man hier kaum Material. das allgemein anwendbar ist -vielleicht ein paar Routinen zur Behandlung von Tabellen, aber das wär's dann schon im wesentlichen. Ganz anders sieht die Angelegenheit jedoch aus. wenn man noch zusätzliche oder erweiterte Basic-Befehle einbauen möchte. Für derartige Projekte stellt der Interpreter natürlich eine Menge wichtiger Unterprogramme zur Verfügung- man muß nurwissen, wo sie sich befinden und was sie genau bewirken. Doch um es gleich klarzustellen: Wir sprechen hier nicht von den üblichen RSX-Kommandos. So bequem diese Methode der Befehlserweiterung auch sein mag, sie bringt doch einige unschöne Einschränkungen mit sich. Abgesehen von dem lästigen Strich vor dem Befehlswort können String-und Fließkommavariablen nur recht umständlich mit Hilfe des Variablen-poinlers (der "Klammeraffenfunktion") behandelt werden, und eine Realisierung von Basic-Funktionen. wie vergleichsweise SIN oder MID$. ist überhaupt nicht möglich. Deshalb möchten wir Ihnen in dieser Folge die notwendige Basis-Software nebst einigen Hintergrundinformationen und Beispielen liefern, um "echte" Basicerweiterungen zu schreiben. und ehrlich gesagt: Wir sind schon sehr gespannt darauf, was die vielen talentierten Assembler-Programmierer unter unseren Lesern daraus machen werden. Wäre es nicht z.B. eine reizvolle Aufgabe, das Basic des CPC so umzustricken, daß für einen anderen Computer geschriebene Programme ohne große Änderungen direkt übernommen werden können? Doch bevor wir dazu kommen, wie so etwas im Prinzip möglich ist, werden wir etwas Grundlagenforschung betreiben. Was geht zum Beispiel im CPC vor. wenn man die Zeile PRINT PEEK(&ACA4)/code]eingibt und dann mit ENTER abschließt? &ACA4 ist die Startadresse eines Eingabepuffers, in dem sich der CPC 464 zunächst einmal alle Zeichen merkt, die Sie über die Tastatur eingeben. Das obige Kommando wird also den ASC'II-Code 80 für den Buchstaben "P” (bzw. 112 für "p") zurückgcben, und PRINT PEEK(&ACA5)ergibt dann entsprechend den Code für das "R” von PRINT. Den Rest können Sie sich denken: Hier steht die Zeile im Klartext. Die eigentliche Arbeit beginnt für den Interpreter erst, nachdem Sie ENTER gedrückt haben. Die Zeile wird daraufhin in einen zweiten Puffer ab Adresse &0040 übertragen und dabei in den Interpretercode umgewandelt. der eine besonders schnelle und effektive Programmausführung ermöglicht. Schauen wir uns einmal an, wie das aussieht: PRINT HEX$(PEEK(&0040))ergibt "BF”, und dieser hexadezimale Code ist in der Tat alles, was von unserem PRINT-Befehl noch übrig geblieben ist. Der CPC hat nämlich inzwischen jedes Wort der Eingabezeile mit Hilfe einer umfangreichen Tabelle überprüft und getestet, ob es sich um einen gültigen Befehl handelt. Ist das wie bei PRINT der Fall, so wird er durch ein einziges Kennbyte ersetzt, das sogenannte "Token". Und jetzt weiter: An der nächsten Adresse (&0041) finden wir den Wert &20. den ASC'II-Code für das Leerzeichen nach dem PRINT-Befehl. und dann &FF - sollte das etwa das Token für HEXS sein? Nein, leider falsch geraten! Dieser Code besagt nur. daß jetzt eine Basic-Funktion folgt, und das eigentliche Kennbyte für HEXS. nämlich &73, befindet sich eine Stelle weiter an der Adresse &0043. Damit haben wir schon einige wichtige Informationen gewonnen, die hier noch durch ein paar Fakten ergänzt werden sollen: Alle Kommandos und Operatoren erscheinen im Interpretercode als ein Kennbyte im Bereich zwischen &80 und &FE (dezimal 128 - 254). Funktionen dagegen erhalten einen Wert zwischen &00 und &79 (dezimal 0- 127). Um sie bei der Ausführung von normalen ASCII-Zeichen, Zahlen und Variablen zu unterscheiden, werden sie Verfolgen wir aber den Weg unserer Eingabezeile noch ein Stück weiter: Was passiert nach der Umwandlung in den Interpretercode? Das hängt davon ab, ob Sie eine Zeile mit oder ohne Zeilennummer eingegeben haben. Findet der CPC keine Zahl am Anfang, so wird die Zeile auf der Stelle ausgeführt, und das geht so: Die Anfangsadresse des Puffers wird einer zentralen Routine des Interpreters als Programmzeiger übergeben, der "Interpreterschleife", die nun die codierten Befehle nacheinander liest, aus den Kennbytes die Adressen der zuständigen Unterprogramme berechnet und sie aufruft. Der Programmzeiger wandert dabei Byte für Byte durch die Zeile, bis er auf eine 0 als Endmarkierung trifft, und dann geht's zurück in den Direktmodus: Ready! Wenn Sie jedoch eine Zeile mit Zeilennummer eingeben, so geschieht etwas ganz anderes: Der Inhalt des Puffers ab Adresse &0040 wird in den Programmspeicher kopiert und an der richtigen Stelle einsortiert. Löschen Sie jetzt bitte den Speicher mit NEW und geben Sie dann 10 PRINTein. damit wir mit der folgenden Direkteingabe untersuchen können, wie das aussieht: FOR adr=&170 TO & 175:PRINT HEX$ (PEEK(adr),2);” ”;:NEXTwobei &170 die Anfangsadresse des Speicherbereichs für Basicprogramme ist. Aufdem Bildschirm sollte jetzt folgende Anzeige erscheinen: 06 00 0A 00 BF 00Zu Beginn steht hierein 2-Byte-Wert, der die Länge der Zeile angibt, also insgesamt 6 Bytes. Beachten Sie bitte, daß wie üblich zuerst das Lowbyte und dann das Highbyte der Zahl angegeben wird! Danach folgt dann ein weiterer Wert, der entsprechend die Zeilennummer enthält (&0A=10), und danach das inzwischen bekannte Token &BF für PRINT. Den Abschluß bildet die bereits erwähnte Endmarkierung, ein Nullbyte. Und jetzt ein kleiner Versuch: Ersetzen wir doch einmal das PRINT-Token durch einen anderen Wert, zum Beispiel &8A: POKE &174,&8AUnd wenn Sie jetzt die Zeile LISTen, steht dort wahrhaftig nicht mehr 10 PRINT, sondern 10 CLS, womit Sie also wissen, welches Token zu diesem Befehl gehört. Übrigens gibt es beim CPC 464 ein paar unbenutzte Kennbytes, die keinem bestimmten Befehl zugeordnet sind. Eines von dieser Sorte ist der Code &E0, der für unsere Befehlserweiterung noch eine sehr wichtige Rolle spielen wird. Doch zunächst werden wir den armen CPC damit in eine teuflische Zwickmühle bringen: Den unendlichen Syntax-Error! Setzen wir also den Code in die Programmzeile ein: POKE &174,&E0Wenn Sie jetzt das Programm mit RUN starten, passiert folgendes: Der CPC findet das Token &E0. aber keinen dazu passenden Befehl, den er ausführen könnte. Aha, denkt ersieh, das kann nur ein Syntax-Error sein! Also wird das Programm abgebrochen, die Meldung ausgegeben, und dann soll wie üblich die fehlerhafte Zeile angezeigt werden. Doch halt -schon wieder ein Problem! Leider gibt es für &E0 kein Befehlswort, das man anzeigen könnte, und in seiner Verzweiflung fällt dem Computer nichts Besseres ein, als nochmals einen Syntax-Error auszugeben, womit sich der Teufelskreis schließt. Die einzige Möglichkeit, dieses Spiel zu beenden, ist ein totaler Reset mit CTRL-SHIFT-ESC. Nach diesem famosen Absturz können wir uns jetzt ein paar Gedanken darum machen, wie eine Befehlserweiterung realisiert wird. Wie unsere Experimente gezeigt haben, gibt es also drei kritische Stellen, an denen ein Eingriff vorgenommen werden muß:
Möglich werden die Eingriffe dadurch, daß der Interpreter an einigen wichtigen Stellen Unterprogramme im RAM aufruft, die normalerweise nur aus einem RET-Befehl bestehen, also keine Auswirkungen haben. Solche "Patchs” werden gerne eingebaut, um noch nachträglich Anpassungen oder Korrekturen vornehmen zu können, ohne daß gleich das ganze Programm umgeschrieben werden muß. Bei der Entwicklung des CPC 464 wurde diese Möglichkeit wohl vorgesehen, um den Anwender mit einer Software-Korrektur trösten zu können, falls sich nach der Markteinführung noch ein Fehler hcrausgestellt hätte - siehe "CHAIN MERGE" beim Floppy-Laufwerk. Beim CPC 664 und 6128 dagegen waren sich die Programmierer ihrer Sache schon wesentlich sicherer: Die "Dummy-Returns” wurden ersatzlos gestrichen, so daß wir unsere Ausführungen leider auf den CPC 464 beschränken müssen. Wie diese Patchs nun konkret ausgenutzt werden, können Sie dem Assemblerprogramm "XBASIC-Basis-routinen” entnehmen. Falls Sie über einen Assembler verfügen und damit umgehen können, sollten Sie jetzt auf der Stelle das Listing abtippen, denn wir kommen sofort zur Sache! Zunächst werden wir Ihnen die XBASIC-Routinen im einzelnen vorstellen und dabei gleich demonstrieren, wie ein neuer Befehl eingebunden wird. Er soll "BEEP” heißen und einfach nur einen kurzen Kontroll-ton erzeugen, genau wie PRINT CHR$(7). (Siehe Listing: Basicrou-tinen.) Beginnen wir also mit der ersten Routine, die mit dem Label INIT gekennzeichnet ist. Dieses Programm muß einmalig aufgerufen werden, um alle neuen Befehle und Funktionen zu initialisieren. Wenn Sie die ORG-Anweisung nicht geändert haben, geschieht das einfach von Basic aus mit CALL &A000. Damit werden die eben erwähnten "Dummy-Returns” durch Sprungbefehle ersetzt und die XBASIC-Routinen in den Interpreter eingehängt. Die INCODE-Routine wird nach der Initialisierung jedesmal vom Interpreter aufgerufen, wenn er versucht, ein Wort aus der Eingabezeile als gültigen Befehl zu identifizieren. Das ist natürlich eine gute Gelegenheit, ihn zunächst eine eigene Tabelle mit neuen Befehlsworten durchsuchen zu lassen. Das Label BEFTAB. das die Startadresse dieser Tabelle markiert, finden Sie am Ende des Listings. Damit der Interpreter das neue BEEP-Kommando akzeptiert, müssen wir hier folgende Eintragung vornehmen: BEFTAB DM ”BEE” |
|
Page précédente : Gläsernen CPC : Fill-routinen |
|