← ST-Computer 11 / 1992

Module in GFA-BASIC

Programmierpraxis

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.