Module in GFA-BASIC

Manche kommerziellen Programme sind in einzelne Module unterteilt. Das dient der Flexibilität. Durch das Auswechseln eines Moduls ist z.B. ein anderer Drucker verwendbar. Zusätzliche Funktionen können über ein (später dazugekauftes) Modul erreicht werden. Die Module bzw. Programmteile arbeiten eng miteinander zusammen. Die Module tragen den Namen des Hauptprogramms mit einer Endung wie OVL oder einer Nummer. Ähnliches können Sie auch mit GFA-BASIC-Programmen machen!

Pfr. S. Just

Diese Modultechnik kann verschiedene Formen annehmen. Kaum als Modulmethode beschreibbar ist folgendes Verfahren: Das Hauptprogramm bearbeitet die Daten, die auf der Diskette oder Festplatte gespeichert werden. Der Anwender verläßt das Hauptprogramm und startet ein anderes Programm, z.B. das Druckprogramm. Die Daten müssen neu vom Massenspeicher eingelesen werden. Nach dem Ausdruck muß das Programm verlassen und das Hauptprogramm neu gestartet werden. Ein etwas umständliches Verfahren!

Die 2. Methode ist schon anwenderfreundlicher: Das Hauptprogramm wird verlassen, aber sofort - ohne Zutun des Anwenders - wird ein anderes Programm gestartet. Dieses liest selbständig die vorher gespeicherten Daten und druckt sie z.B. aus. Das Druckprogramm ruft nach seinem Ende sofort wieder das Hauptprogramm auf, das selbständig die Daten wieder einliest. Beide Programme verwenden zur Kommunikation SHEL_WRITE und SHEL_READ. Das ist problemlos möglich. Damit diese Befehle sofort zur Ausführung kommen können, müssen Sie nur das Programm bald nach diesen Befehlen enden lassen. Erst bei der angeblichen Rückkehr zum Desktop wertet der ATARI-ST/TT die Daten im Puffer aus.

Anders reagiert der Befehl EXEC in GFA-BASIC: Ein Programm wird sofort ausgeführt. Mit diesem Befehl wurde das im folgenden beschriebene Verfahren verwirklicht: Das Hauptprogramm, in GFA-BASIC geschrieben, ruft verschiedene/ ein anderes Programm - auch in GFA-BASIC geschrieben - auf über EXEC. In der Kommandozeile wird eine Adresse übergeben, die auf einen Speicherblock mit verschiedenen Variablen und weiteren Zeigern weist. Das aufrufende Programm samt seinen Daten bleibt im Speicher. Das aufgerufene Programm bzw. Modul liest die Adresse und greift auf die ja noch im Speicher stehenden Daten zu.

INLINE com%,96 
ABSOLUTE max_zl%,com%
ABSOLUTE drk_seit_l%,comSs+4 
ABSOLUTE zl_ptr%,com%+8 
ABSOLUTE bild_ptr%,com%+12 
ABSOLUTE drk_ptr%,com%+16 
ABSOLUTE aus_ptr%,com%+20 
ABSOLUTE drklrand%,com%+24 
ABSOLUTE drkabseit%,com%+28 
ABSOLUTE drkzlnum%,com%+32 
ABSOLUTE drkzlnum_offset%,com%+36 
ABSOLUTE drkbissp%,com%+40 
ABSOLUTE drkinter!,com%+44 
ABSOLUTE drkfrage!,com%+46 
ABSOLUTE is_tt!,com%+48 
ABSOLUTE art_ptr%,com%+50 
ABSOLUTE kopf_ptr%,com%+54 
ABSOLUTE seit_offs%,com%+58 
ABSOLUTE drkbisseit%,com%+62 
ABSOLUTE kopfzeile!,com%+66 
ABSOLUTE b_opt_ptr%,com%+68 
ABSOLUTE fnt_breit%,com%+72 
is_tt!=(tt!=TRUE) 
fnt_breit%=10 ! PICA

