Artikel

Platz schaffen durch
Überlagern

Overlay-Strukturen in Turbo Pascal

Große Programme, oft benötigte Unterstrukturen und bedingungsabhängiges Zuladen weiterer Programmteile lassen sich nur durch Overlays realisieren. Den mit der Version 3.0 einmal begangenen, mit 4.0 wieder verlassenen Weg nimmt Borland mit Turbo Pascal 5.0 und 5.5 wieder auf.

Die Arbeitsweise der Overlay-Technik ab der Version 5.0 ist völlig anders, als aus der Version 3.0 bekannt: Überlagert werden nun nicht mehr einzelne Routinen, sondern ganze Units. Auch reserviert Turbo nun nur noch einen Overlay-Puffer für alle Überlagerungsroutinen, und nicht mehr einen Bereich für jede Overlay-Gruppe. Der Overlay-Puffer wird bereits durch die Initialisierung der Unit System angelegt und entspricht der Größe der längsten überlagerbaren Unit, er kann (und sollte) vom Programmierer vergrößert werden.
Die Codeteile aller Overlay-Units werden nicht mehr in der EXE-Datei gespeichert, sondern in eine Datei mit der Extension OVR geschrieben. Aus dieser Datei wird bei Bedarf der Code in den Arbeitsspeicher nachgeladen. Die globalen Daten der Units werden wie gewohnt im Datensegment gehalten und sind von der Overlay-Technik nicht betroffen.
Beim Schreiben einer Unit braucht noch nicht festgelegt zu werden, ob diese Unit später überlagerbar sein soll. Lediglich die beiden Optionen des Turbo-Pascal-Compilers $F+ und $O+ müssen auf jeden Fall gesetzt sein. Durch die Option $F+ werden auch alle lokalen und nicht exportierten Routinen far erzeugt. Bei ihrem Aufruf wird die Return-Adresse in 4 Bytes (Segment und Offset) auf dem Stack abgelegt (Bild 1). Dieses Format braucht das Laufzeitsystem, um den gesamten Stack bei Ein- und Auslagerungen zu überprüfen. Die Option muß bei allen Units und beim Hauptprogramm gesetzt sein, wenn Overlays verwendet werden sollen. String- und Set-Konstanten werden von Turbo Pascal im Code-Segment abgelegt.

Daraus ergibt sich ein Problem, wenn solche Konstanten als Parameter übergeben werden: Das aufrufende Programm – und damit auch die Konstanten – sind eventuell nicht mehr im Speicher. Hier greift die Option $O+; es ist die einzige Wirkung dieser Option: Bei jedem Aufruf, der in eine andere Unit führt und der String- oder Set-Konstanten als Parameter übergibt, generiert der Compiler vorab den Aufruf einer Kopier-Routine. Diese Routine kopiert die Konstante auf den Stack und es wird die Adresse der Kopie als Parameter übergeben. Mit dieser Option sollte man allerdings sparsam umgehen, da der zusätzliche Kopiervorgang Laufzeit und Platz erfordert. Im Hauptprogramm hat diese Option nichts zu suchen.
Erst bei der Übersetzung des Hauptprogramms wird die Overlay-Struktur definiert. Hier sind alle Units, die überlagerbar sein sollen, mit der Option $O <name> aufzuführen. Dadurch wird Turbo veranlaßt, den Code in die OVR-Datei auszulagern und statt dessen einen Steuerblock (Bild 2) anzulegen.
Zu Programm-Beginn muß dann die Routine OvrInit mit dem Namen der OVR-Datei als Parameter aufgerufen werden. Dieser Aufruf eröffnet die OVR-Datei und initialisiert das Overlay-System.

Der Overlay-Puffer

