Inhaltsverzeichnis

3.4 Automatisches Nachladen von Programmteilen

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.

Inhaltsverzeichnis