Listing 1


zl_ptr%={ARRPTR(zeile$())}
ADD zl_ptr%,10
bild_ptr%={ARRPTR(bildfile$())}
ADD bild_ptr%,10 
drk_ptr%={ARRPTR(drk$())}
ADD drk_ptr%,10 
aus_ptr%=ARRPTR(drkausfile$) 
art_ptr%=VARPTR(art|(1)) 
kopf_ptr%=ARRPTR(kopfzeile$) 
b_opt_ptr%=VARPTR(b_opt|(1))
ex%=EXEC(0,graf_prnt_prg$,CHR$(8)+HEX$(com%,8)+CHR$(0),"")

IF ex%<>0 THEN 
    ALERT 1,"Modul konnte nicht|ausgeführt werden!" 
ENDIF

Listing 2

c%=BASEPAGE+128 
l%=BYTE{c%} 
com$=SPACE$(l%)
FOR i%=1 TO 1%
    MID$(com$,i%,1)=CHR$(BYTE{ADD(c%,i%)}) 
NEXT i%
com%=VAL("&H"+com$)
IF com%<2048 THEN
    ALERT 3,"Kommunikation|Fehlgeschlagen!",1,"NoPrint",d%
    END
ENDIF
'
max_zl%={com%) 
drk_seit_l%={com%+4} 
zl_ptr%={com%+8} 
bild_ptr%={com%+12} 
drk_ptr%={com%+16} 
aus_ptr%={com%+20} 
drklrand%={com%+24} 
drkabseit%={com%+28} 
drkzlnum%={com%+32} 
drkzlnum_offset%={com%+36} 
drkbissp%={com%+40} 
drkinter!=BYTE{com%+44} 
drkfrage!=BYTE{com%+46} 
is_tt!=BYTE{com%+48} 
art_ptr%={com%+50} 
kopf_ptr%={com%+54} 
seit_offs%={com%+58} 
drkbisseit%={com%+62} 
kopfzeile!=BYTE{com%+66} 
b_opt_ptr%={com%+68} 
fnt_breit%={com%+72}

Listing 3

Eine wesentliche Einschränkung hat diese Methode: Die Zeichenketten, die Strings des Wirtsprogrammes, darf das Gastprogramm, das Modul nicht ändern. Erlaubt wäre höchstenfalls eine Verkürzung derZeichenketten. Beim Lesen der Zeichenketten gibt es aber keine Einschränkungen.

Bei dieser 3. Methode ist der Umweg über die Diskette bzw. die Festplatte nicht mehr nötig, was die Daten angeht. Die Daten müssen nicht geschrieben und wieder gelesen werden. Leider muß das Modul geladen werden.

Dieses Verfahren wurde für verschiedene Druckersteuerungen bei einem selbstgeschriebenen Public-Domain-Textverarbeitungsprogramm angewendet. Die Anpassung an verschiedene Drucker mit Steuercodes ist eine einfache Aufgabe. Dafür bräuchte man keine Module! Da das betreffende Programm aber Grafik und Text (fast) beliebig mischen kann, unterscheiden sich die Druckverfahren der einzelnen Druckertypen grundsätzlich. Mit verschiedenen Steuercodes ist dem nicht mehr beizukommen!

Nun etwas näher zu den Details der Lösung. Die Druckermodule brauchen sehr viele Informationen aus dem Hauptprogramm. Deswegen wurde mit INLINE ein großzügig bemessener Bereich von 96 Bytes geschaffen, der einzelne Variablen selber bzw. Zeiger auf Felder/ARRAYs enthielt. Listing 1 zeigt die Struktur dieses Kommunikations-Bereiches.

Sie sehen, wieviele Parameter mitgeteilt werden müssen! Damit vor dem Aufruf des Moduls nicht alle Werte einzeln in diesen Bereich geschrieben werden müssen, wurde mit ABSOLUTE die Variable von Anfang an in den com-Bereich gelegt. ‚Automatisch‘ wird so die Änderung wichtiger Variablen mitgeteilt.