Bereits bei Programmstart wird ein Overlay-Puffer angelegt. Dazu verschiebt der Initialisierungsteil der System-Unit den Beginn des Heap-Speichers (HeapOrg) um soviel, daß in den nun freigewordenen Platz die größte Overlay-Unit hineinpaßt. Die Variablen OvrHeapOrg und OvrHeapEnd werden mit dem Anfang und dem Ende des Puffers besetzt. Die Puffergröße ist in der Konstanten OvrHeapSize gespeichert. Allerdings enthält diese Konstante auch nach Vergrößerung des Overlay-Puffers immer noch den Start-Wert; die tatsächliche Größe des Puffers kann nur mit OvrGetBuf oder Subtraktion der Werte OvrHeapEndOvrHeapOrg ermittelt werden. Der Pointer OvrHeapPtr zeigt immer auf die Stelle im Puffer, an der das nächste Overlay geladen werden soll, er ist mit OvrHeapOrg initialisiert.
Der Puffer kann mit der Routine OvrSetBuf vergrößert werden. Dies ist aber nur möglich, wenn der Puffer leer ist – das läßt sich durch Aufruf von OvrClearBuf erreichen – und noch kein Heap-Speicher verwendet wurde. Die zweite Einschränkung ist wesentlich: Da durch die Vergrößerung der Beginn des Heap verschoben wird, würden bereits angelegte Heap-Variable überschrieben.
Bei einem Einlagerungsvorgang prüft das Laufzeitsystem, ob oberhalb des Zeigers OvrHeapPtr genügend Platz für die neue Unit vorhanden ist. In diesem Fall wird die Unit in den Puffer kopiert. Andernfalls werden die ältesten Units aus dem Puffer entfernt und die verbleibenden an das Puffer-Ende verschoben (Bild 3).
Ab der Version 5.5 kann ein Bewährungsbereich definiert werden, dieser Bereich liegt immer oberhalb des Zeigers OvrHeapPtr. Standardmäßig ist der Bereich mit 0 Byte angelegt, das Overlay-System verhält sich dann wie in der Version 5.0. Das Laufzeit-System vermerkt alle Aufrufe der Units im Bewährungsbereich in den Steuersegmenten. Muß nun durch eine Einlagerung eine Unit aus dem Puffer entfernt werden, so wird vorab geprüft, ob die Unit „sich bewährt“ hat – in diesem Fall wird sie wieder an den Beginn des Puffers verschoben und eine andere Unit ausgelagert (Bild 3). Durch dieses Verfahren bleiben oft referierte Units im Puffer erhalten, die Effizienz des Programms wird erhöht. Die Größe des Bewährungsbereiches wird mit der Prozedur OvrSetRetry eingestellt und kann mit OvrGetRetry abgefragt werden.
Besitzt der Rechner Expanded Memory (EMS), kann die gesamte OVR-Datei durch OvrInitEMS in diesen Speicherbereich übertragen werden. Bei Einlagerungen werden die Units dann von dort statt aus der OVR-Datei gelesen – das Verfahren wird dadurch wesentlich beschleunigt. Der Code wird aber nicht im EMS-Speicher ausgeführt, sondern weiterhin in den normalen Arbeitsspeicher kopiert. Durch eigene Laderoutinen kann dieses Verfahren ab der Version 5.5 auch auf Extended Memory bei AT-Rechnern angewendet werden (siehe Listing 3).

Die interne Struktur

