Obwohl Sie für übersetzte Pascalprogramme die gesamte Bank 0 zur Verfügung haben, kann es notwendig werden, Programme zu schreiben, die länger als 64K werden. Die sinnvollste Lösung für solche Anwendungen besteht darin, das Programm in mehrere Teile zu zerlegen, die möglichst unabhängig voneinander sind. Diese Teile kommunizieren dabei nur über globale Variablen. Beim Übergang von einem Teil zum anderen wird das entsprechende Teilprogramm von der Diskette nachgeladen.
Diese eigentlich triviale Idee beinhaltet jedoch einige Probleme: Nachdem das Folgeprogramm nachgeladen wurde, muß die Programm- kontrolle explizit an das neue Programm übergeben werden. Außerdem muß dafür gesorgt werden, daß die globalen Variablen bei allen Programmen an den gleichen Adressen gespeichert werden, damit sie zum Austausch von Informationen zwischen den Teilen verwendet werden können.
In diesem Kapitel wird eine Methode zum Nachladen von Programmen vorgestellt, die universell verwendbar ist. Zwar sind dabei einige manuelle Nacharbeiten erforderlich, jedoch sollten Sie bedenken, daß z.B. der Compiler Pascal 2.0 vollständig in Pascal geschrieben ist und nur 22 K Speicherplatz benötigt. Sie müßten also Programme entwickeln, die mehr als doppelt so komplex sind wie der Compiler, bevor Sie die Speicherkapazität des C-128 überfordern.
Die typische Struktur eines solchen großen Programmes zeigt Bild 108. Aus einem relativ kleinen Hauptprogramm werden viele Prozeduren aufgerufen, die weitgehend unabhängig voneinander sind.
PROGRAM RIESIG (INPUT, OUTPUT); VAR A: ARRAY[0..100] OF INTEGER; WAHL: CHAR; PROCEDURE VORBELEGEN; VAR I: INTEGER; BEGIN FOR I:= 0 TO 100 DO A[I]:= I END; (* VORBELEGEN *) PROCEDURE LOESCHEN; VAR I: INTEGER; BEGIN FOR I:= 0 TO 100 DO A[I]:= 0 END; (* LOESCHEN *) PROCEDURE AUSGEBEN; VAR I: INTEGER; BEGIN FOR I:= 0 TO 100 DO WRITE(A[I]:4); WRITELN END; (* AUSGEBEN *) BEGIN REPEAT WRITELN('V)orbelegen L)oeschen A)usgeben oder E)nde?') READLN(WAHL); CASE WAHL OF 'V': VORBELEGEN; 'L': LOESCHEN; 'A': AUSGEBEN; 'E': HALT; ELSE END; (* CASE *) UNTIL FALSE; END.
Bild 108: Struktur des Gesamtprogrammes
Zunächst stellen Sie alle Konstanten, Typ-, Variablen- und Prozedurdeklarationen, die global von allen Programmteilen verwendet werden sollen, in einem eigenen Include-File zusammen. Als erste Prozedurdeklaration in diesem Include-File steht die Prozedur CHAIN, die den gegenseitigen Aufruf der Teilprogramme steuert:
PROGRAM RIESIG (INPUT,OUTPUT); VAR A: ARRAY[0..100] OF INTEGER; WAHL: CHAR; PROCEDURE CHAIN(S: STRING); (* Folgeprogramm S aufrufen. Der Rücksprung muß explizit *) (* mit einem Aufruf der Prozedur CHAIN erfolgen! *) PROCEDURE LOAD(S: STRING; TARGETBANK: INTEGER); (* DIE PROZEDUR LÄDT DIE DATEI MIT DEM NAMEN S VON DER *) (* DISKETTE IN DIE ANGEGEBENE BANK. DIE LADEADRESSE *) (* IST MIT DEM FILE GESPEICHERT WORDEN. DIE PROZEDUR *) (* ENTSPRICHT ALSO DEM BASIC-BEFEHL BLOAD"...",ONBx *) VAR I : INTEGER; REGS: RECORD SR,AKKU,X,Y: BYTE END; BEGIN BANK:=15; WITH REGS DO BEGIN
AKKU:= TARGETBANK; X:=1; (* NAME IN BANK 1 *) SYS(-152,REGS); (* SETBNK *) AKKU:= LENGTH(S); I:= ADDU(ADR(S),1); X:= I; Y:= HBYTE(I); (* ADRESSE NAME *) SYS(-67, REGS); (* SETNAM *) (* A = laden *) AKKU:=0; X:=8; Y:=1; (* X = Gerät *) (* Y = absolut *) SYS(-70,REGS); (* SETLFS *) AKKU:=0; SYS(-43,REGS); (* LOAD *) IF ODD(SR) THEN (* CARRY=1 ? *) BEGIN WRITELN('LADEFEHLER!'); HALT END; END; END; (* LOAD *) BEGIN LOAD(S,0); BANK:=0; SYS(7200) END; (* CHAIN *)
Bild 109: Inhalt des Include-Files GLOBAL.INC
Die Operationen werden nun jeweils in Form eines eigenständigen Programmes definiert. Dabei werden die globalen Deklarationen von dem Include-File GLOBAL.INC gelesen.
(* HAUPTROGRAMM RIESIG.P *) (*$"GLOBAL.INC" *) BEGIN REPEAT WRITELN('V)orbelegen L)oeschen A)usgeben oder E)nde?') READLN(WAHL); CASE WAHL OF 'V': CHAIN('VORBELEGEN.M'); 'L': CHAIN('LOESCHEN.M'); 'A': CHAIN('AUSGEBEN.M); 'E': HALT ELSE END; (* CASE *) UNTIL FALSE; END. (* TEILPROGRAMM VORBELEGEN.P *) (*$"GLOBAL.INC" *) PROCEDURE VORBELEGEN; VAR I: INTEGER; BEGIN FOR I:= 0 TO 100 DO A[I]:= I END; (* VORBELEGEN *) BEGIN VORBELEGEN; CHAIN('RIESIG.M'); END. (* TEILPROGRAMM LOESCHEN.P *) (*$"GLOBAL.INC" *) PROCEDURE LOESCHEN; VAR I: INTEGER; BEGIN FOR I:= 0 TO 100 DO A[I]:= 0 END; (* LOESCHEN *) BEGIN< LOESCHEN; CHAIN('RIESIG.M'); END. (* TEILPROGRAMM AUSGEBEN.P *) (*$"GLOBAL.INC" *) PROCEDURE AUSGEBEN; VAR I: INTEGER; BEGIN WRITELN('Die Daten lauten:'); FOR I:= 0 TO 100 DO WRITE(A[I]:4); WRITELN END; (* AUSGEBEN *) BEGIN AUSGEBEN; CHAIN('RIESIG.M'); END.
Bild 110: Zerlegung in Teilprogramme
Wenn Sie nun die in Bild 110 aufgeführten Teilprogramme übersetzen, so besitzen Sie anschließend vier Programmdateien auf der Diskette. Für jede Datei meldet Ihnen der Compiler die Endadresse der Konstanten.
RIESIG.M | Stackanfang = 8282 |
VORBELEGEN.M | Stackanfang 8212 |
LOESCHEN.M | Stackanfang 8212 |
AUSGEBEN.M | Stackanfang 8229 |
Bei dieser Adresse beginnt der Variablenspeicher der Programme. Da Sie natürlich in allen Programmen die in GLOBAL.INC deklarierten Konstanten und Variablen verwenden wollen, müssen Sie manuell in den Teilprogrammen die Anfangsadresse für den Variablenspeicher festlegen: Den meisten Platz für Konstanten belegt das Programm RIESIG.M, daher dürfen die Variablen in den anderen Programmen auch erst an der Adresse 8282 beginnen. Dazu laden Sie jeweils die Proramme VORBELEGEN.M, LOESCHEN.M und AUSGEBEN.M. Mit folgenden BASIC-Befehlen modifizieren Sie die Anfangsadresse des stack:
CLR A=PEEK(7397)+256*PEEK(7398) B=8282 POKE A,B AND 255 POKE A+1, B / 256
Wenn Sie anschließend die Programme wieder mit SAVE auf Diskette speichern, können Sie das Gesamtprogramm RIESIG.M mit RUN starten. Falls Sie eine Floppy des Typs 1571 besitzten, halten sich die Ladezeiten auch bei großen Programmen in einem erträglichen Rahmen.