Auf Felder und einzelne Strings kann nur über ihre Deskriptoren zugegriffen werden. Diese sind bequem mit ARRPTR erreichbar, wie Listing 2 demonstriert.

Auf die Felder art|() und b_opt|(), die den Wert für die Textattribute der Zeilen (kursiv, u.ä.) oder Optionen für die Grafiken (Dichte, Größe) enthalten, kann über VARPTR zugegriffen werden. Alle Werte stehen in diesen Arrays hintereinander, in diesem Fall im Abstand von 1 Byte. Die Länge eines Eintrags ist also bekannt.

Bei Zeichenketten oder Arrays von Zeichenketten - immer mit _ptr% gekennzeichnet - interessiert die Startadresse der Zeichenkette und ihre Länge. Beides steht im 6 Byte langen Deskriptor eines Strings. Zuerst kommt die Startadresse in LONG_INTEGER, dann die Länge mit WORD-Format. Da ich Zeichenketten-Arrays nicht ab Index 0, sondern 1 verwende - die 1. Zeichenkette ist zeile$(1) -, addiere ich gleich 6 auf den Pointer, um zeile$(0) zu überspringen. Nun lautet der Wert aber 10 im obigen Listing. Bei Array-Deskriptoren liefert ARRPTR einen Zeiger auf einen 2. Deskriptor, in dem die Dimensionstiefen vermerkt sind. Erst nach diesen einzelnen Tiefen kommen die eigentlichen String-Deskriptoren. Da die betreffenden Arrays im Beispiel nur eindimensional sind, ist nur 1 Tiefe vermerkt. Diese Vermerke sind immer LONG_WORD breit für eine Dimension. Also muß man 6+4=10 addieren, um auf den Deskriptor von zeile$(1) zu kommen.

kopfzeile$=@ptr_to_strng$(kopf_ptr%)
'
FUNCTION ptr_to_strng$(ptr%)
    LOCAL l%,p% 
    p%={ptr%} 
    l%=INT{ptr%+4}
    IF l%=0 OR p%=0 THEN 
        RETURN ""
    ELSE
        hhilf$=SPACE$(l%)
        BMOVE p%,VARPTR(hhilf$),l%
        RETURN hhilf$
    ENDIF
ENDFUNC ! ptr_to_strng$

Listing 4

Die Adresse von com% wird als 8stellige HEX-Zahl in der EXEC-Zeile übergeben. Vergessen Sie nicht den Längeneintrag von 8 ganz am Anfang der Kommandozeile mit CHR$(8)! Wenn Sie ihn weglassen, wird die Übergabe der Kommandozeile zum ‚Glücksspiel‘ - das freilich meistens funktioniert! Das Schlußzeichen ASCJI- 0 füge ich gerne als ‚Sicherheit‘ beim Umgang mit betriebssystemnahen Funktionen an!

Nun wird das Modul (graf_prnt_prg$) aufgerufen. Fast hätte ich es vergessen: Es muß natürlich genügend Speicherraum haben. In Ihrem Wirtsprogramm muß irgendwo ein RESERVE stehen. Ich habe in meinen Programmen grundsätzlich irgendwo stehen:

RESERVE FRE(0)-200*1024

Das heißt: ,Laß noch 200 KByte für ein Fremdprogramm übrig!‘ Wenn Sie soviel Speicher übrig haben, sollten Sie eine solche Zeile verwenden. Ihr Modul kann in diesem Freiraum arbeiten. Vielleicht müssen Sie diese Grenze verändern. Wenn’s zu wenig ist, werden Sie von EXEC die Meldung bekommen: ‚Speicher reichte nicht! Nr.-39‘