Für jede Overlay-Unit erzeugt Turbo Pascal in der EXE-Datei einen Steuerblock. Dieses Steuersegment enthält in einem festen Teil von 32 Byte (2 Paragraphen) die wichtigsten Informationen über die Unit: Position und Länge des Codes in der OVR-Datei, die Ladeadresse, falls der Code geladen ist und eine Verkettung aller geladenen Units sowie aller Overlay-Steuerblöcke. An diesen festen Teil schließt sich eine variable Vektor-Tabelle an. Hier ist für jede Routine der Unit ein 5-Byte-Eintrag vorhanden. Jeder Vektor enthält den Befehl INT 3Fh (CD 3F) sowie die relative Adresse der Routine innerhalb des Unit-Codes. Das fünfte Byte bleibt vorerst frei.
Der Linker setzt alle Verweise auf Routinen in Overlay-Units auf den entsprechenden Vektor. Der Aufruf einer solchen Routine führt nun zu einem Interrupt 3Fh. Der Interrupt-Handler 3Fh wird durch die Prozedur OvrInit eingekettet. Auch die System-Unit kettet bereits einen Interrupt-Handler 3Fh ein – dieser erzeugt lediglich den Laufzeit-Fehler 208: Overlay-System nicht initialisiert. Der Interrupt-Handler lädt nun das Overlay-Coding in den Puffer, setzt die Lade-Adresse im Steuersegment und verkettet die Unit mit den übrigen geladenen Units. Außerdem werden alle Vektoren in der Tabelle in Befehle JMP <segment>:<offset> umgesetzt, jetzt wird das 5. Byte benötigt. Jeder Aufruf einer bereits geladenen Routine führt über diesen JMP dann direkt zur richtigen Prozedur.
Durch die Einlagerung müssen eventuell andere Units aus dem Puffer entfernt werden. In den Steuerblöcken dieser Units wird die Lade-Adresse gelöscht und sie werden aus der Lade-Kette entfernt. Damit ist es aber nicht getan: Es könnten Prozeduren aktiv sein, also Return-Adressen auf dem Stack auf diese Unit verweisen. Damit der spätere Return nicht in die ewigen Jagdgründe führt, untersucht der Interrupt-Handler bei jedem Entfernen einer Unit den gesamten Stack. Auf der Adresse SS:BP-4 ist der Segment-Anteil der Return-Adresse gespeichert. Stimmt dieser mit der alten Ladeadresse der Unit überein, wird der Segmentanteil durch die Adresse des Steuerblockes ersetzt. Zusätzlich wird beim ersten Treffer der Offset-Anteil gesichert und anschließend gelöscht. Die Position dieses Treffers wird ebenfalls im Steuerblock vermerkt. Über das BP-Register wird dann die nächste Verschachtelungsebene untersucht. Dieser Algorithmus macht die Option $F+ notwendig.
Kehrt nun das Programm über einen retf zu einem nicht mehr geladenen Overlay zurück, so verweist die Return-Adresse auf die Adresse 0 innerhalb des Steuerblockes. Da dort ebenfalls ein INT 3Fh-Befehl steht, wird der Interrupt-Handler aktiviert und sorgt für die Einlagerung der unterbrochenen Unit.
Jeder Einlagerungsvorgang untersucht ebenfalls den Stack. Findet die Lade-Routine ein Return-Segment, das auf den Steuerblock zeigt, so wird dieses durch den Segment-Anteil der neuen Ladeadresse ersetzt. Ebenso werden natürlich bei Verschiebungen innerhalb des Overlay-Puffers die Return-Segmente entsprechend angepaßt.
Der Bewährungs-Mechanismus funktioniert auf die gleiche Weise: Sobald eine Unit in diesen Bereich verschoben wird, werden die Vektoren auf INT 3Fh gesetzt, die Lade-Adresse im Steuerblock bleibt jedoch erhalten. Der Interrupt-Handler prüft ab der Version 5.5 zuerst, ob noch eine Lade-Adresse vorhanden ist. In diesem Fall werden lediglich die Vektoren wieder auf JMP-Befehle umgesetzt und die Bewährung im Steuerblock vermerkt. Turbo Pascal stellt dazu noch die Zähler OvrTrapCount und OvrLoadCount zur Verfügung. Im ersten werden alle Interrupts 3Fh gezählt, der zweite gibt Auskunft über die tatsächlichen Ladevorgänge. Die Differenz beider Zähler ergibt die Anzahl Bewährungsaufrufe.
Die Steuersegmente sind mit zwei Zeigerketten untereinander verbunden. Der Anker der ersten Kette ist die Variable OvrLoadList, hier sind die Steuersegmente aller geladenen Units verkettet. Die Fortsetzung der Kette befindet sich in den Bytes 14/15 der Steuerblöcke. Die Zeiger haben das Format word und enthalten den Segmentanteil der Adressen (Offset ist 0). Die zweite Kette verbindet alle Steuerblöcke über den Anker OvrCodeList und die Bytes 0E/0F der Steuersegmente. Die Zeiger haben ebenfalls word-Format und enthalten den Segmentanteil der Adressen. Jedoch sind die Zeiger noch nicht relokiert – es muß also das Programmbeginn-Segment (PrefixSeg+$10) aufaddiert werden.

