GFA-Basic ruft Maschinencode, bitte kommen...

...Verstanden. Antworten...

Wenn es nur so einfach gehen würde, dachte ich mir schon oft, als ich die beiden GFA-Basic-Befehle CALL und C: als Schnittstelle für kleine Maschinenprogramme benutzen wollte. Doch die vorhandene Literatur (Benutzer-Handbuch, GFA-Basic-Buch von F. Ostrowski, Data Becker’s GFA-Buch, diverse Zeitschriften) schweigen sich beharrlich über dieses Thema aus, vorallem was den Befehl CALL betrifft.

Der parameterlose Aufruf mittels CALL bietet ja keine Probleme, also rufe ich meine Prozedur so auf, nachdem ich alle Zahlen und Strings mühsam gepoked habe. Nun, dies mag wohl für kleine Testprocedu-ren hinhalten, doch auf die Dauer ist sowas kein Programmierstil und dazu noch kaum lesbar!

Jetzt aber zur Sache! Als erstes widme ich mich dem C:-Aufruf, da dieser einfacher zu handhaben ist. Er ist weniger komplex als der CALL-Aufruf, dafür können jedoch keine Strings übergeben werden.

Mit dem C:-Aufruf können (keiner oder mehrere) Parameter übergeben werden. Als Parametertypen sind erlaubt: Integer-, Fließkomma- und bool’-sche Zahlen als Variablen sowie auch als Direktwerte (siehe Bild 1). Die Parameter werden standardmäßig in 16-Bit-Integer-Zahlen konvertiert und auf den Stapel geschoben. Wenn vor einem Parameter der Zusatz L: steht, wird der 3 2-Bit Wert berechnet und auf dem Stapel in der üblichen Weise abgelegt. Der erste Wert (jener ganz links in der Parameterliste) liegt auf dem Stapel ab 4(SP), der zweite ab 6(SP) wenn der erste Wortlänge hatte, sonst ab 8 (SP) usw. Bei (SP), also an der Spitze des Stapels, liegt die Langwortadresse für den Rücksprung, denn die Maschinenroutine wird intern mit JSR (A0) aufgerufen. Doch dies betrifft uns bei der Programmierung nur insofern, als diese Adresse nicht verändert werden darf und der Stapelzeiger den gleichen Wert hat wie beim Aufruf der Routine (das Gleiche gilt für CALL). Somit dürfte auch klar sein, daß das ganze Programm mit RTS (&H4E75) abgeschlossen werden muß.

Bild 1 Bei dem C-Aufruf sind folgende Parametertypen erlaubt: Integer-Fließkomma-und bool’-sche Zahlen als Variablen sowie auch als Direktwerte.

Ist die Routine abgearbeitet, wird der im Datenregister 0 gespeicherte Langwort-Wert ans Basic zurückgegeben. Auf diese Weise kann - wie bei einem Funktionsaufruf - ein Wert an eine Variable übergeben werden oder, falls nicht benötigt, mittels der Pseudo-Variablen Void ignoriert werden.

Sollte eine Problemstellung mehrere Rückgabeparameter erfordern, so kann mit dieser Möglichkeit in DO die Adresse eines Speicherblockes übergeben werden, wo sich die entsprechenden Daten befinden.

Werden keine Parameter von der Prozedur benötigt, muß trotzdem eine leere Parameterliste da sein, (z.B. Void C:Versuch()), im Gegensatz zu Call, das dann so aussähe: Call Versuch

Ein Tip noch: Hat die Prozedure Parameter, so ist am Anfang der erste Wert in DO abgelegt. Der Aufruf Print C:Test(123) hätte also, würde das Programm nur aus RTS bestehen, die Ausgabe ”123” zur Folge.

Damit wäre mal die erste Möglichkeit ein wenig ausgeleuchtet. Diese Funktion können Sie noch am Beispiel der Laufschrift näher studieren.