Bei der Entwicklung von Modulen müssen Sie noch mehr Speicher zurückgeben, wenn Sie im Interpreter arbeiten, z.B. - 400*1024. Dann können Sie folgendermaßen Vorgehen: Sie entwickeln das Hauptprogramm und compilieren es. Als Modulname geben Sie GFA-BASIC.PRG an. Wenn nun EXEC ausgeführt wird vom Hauptprogramm, wird der GFA-BASIC-Interpreter geladen. Er wird sich beschweren: ‚Datei * nicht gefunden‘ Er kann nämlich mit einer 8stelligen HEX-Zahl nichts anfangen! Nun laden Sie in den Interpreter, der jetzt als ‚Modul‘ arbeitet, den Quelltext Ihres Modules und führen RUN aus. Irgendwo in Ihrem Modulquelltext müßte nun etwas wie in Listing 3 stehen.

Sie erkennen sofort wieder die Struktur des com-Bereiches. Ich verwende im Modul die exakt gleichen Variablennamen. Das hat den Vorteil, daß ich anfänglich einen Modulquelltext im Hauptprogramm entwickeln kann - ganz ‚normal‘. Arbeiten die Hauptprogrammteile zufriedenstellend, werden sie .ausgelagert' in das Modul. Dann beginnt die Überprüfung, ob sie auch noch als Modul richtig arbeiten. Manchmal zeigen sich erst dann kleine ‚Fehlerchen‘!

Die com%-Adresse muß über die Basepage ermittelt werden. SHEL_READ wäre sinnlos! Das ‚Zusammenbauen‘ des com$ mag Ihnen ein wenig umständlich erscheinen. ,So‘ kann man es aber machen. Diese Methode ist deutlich schneller als das übliche ,com$=com$+...‘ in einer Schleife. Bei 8 Buchstaben spielt das aber keine Rolle! Wenn der com%-Wert 0 ist, hat die Kommunikation mit dem Hauptprogramm nicht funktioniert. Beenden Sie Ihr Modul dann lieber! Die einfachen Variablen wie max_zl% sind nun direkt ohne Umwege verfügbar. Schwieriger ist das mit den Zeichenketten. Sie müssen zusammengebaut werden. Dazu muß der Deskriptor ausgewertet werden wie im Listing 4.

Die Adresse des 1. Zeichens der Zeichenkette steht in {ptr%} als LONG_WORD. Dahinter befindet sich die Länge im WORD-Format. Ist einer der beiden Werte 0, wird gleich eine leere Zeichenkette zurückgegeben. Wenn ein String in GFA-BASIC nicht belegt ist, enthält er die Adresse 0! Deshalb muß auch p% abgefragt werden.

Damit die Zeichenkette bequem im Modul als normale GFA-BASIC-Zeichenkette behandelt werden kann, wird sie in einen ,eigenen' String verwandelt. Dazu muß die Zeichenkette kopiert werden. Die rechtsstehende Abbildung verdeutlicht die Lage.

Bei einzelnen Zeichenketten ist das sehr einfach. Arrays von Zeichenketten verlangen eine andere Behandlung. Hieße es im Hauptprogramm ohne Modultechnik

x$=zeile$(i%)

muß es im Modul heißen

x$=@ptr_to_strng$(zl_ptr%+(i%-1)*6)

Da über com% der Zeiger auf zeile$(1) übergeben wurde, muß von i% der Wert 1 abgezogen werden. Die Deskriptoren sind 6 Bytes lang. Günstiger wäre vielleicht doch gewesen, den Zeiger auf den Deskriptor von zeile$(0) zu übergeben.

Im Beispielprogramm mußte immer nur 1 Zeile im Zugriff sein. Es wurde nicht das gesamte Array zeile$() gebraucht. Wenn das bei Ihnen anders sein sollte, müssen Sie ein ganzes Array mit der Funktion @ptr_to_strng$() Zeichenkette für Zeichenkette auffüllen.



Aus: ST-Computer 11 / 1992, Seite 89

Links

Copyright-Bestimmungen: siehe Über diese Seite