Eigene Laderoutinen

Zum Laden einer Overlay-Unit ruft der Interrupt-Handler die Laderoutine auf. Die Laufzeitbibliothek enthält zwei verschiedene Laderoutinen. Die eine wird von OvrInit eingekettet und liest den Code aus der OVR-Datei, die zweite wird durch OvrInitEMS aktiviert und sorgt für den Transport aus dem EMS-Speicher. Ab der Version 5.5 ist der Zeiger auf die Laderoutine unter dem Namen OvrReadFunc exportiert, dadurch wurde die Möglichkeit geschaffen, eigene Laderoutinen zu realisieren.
Die beiden Beispiele greifen auf die Steuerblöcke zu, deshalb ist die Typ-Vereinbarung als Include-Datei extrahiert (Listing 1).
Das erste Beispiel (Listing 2) realisiert eine Lade-Statistik für alle Overlay-Units, es ist ein gutes Hilfsmittel für die Programmentwicklung. Anhand dieser Information kann entschieden werden, welche Units sinnvoll überlagerbar gemacht werden und welche besser im festen Teil unterzubringen sind. Die Unit zählt alle Ladeaufrufe in einem freien Feld im Steuerblock und gibt bei Programmende alle Zähler auf dem Bildschirm aus. Zusätzlich exportiert die Unit eine Funktion ShowLoadCount, die dem rufenden Overlay den aktuellen Zählerstand meldet; erfolgt der Aufruf nicht aus einer Overlay-Unit, so wird als Fehlermerker der Wert -1 zurückgegeben.
Beachten Sie den Trick, mit dem das Code-Segment der rufenden Routine gelesen wird: Die Funktion definiert eine Tabelle als lokale Variable, diese Tabelle liegt auf Adresse SS:BP-2. Durch den – eigentlich unzulässigen – Index +4 kann auf den Segment-Anteil der Return-Adresse zugegriffen werden. Diese Adressierung kann nicht durch untypisierte Konstanten erfolgen (wie dummy [4]), da der Compiler diesen „Fehler“ bereits bemerkt und die Übersetzung abbricht. Notwendig ist auch der Compilerschalter $R-, um einen Laufzeitfehler bei diesem Zugriff zu unterbinden. Viele AT-Rechner sind heute mit mindestens 1 MByte RAM bestückt, besitzen also extended Memory von 384 KByte oder mehr. Dieser Speicher wird mit dem zweiten Beispiel (Listing 3) als Overlay-Zwischenpuffer benutzt und arbeitet dabei ähnlich wie die Standardroutine zur EMS-Unterstützung.
Der Initialisierungsteil arbeitet zunächst die Kette aller Steuerblöcke ab, beginnend bei dem Anker OvrCodeList Die Adressen müssen erst noch durch Addition des Prefix-Segmentes aus PrefixSeg und 100h Byte (10h Paragraphen) relokiert werden. Die Codelängen aller Overlay-Units werden addiert, um zu sehen, ob das Extended Memory ausreicht.
Danach wird über die gleiche Kette jede Unit einmal geladen und in den erweiterten Speicher gebracht. Die Ladeadresse wird in vier der freien Bytes vermerkt.
Die Laderoutine OwnOvrRead ersetzt in diesem Beispiel das Original vollständig. Über den Parameter OvrSeg wird vom Laufzeitsystem die Adresse des Steuerblockes übergeben, ebenso hat der Interrupthandler bereits die Ladeadresse eingetragen. Es braucht nur noch der Bereich aus dem erweiterten Speicher hierher kopiert werden. Auch eine Relokierung ist nicht mehr notwendig, da sie bereits während der Initialisierung erfolgte.
Durch dieses Verfahren ergeben sich nur wenige Einschränkungen. Insbesondere funktionieren Sprünge von einem Overlay in ein anderes – im Gegensatz zur Implementierung in der Version 3. Overlay-Units mit Exit-Coding werden korrekt behandelt und auch mit Zeigern auf Prozeduren gibt es keine Probleme.
Von den Standard-Units ist nur die Unit DOS überlagerungsfähig, da die anderen Units nicht mit der Option $O+ übersetzt sind.
Wegen der Manipulationen am Stack müssen alle Routinen des Programms, die direkt oder indirekt überlagerbare Prozeduren aufrufen, mit der Option $F+ (erzeuge far Calls) übersetzt werden. Andernfalls kann die Nachladeroutine Unterbrechungen nicht korrekt erkennen und das Programm verläuft sich im Walde.
Die Prozedur InitGraph definiert Variable auf dem Heap und darf deshalb erst nach einer eventuell notwendigen Vergrößerung des Puffers durch OvrSetBuf aufgerufen werden. Dies gilt nicht für OvrInit, da der Overlay-Puffer bereits von der System-Unit angelegt wird.
Die Overlay-Datei kann nur erzeugt werden, wenn die Option Compile/Destination Disk gesetzt ist; ein Aufruf des Programms aus der integrierten Entwicklungsumgebung heraus ist daher nur möglich, wenn der Schalter gesetzt ist oder das Programm vorab bereits mit Schalter übersetzt wurde.
Units, die eigene Interrupt-Behandlungen machen, dürfen keinesfalls überlagert werden, da sich ihre Adresse laufend verändern könnte, oder sie zum Zeitpunkt des Interrupts gar nicht geladen sind.
Units mit Initialisierungs-Coding müssen schon vor dem ersten Befehl des Hauptprogramms geladen werden. Dieses Problem läßt sich aber umgehen, indem der Aufruf von OvrInit selbst in die Initialisierungsroutine einer Unit verlegt wird. Diese Unit darf nicht überlagerbar sein und muß vom Hauptprogramm vor allen Overlay-Units in der Uses-Klausel definiert werden.