Nun zum CALL-Aufruf. Als er stes sei mal gesagt, daß CALL in der aktuell vorliegenden GFA-Basic Version 2 nicht fehlerfrei implementiert ist und deshalb nur bedingt verwendbar ist. So kommt es bei mir z.B. regelmäßig zu einem Absturz, wenn ich CALL mit einem direkten String C’Hallo”) als Parameter aufrufe und danach in den Direktmodus schalte (mit der ESC-Taste). Die Maus reagiert noch, doch hängt sich scheinbar der Rest der Tastatur auf. Wird aber nicht in den Direktmodus gegangen, oder erst, nachdem das Programm mit NEW gelöscht worden ist, ist nichts zu befürchten. Auch bei Aufrufen mit numerischen Parametern sind bisher noch keine Störungen aufgetreten.

Trotzdem ist es mir nach einigen Anläufen gelungen, das Geheimnis dieses Befehles zu lüften.

Die Daten werden z.B. folgendermaßen vom Stapel geholt: (z.B.

Ret%=G:Start(Anzahl!lu.L:Adresse%,Offset%) )

START:	MOVEW	4(SP),D1	* =	Anzahl%
		MOVE.L	6(SP),A0	* = Adresse%
		ADDA.L	A(SP),A0	* = Offset%
			.
			.
			.
		MOVE.L	A0,D0		*Ret% := A0
		RTS
Bild 2: Die numerischen Parameter werden analog zum C-Aufruf in Integerzahlen konvertiert. Der Unterschied liegt darin, daß hier alle Werte auf einer Breite von 32 Bit umgewandelt werden.

Der Call Befehl ist nicht mit einer Funktion vergleichbar, sondern er führt das Programm ohne Echo aus. Die Parameterübergabe hingegen ist universeller als bei C:. Es sind grundsätzlich alle eindimensionalen Parameter erlaubt, also zusätzlich zu jenen von C: auch noch Strings (als Variable sowie als Direktwert). Es muß jedoch jedes Mal ein sogenannter Dummywert an die Parameterliste gehängt werden, da der letzte Wert dieser Liste vom Interpreter listigerweise einfach ignoriert wird. Dafür wird meines Erachtens völlig unnötig als erster Wert die Adresse der Prozedur noch in der Werteliste im Speicher abgelegt. Diese bei den Verhaltensweisen des Interpreters sind wahrscheinlich auf die falsche Programmierung eines Laufindexes zurückzuführen. Gut, diesen Fehler können wir nun relativ einfach umgehen mit der besagten "Dummy-Strategie”.

Beim Aufruf sieht der Stapel wie folgt aus: Zuerst kommt die Langwortrücksprungadresse, darauf folgt bei 4 (SP) ein Wort, welches die Anzahl Parameter (inclusiv Dummy Wert) angibt, und zu guter Letzt folgt bei 6 (SP) die Langwort adresse, die auf einen Speicher bereich zeigt, wo die Werte abgelegt sind. Dort steht zuerst die Adresse der Routine. Darauf folgen die Parameter. Jeder Parameter belegt vier Byte im Block (außer dem Dummy, der sich mit NULL BYTE begnügt). Die numerischen Parameter werden analog zu C: in Integerzahlen konvertiert, nur geschieht das hier mit allen Werten auf einer Breite von 32 Bit (siehe Bild 2).

Für die Strings ist in ihren vier Bytes die Adresse abgelegt, ab welcher der String zu finden ist. Doch hier beginnt nun der eigentliche Haken der ganzen Sache. Ist die Länge des Strings gerade, folgt auf das letzte Zeichen ein Null-Byte (auf einer geraden Adresse). Ist die Länge jedoch ungerade, folgt auf das letzte Zeichen ein zufälliger Wert und erst darauf das Null Byte an der geraden Adresse. Also kann die wirkliche Länge des Strings beim besten Willen daraus nicht ersehen werden. Für Strings, die in Form einer Variablen übergeben werden, kann die Länge auf andere Art bestimmt werden. Auf das Null-Byte folgt eine Drei Byte Adresse (mit dem führenden Null-Byte zusammen ergibt das eine Langwortadresse, doch die oberen acht Bit sind nicht nötig für die Adressierung, da der Adressbus sowieso nur 24 Bit breit ist). Diese Adresse zeigt auf einen sechs Byte umfassenden Block. Sie ist dieselbe, die bei der Basic-Funk tion Arrptr(StringS) zurückgegeben wird. Die ersten vier Bytes zeigen wiederum auf den String, was der Funktion Varptr(String$) entspricht. Die nächsten beiden Bytes geben die Länge des Strings wieder (siehe Bild 3).