Norbert Dohmen

Literatur
  1. [1] Dohmen, N.: Turbo-Pascal Enzyklopädie, Franzis Verlag, 1990.

 

Pascal-Stack
Bild 1. Der Aufbau des Pascal-Stacks
Overlay-Steuersegment
Bild 2. Das Overlay-Steuersegment steht anstelle der Unit, die sich in der OVR-Datei befindet, im Programmcode
Overlay-Unit nachladen
Bild 3. Für das Nachladen einer Overlay-Unit wird, wenn nötig, die älteste Unit entfernt, es sei denn, diese steht im Bewährungsbereich

 

Listing 1. In einer Include-Datei befinden die Typvereinbarungen
für die beiden Pascal-Programme, die Steuerblöcke zugreifen
type OvrSteu = record                  {Overlay-Steuer-Block}
     int3f   : integer;                  {"INT 3F"}
     Unterbr : integer;                  {Return-Offset einer Unterbrechung}
     Codeptr : longint;                  {Position in der OVR-Datei}
     Codelen : integer;                  {Länge des Codings}
     relolen : integer;                  {Länge der Relokationstabelle}
     jmpcnt  : integer;                  {Anzahl Sprungvektoren}
     nextcode: word;                     {Kette aller Steuerblöcke}
     loadadr : word;                     {Lade-Segment der Unit}
     stackpos: word;                     {Stackposition einer Unterbrechung}
     nextload: word;                     {Kette Steuerblöcke der geladenen Units}
     emsnr   : integer;                  {EMS-Seitennummer}
     emsoffs : word;                     {Offset in EMS-Seite}
     {die 6 freien Bytes können vom Programm genutzt werden}
     frei1   : integer;
     frei2   : integer;
     frei3   : integer;
     {Sprungvektoren}
     Vektor  : array[0..1] of array [1..4] of byte;
end;

 

Listing 2. Mit diesem Programm werden alle Ladevorgänge erfaßt
unit ovstati;

interface

function ShowLoadCount : integer;

implementation

uses overlay;

{$I ovsteu.inc}

var SteuPtr : ^OvrSteu;                 {Steuerblock-Adresse}
    OrgExit : Pointer;                  {Original-Exitroutine}
    OrgLoad : OvrReadFunc;              {Original-Laderoutine}
    OvlName : string[63];               {Name der OVR-Datei
                                         - muß noch individuell gesetzt werden!}
{$F+,R-}

function OwnOvrRead(OvrSeg : Word) : integer;
begin
  SteuPtr := Ptr(OvrSeg, 0);            {Zeiger auf Steuersegment}
  inc(SteuPtr^.frei1);                  {Lade-Vorgang zählen}
  OwnOvrRead := OrgLoad(OvrSeg);        {Original-Laderoutine aufrufen}
end;

function ShowLoadCount : integer;       {Melde Lade-Zähler an Overlay}
var dummy : array[0..0] of word;        {zur Adressierung der Return-Adresse:}
                                        {Index -1: function-Return}
                                        {Index -2: BP Aufrufer}
                                        {Index -3: Return-Offset}
                                        {Index -4: Return-Segment}
const i : integer = 4;                  {typisierte Konstante für Tabellenzugriff}
                                        {mit untypisierter Konstanten ist kein Zugriff}
                                        { außerhalb der Tabellengrenzen möglich!}
begin
  dummy[0] := OvrLoadList;              {suche nach rufendem Overlay}
  while dummy[0] <> 0 do
  begin
    SteuPtr := Ptr(dummy[0], 0);        {Zeiger auf Steuersegment}
    if SteuPtr^.LoadAdr = dummy[i] then
    begin                               {Overlay gefunden}
      ShowLoadCount := SteuPtr^.frei1;  {Zähler übergeben}
      exit;                             {und Funktion abbrechen}
    end;
    dummy[0] := SteuPtr^.nextload;      {nächstes geladenes Overlay}
  end;
  ShowLoadCount := -1;                  {Fehler: Aufruf nicht von einem Overlay!}
end;

procedure MeldLoad;
var i : integer;
    w : word;
begin
  ExitProc := OrgExit;
  i := 0;
  w := OvrCodeList;                     {Anker der Overlay-Steuerblöcke}
  while w<>0 do
  begin
    inc(i);
    SteuPtr := Ptr(w+PrefixSeg+$10, 0); {Zeiger auf Steuerblock}
    writeln('Overlay Nr. ', i:4, ' wurde ', SteuPtr^.frei1:5, ' mal geladen.');
    w := SteuPtr^.nextcode;
  end;
end;

begin
  ovrinit(OvlName);
  OrgLoad := OvrReadBuf;
  OvrReadBuf := OwnOvrRead;
  OrgExit := ExitProc;
  ExitProc := @MeldLoad;
end.

 

Listing 3. Turbo Pascal benutzt von sich aus kein Extended Memory
für den Overlay-Puffer, mit diesem Programm ist es möglich
unit ovextm;

interface

implementation

uses overlay, dos;

{$I ovsteu.inc}

const Rights = $93000000;                {Recht: lesen und schreiben (Descriptor)}

var MemDesc : record                     {Descriptor-Table für INT15h}
      Dummy   : array[0..7] of byte;
      GDT     : array[0..7] of byte;
      SRClen  : integer;                 {Segmentgröße}
      SRCAdr  : longint;                 {24-Bit-Adresse Source + Rechte}
      SRCrest : integer;
      DSTlen  : integer;                 {Segmentgröße}
      DSTAdr  : longint;                 {24-Bit-Adresse Destination + Rechte}
      DSTrest : integer;
      BiosCS  : array[0..7] of byte;
      SS      : array[0..7] of byte;
    end;
var SteuPtr   : ^OvrSteu;                {Steuerblock-Adresse}
    Regs      : Registers;               {Register für BIOS-Aufruf}
    OvlName   : string[63];              {Name der OVR-Datei
                                          - muß noch individuell gesetzt werden!}
{$F+,R-}