Bild 3 Die ersten vier Bytes zeigen auf den String zeiger. Die nächsten beiden By tes geben die Länge des Strings wieder.

Die Stringeingabe kann man aber trotzdem auf verschiedene Weise absturzsicher implementieren.

Es könnte z.B. vor jedem String die Länge übergeben werden, oder die Strings dürfen nicht als Direktwert angegeben werden. Ein anderer Weg, der auch mit C: begangen werden könnte, ist die Übergabe von Arrptr(String$). Damit ist die Adresse des Strings und die Länge bekannt.

Eigentlich schade, daß ein so flexibler Befehl wie der Call-Befehl im Sumpf der Programmierfehler verelendet. Scheinbar hat noch kaum jemand ernsthaft mit diesem Befehl programmiert, sonst wäre wohl auch mehr darüber in der Literatur nachzulesen gewesen. Wer weiß, ob Frank Ostrowski von diesen fehlerhaften Befehl schon etwas vernommen hat. Das soll kein Vorwurf an den Programmierer dieses sonst brillianten und höchst zuverlässigen Basics sein! Der Call Befehl ist vielleicht auch in Vergessenheit geraten, weil die Alternative im C:-Befehl existiert, und weil dieser einfacher zu handhaben ist und vorallem auch noch die Möglichkeit der Parameterübergabe ans Basic zurück vorsieht.

Ein Nachtrag noch zur Konvertierung der Fließkommazahlen in Integer:

Die Funktion Int(Wert) entspricht nicht der Basic-Funk-tion Int! Bei der Basic-Funktion wird aus -4.3-5 = Int(-4.3). Bei der Funktion der Konversion wird daraus -4 — Int(-4.3).

Basic-Erweiterung selbst gestrickt

Nun will ich als kleine Demonstration der beiden Befehle eine kleine Basic-Erweiterung anregen, damit jederman seine eigenen Ideen verwirklichen kann.

Beim Beispiel handelt es sich um die Programmierung einer Rollschrift. Das Programm ist in zwei Teile gegliedert: in eine Initialisierung (wo die Koordinaten der zu rollenden Box gespeichert werden) und in den eigentlichen Roll Befehl (wo die Anzahl zu rollender Bits und ein Synchronisationswert, der es erlaubt, die Verzerrung auf ein Minimum zu reduzieren, eingegeben werden). Die Funktion läuft nur auf Monochrom Monitor, da direkt auf den Bildschirm-Ram zugegriffen wird. In der Prozedur wurde bewußt auf ’ Vsync’ verzichtet, da dies erstens eine enorme Verzögerung mit sich bringt und zweitens je nach Boxgröße unschöne Verzerrungen gibt. Stattdessen kann mit einem Zahlenwert nach jedem Bitschritt eine entsprechende Warteschleife durchlaufen werden, so daß die Rotation mit dem Strahlrücklauf synchronisiert werden kann. Dadurch wird die Verzerrung zwar nicht verschwinden, doch sie kann damit in einen Bereich 'verschoben’ werden, wo sie nicht stört ober garnicht sichtbar ist. Das Demo-Programm (Listing 3) zeigt, wie der Abgleich ablaufen könnte. Aber wie immer: experimentieren ist erlaubt!

Die Initialisierung geht folgendermaßen:

Bit% = C:Scrollinit%(X,Y,X1,Y1)

Wobei X/Y die obere linke, X1/ Y1 die untere rechte Ecke ist. Die Werte X und X1 werden auf Vielfache von 16 gerundet, daraus wird die Anzahl der Bits pro Zeile berechnet und nach Bit% übergeben (siehe Bild 6). Die Parameter werden der Einfachheit halber nicht auf den gültigen Bereich hin überprüft (siehe Bild 4).

Bild 4 Ein sauberer Überblick über das gesamte ’Scrollinit’ Procedure.