function OwnOvrRead(OvrSeg:Word) : integer;
begin
  SteuPtr := Ptr(OvrSeg,0);              {Zeiger auf Steuersegment}
  move(SteuPtr^.frei2,MemDesc.SrcAdr,4); {24-Bit-Adresse im Extended Memory}
  MemDesc.DstAdr :=                      {24-Bit-Adresse im Overlay-Puffer}
    Rights or (longint(SteuPtr^.loadadr) shl 4);
  Regs.CX := SteuPtr^.Codelen;           {zu verschiebende Bytes}
  Regs.ES := Seg(MemDesc);
  Regs.SI := Ofs(MemDesc);
  Regs.AH := $87;
  Intr($15, Regs);                       {INT15/87: Block übertragen}
  OwnOvrRead := Regs.AX;                 {Fehlercode als Return-Wert}
end;

procedure InitExtMem;
var w     : word;                        {Zwischenspeicher Steuersegment}
    f     : integer;                     {Ergebnis Ladefunktion}
    OSize : Longint;                     {Größe aller Overlays}
    LAdr  : Longint;                     {Ladeadresse (24 Bit)}
begin
  FillChar(MemDesc, SizeOf(MemDesc), 0); {Descriptor-Table initialisieren}
  MemDesc.SRClen := $7FFF;                 {Segment-Größen: 64 KB}
  MemDesc.DSTlen := $7FFF;
  OSize := 0;                            {Berechnung des notwendigen Speichers}
  w := OvrCodeList;                        {Anker der Overlay-Steuerblöcke}
  while w<>0 do
  begin
    SteuPtr := Ptr(w+PrefixSeg+$10, 0);    {Zeiger auf Steuerblock}
    inc(OSize, SteuPtr^.CodeLen);          {Code-Größe addieren}
    w := SteuPtr^.nextcode;                {nächste Unit}
  end;
  OSize := (OSize+$3ff) shr 10;          {Umrechnung in KByte}
  Regs.AH := $88;
  Intr($15,Regs);                          {INT15/88: Größe Extended Memory lesen}
  if Regs.AX < OSize then
  begin                                    {nicht genügend Speicher}
    OvrResult := -6;
  exit;
  end;
                                         {Overlays in Extended Memory übertragen}
  LAdr := $100000 or Rights;               {Adresse: 1MB plus Recht lesen/schreiben}
  w := OvrCodeList;                        {Anker der Overlay-Steuerblöcke}
  while w<>0 do
  begin
    inc(w, PrefixSeg+$10);                 {Umrechnung in Segment-Adresse}
    SteuPtr := Ptr(w, 0);                  {Zeiger auf Steuerblock}
    SteuPtr^.LoadAdr := OvrHeapOrg;        {Ladeadresse eintragen}
    move(LAdr, SteuPtr^.frei2, 4);         {24-Bit-Adresse im Extended Memory merken}
    MemDesc.DstAdr := LAdr;
    MemDesc.SrcAdr :=                      {24-Bit-Adresse im Overlay-Puffer}
      Rights or (longint(OvrHeapOrg) shl 4);
    f := OvrReadBuf(w);                    {Unit laden}
    SteuPtr^.LoadAdr := 0;                 {Ladeadresse löschen}
    if f<>0 then
    begin                                  {Lese-Fehler}
      OvrResult := -4;
      exit;
    end;
    Regs.CX := SteuPtr^.CodeLen;           {Unit-Größe}
    Regs.ES := Seg(MemDesc);
    Regs.SI := Ofs(MemDesc);
    Regs.AH := $87;
    Intr($15,Regs);                        {INT15/87: Block übertragen}
    if Regs.AX <> 0 then
    begin                                  {Fehler bei INT15}
      OvrResult := -4;
      exit;
    end;
    inc(LAdr, SteuPtr^.CodeLen);           {Adresse weiterrechnen}
    w := SteuPtr^.nextcode;                {nächste Unit}
  end;
  OvrReadBuf := OwnOvrRead;                {eigene Laderoutine einketten}
end;

begin
  ovrinit(OvlName);
  InitExtMem;
end.

 

aus mc 12/90 – Seite 124-130