Ein Bereichskonflikt ist im allgemeinen mit einem Absturz verbunden!

Der wiederkehrende Aufruf lautet: (siehe Bild 5)

Bild 5 Die Procedure, die das Bild 'laufen’ läßt, ist hier zu sehen.

Call Scroll%(Anzahl%,Abgleich%,Dummy)

Anzahl% gibt die Anzahl Bit an, die mit diesem Aufruf gerollt werden sollen, und Abgleich% ist der Synchronisationswert (positiv!).

Nun wünsche ich allen viel Spaß beim Erweitern des GFA-Basics und vielleicht hören wir auch mal von Ihnen etwas über eine 'selbst gestrickte’ Erweiterung!

Bild 6 Die Berechnung der Box, deren Inhalt 'Scrolling’ sein soll, wird sehr raffiniert gemacht.

Listing 1: Die Assembler-Routine

Listing 2: Diese Routine schreibt das Scroll-Programm auf Diskette

Open "O”,#1,"SCROLL.PRG"	!	Name der Datei
Clr Test%	!	Check-Summe
Do	!	Einleseschleife
	Read A$
	Exit If A$="***"	!	Abbruchbedingung
	A%=Val("&H"-A$)
	Print #1,Mki$(A%);	!	als 2-Byte-Wert speichern
	Add Test%,A%		!	Summieren fuer Test
Loop
Close #1				!	korrekt schliessen
Read A$					!	Check-Summe	lesen
If Val ("&H"+A$)<>Test%	!	vergleichen	und reagieren
	Kill "SCROLL.PRO"
	Print " Ein Datenfehler ist aufgetreten! (Datei geloescht)“
Else
	Print " Alle Daten sind korrekt unter 'SCROLL.PRO gespeichert!"
Endif
End
'
Daten:					! Daten
Data 3F3C,0003,4E4E,548F,2040,43FA,004A,302F 
Data 8008,E848,322F,0004,E849,9041,E349,44C1 
Data 01C1,3200,8640,6FFF,3348,0020,E349,3341 
Data 0026,302F,0006,CBFC,0050,D1C0,2348.0014 
Data 302F,000A,906F,0006,5340,3340,001A,3001 
Data E748,44C0,4E75,226F,0006,2429,0004.5382 
Data 2629,0008,51CB,FFFE,207C,0007,8000,323C 
Data 000A,3010,E350,7827,D1FC,0000,0050,E5E0 
Data 51C8,FFFC,D1FC,0000,0050,51C9,FFE6,51CA 
Data FFD0,4E75 
Data ***
Data 001804C1

Listing 3: Demonstrationsprogramm

Dim Scroll%(36)	!	Platz fuer das Programm
Scrollinit%=Varptr(Scroll%(0))	! Start des Initialisierungsprogrammes 
Scroll%=Scrollinit%+86			! und	des Scroll-Programmes
Bload "SCROLL.PRO",Scrollinit%	! Das Programm laden
'
Pbox 29,10,610,38				! Kleine Testgrafik zeichnen
Deffill 0
Pbox 32,13.607,35
Deffill 1
Print At(11 #2);ChrS(189)1987 by Andreas Gieriet, Kirchstrasse 6, CH-7402 Bonaduz"
'
Laenge%=C:Scrollinit%(32,18,608,32)	!	Initialisierungsaufruf
'
Hidem		! Maus verschwindet, damit es keinen 
Abgleich%=0	! Kollision gibt
Print At(15,10);"Abgleich mit :"
Do
	Print At(33,10);Abgleich%
	Vsync	! Nicht noetig
	Call Scroll%(Laenge,Abgleich%,0)	!	Mit verschiedenen Abgleichwerten 
	Pause 20	!	eine ganze Breite rollen
	Add Abgleich%,100	!	Naechster Abgleichwert
Loop		! Abbruch nur mit CONTROL/SHIFT/ALTERNATE
Showm		! Maus wieder einschalten
End

Andreas Gieriet
Aus: ST-Computer 01 / 1988, Seite 84

Links

Copyright-Bestimmungen: siehe Über diese Seite