TOS-Accessory: Multi-Accessory im Quelltext, Teil 4

Oh nein! Bei der neuesten Ausgabe des TOS-Accessories handelt es sich nicht um einen DOS- oder Macintosh-Emulator, wenngleich von nun an allen Programmen eine unbegrenzte Anzahl von GEM-Objekten zur Aufbesserung der grafischen Oberfläche bereit steht.

Wer sich nicht mit den Standardobjekten des GEM begnügen will, hat zur Zeit zwei Alternativen. Entweder er besorgt sich ein neueres Resource-Construction-Set, das eine Bibliothek für benutzerdefinierte Objekte sein eigen nennt, oder er schreibt sich eine solche selbst. Das TOS-Accessory zeigt auf, wie mit benutzerdefinierten Objekten umzugehen und eine solche Bibliothek aufgebaut ist.

Getreu dem Motto des TOS-Accessories: ganz uneigennützig die eigenen Funktionen allen Programmen zur Verfügung zu stellen, erhebt sich diese Implementation der benutzerdefinierten Objekte zur wahren Sensation. So profitiert nicht nur die Accessory-Oberfläche von den erweiterten Objekten, sondern jedes Programm, dessen Resource-Datei sich ändern läßt.

Das andere große Thema behandelt die in der letzten TOS-Ausgabe angekündigte RAM-Disk. Eine Ram-Disk gehört zwar schon lange nicht mehr zu dem, was den Anwender gleich vom Hocker reißt, ist aber für ein Multi-Accessory, das etwas auf sich hält, unverzichtbar. Behandeln wir allerdings dieses Implantat ernst genug, stellen wir schnell fest, daß es eine ganze Reihe an extravaganten Erweiterungen gibt, mit der sich eine RAM-Disk ausstatten läßt.

Die RAM-Disk

Zur Minimalausführung einer RAM-Disk gehören eigentlich nur drei Funktionen, die über die System-Variablen »hdv_rw« (0x476), »hdv_bpb« (0x472) und »hdv_mediach« (0x47E) abrufbar sind. Außerdem braucht man natürlich jede Menge Speicher.

Das GEMDOS ermittelt über »hdv_bpb« (Tabelle 1) die Größe und Organisationsstruktur eines Speichermediums (einer Diskette, Festplatte oder auch RAM-Disk). »hdv_rw« dient dem Lesen und Schreiben von Sektoren. Welche Sektoren wann, weshalb oder warum zu lesen oder schreiben sind, interessiert uns nicht, denn darum kümmert sich ebenfalls das GEMDOS.

Die dritte Funktion »hdv_mediach« dient zum Erkennen eines Wechsels von Disketten oder Wechselplatten. Sie gibt an, ob etwas ausgetauscht wurde oder nicht. Da es sich mehr als nur schwierig erweist, die RAM-Chips während des Betriebs zu wechseln, dürfen wir in unserem Fall stets den Wert »nicht gewechselt« zurückliefern.

Sobald die gewünschte Größe der RAM-Disk feststeht, und sichergestellt ist, daß auch genügend Speicher zur Verfügung steht, gilt es die BPB-Struktur zu berechnen und anzulegen. Eine einfache Dreisatzrechnung scheitert jedoch noch an zu vielen Unbekannten, was es uns wiederum erlaubt, einige Parameter selber festzulegen. So soll die Sektorgröße in Bytes immer 512 betragen. Dies ist auf der einen Seite speichersparend und auf der anderen Seite kommt damit wirklich jede TOS-Version zurecht.

Zum zweiten soll die FAT-Eintragsgröße immer 16 Bit betragen (also bflags = 1). Eine 12-Bit-FAT würde vom Wertebereich eigentlich ausreichen und ist nebenbei noch genügsamer in Sachen Speicherverbrauch. Die Kehrseite der Medaille ist aber die bei 12-Bit erforderliche Bit-Schieberei, die das GEMDOS enorm bremst. Lese- und Schreibaktionen benötigen dann unangenehm viel Zeit.

Da die RAM-Disk nach dem Booten möglichst früh arbeitsbereit sein sollte, versteht es sich von selbst, alle Routinen in das seit der letzten TOS-Ausgabe eingeführte AUTOTACC-Programm einzubinden. Die Funktionsweise der hdv_rw-Funktion ist trivial. Dazu teilt man den reservierten Speicher in durchnumerierte 512-Byte-Häppchen, um dann - je nach Schreib- oder Lesezugriff - die angegebene Pufferadresse stückchenweise auszulesen oder zu beschreiben.

Übrigens durchlaufen die BIOS-Funktionen Rwabs, Getbpb und Mediach direkt die entsprechenden hdv_xxx-Funktionen, womit auch die Anordnung der zu übergebenden Parameter bekannt ist. Mit dem »RTS«-Befehl springen die hdv-Funktionen wieder zu ihren BlOS-Pendants zurück. Soweit haben wir also eine ganz gewöhnliche RAM-Disk. Nun aber zu den Extrafunktionen.

Bis daß der Strom uns scheidet

Natürlich eignen sich RAM-Disks nicht als Speichermedium für Backups oder ähnliches; trotzdem lohnt sich das Anlegen einer RAM-Disk um so mehr, je länger sich deren Daten aufrecht halten lassen. So muß es uns noch gelingen, die Daten nicht verhungern oder verdursten zu lassen, solange der Computer eingeschaltet ist. In anderen Worten müssen Dateien die Brachialgewalten eines Resets überleben.

Bei genauerer Betrachtung des Ablaufs eines Resets fällt besonders unangenehm auf, daß eine Routine den kompletten Speicher löscht, dessen Größe bei einem Kaltstart ermittelt und in der Systemvariable »phystop« (0x42E) abgelegt wurde. Letztere Anmerkung erfolgte nicht ohne Grund. So bestätigt sich dann auch der Verdacht, daß sich durch das Herabsetzen von »phystop«, das Löschen des Speichers oberhalb dieser Grenze beim nächsten Reset erfolgreich verhindern läßt.

Setzt man beim Einrichten der RAM-Disk diese Variable genau um deren Speicherbedarf herunter, läßt sich schon mal der Reinwaschgang beim nächsten Reset vermeiden. Nur weiß das GEMDOS noch nichts von dieser Speicherreduzierung, so daß die »Malloc«-Funktion auch diesen Bereich als freien Speicher zurückliefert. Abhilfe erreicht man, indem wir uns diesen Speicher auch noch mit Malloc holen. Somit ist der Platz bis zum nächsten Reset an die RAM-Disk vergeben.

Da die Malloc-Funktion den Speicher von unten her verteilt, unser Interesse aber dem oberen Bereich gilt, müssen wir das GEMDOS mit einem kleinen Trick überlisten. Fordern wir nämlich gleich zwei Speicherblöcke auf einmal an, deren Größen geschickt zu wählen sind, und geben wir dann den unteren der beiden Blöcke mit »Mfree« wieder frei, sollte es uns gelingen, exakt den Speicher zu reservieren, den wir mit »phystop« abgegrenzt haben. Diese Geschichte ist, wie gesagt, nur bei der Initialisierung nötig, da sich nach einem Reset auch das GEMDOS neu initialisiert und folglich die neue Speichergröße berücksichtigt. Ein kleines Detail fehlte bis jetzt noch. Der Bildschirmspeicher befindet sich nämlich ebenfalls am oberen Speicherende und ist auf einen niedrigeren Bereich (direkt unter phystop) zu verlegen.

Damit haben wir nun erreicht, daß sich die Daten der RAM-Disk nach einem Reset oberhalb von phystop befinden und uns dort nicht verloren gehen. Die System-Variablen hdv_xxx wurden durch den Reset bedingt jedoch neu eingerichtet, womit sich die Frage stellt, wie man das Vorhandensein der Daten überprüfen soll. Schließlich garantiert uns niemand, daß die RAM-Disk vor dem Reset überhaupt installiert war. Den Bereich einfach oberhalb von phystop zu durchforsten scheitert spätestens dann, wenn es diesen Speicher physikalisch gar nicht gibt.

Eine neue Busfehlerroutine (0x8) sorgt dafür, daß zum einen keine häßlichen Bomben auf dem Bildschirm erscheinen, und zum anderen läßt sich hier erkennen, ob phystop auch das hält, was es verspricht nämlich das absolute und endgültige Speicherende. Haben wir festgestellt, daß die Daten der RAM-Disk schon existieren, brauchen wir nur noch die vom letzten Reset zerstörten hdv_xxx-Funktionen neu zu setzen.

Um beim Systemstart die RAM-Disk automatisch mit Dateien zu füllen, dient eine Option die ein beliebiges Programm mit Kommandozeile direkt nach der Installation aufruft; zum Beispiel ein Kopierprogramm.

GEM-Objekte im Überschuß

Wie Vorteilhaft die kleinen runden RADIO-Buttons, oder die ankreuzbaren quadratischen Buttons mit nebenstehender Erklärung beim Gestalten von Dialogboxen sind, beweisen diverse Programme, die sich die benutzerdefinierten Objekte zu Nutzen machen. Der damit verbundene größere Aufwand ist aber für viele Programmierer noch ungerechtfertigt hoch. Die neueren Resource-Editoren liefern aus diesem Grund gleich einige Bibliotheksfunktionen, die das Zeichnen solcher Objekte übernehmen.

Die Vorgehensweise sieht so aus, daß man zum Beispiel ein Objekt des Typs »G_BUTTON« im Resource-Editor als erweiterten Objekttyp deklariert. Zu diesem Zeitpunkt handelt es sich immer noch um ein G_BUTTON-Objekt. Das eigene Programm legt dann beim Laden der Resource-Datei für diesen Objekttyp zusätzlich noch eine USERBLK-Struktur an. Diese Struktur enthält einen Zeiger auf die Zeichenroutine (aus der mitgelieferten Bibliothek) und den alten »ob_spec«-Wert. Jetzt wandelt man das Objekt vom Typ (»ob_type«) »G_BUTTON« in »G_USERDEF«. Der Parameter ob_spec des G_USERDEF-Objekts (es handelt sich jetzt um einen ganz neuen Objekttyp) zeigt nicht mehr auf einen Text oder eine »TEDINFO«-Struktur, sondern auf die USERBLK-Struktur. Die AES-Funktion »objc_draw« weiß nun, daß sie im Falle eines solchen Objekttyps in ob_spec einen Zeiger auf USERBLK findet, und springt demzufolge in die dazugehörende Funktion, die das Zeichnen dieses Objekts übernimmt.

Der Unterschied zu den orginal Objekttypen liegt darin, daß eine Resource-Datei alleine nicht reicht, sondern mindestens noch zwei Funktionen damit fest verbunden sein müssen: die neue Zeichenfunktion und eine Routine, die die USERBLK-Strukturen anlegt und reloziert.

Die hier vorgestellte Implementierung schlägt in sofern einen anderen Weg ein, als die komplette Umwandlung der erweiterten Objekte in »G_USERDEF«-Objekte innerhalb einer neuen »objc_draw«-Funktion abläuft. Vor dem Verlassen wird dann wieder alles zurückgesetzt.

Somit zählt auf Programmiererebene nur die Tatsache, daß die objc_draw-Funktion eben noch mehr leistet als bisher. Die neue objc_draw-Funktion übernimmt also alle zu erledigenden Aufgaben.

Leider ist objc_draw() nicht die einzig verantwortliche Zeichenfunktion für Objekte. Beim Anklicken eines selektierbaren Objekts innerhalb der form_do-Funktion ändert sich dessen Status (SELECTED) und dadurch auch das Aussehen. Die »form_do«-Funktion ruft hierzu »form_keybd« bzw. »form_button« auf. Diese verwenden wiederum die Funktion »objc_change«, um ein Objekt zu (de)selektieren. Zusammenfassend stellt man dann fest, daß folgende fünf Routinen die Zeichenroutinen aus «USERBLK» aufrufen:

objc_draw(), form_do(), form_keybd(), form_but-ton und objc_change().

Diese lassen sich nach folgendem Schema an zusätzliche Objekttypen anpassen:

{
	neue_funktion( OBJECT x tree, ... ) 
	int ret;
	ExpandObjs( tree );
	ret = alte_funktion( tree, ... );
	ReduceObjs( tree ); 
	return ( ret );
}

ExpandObjs() erzeugt für die erweiterten Objekttypen die entsprechenden USERBLK-Strukturen, und ReduceObjs() setzt die Objekte auf die ursprüngliche Form zurück.

Nachdem nun alle relevanten Funktionen für die neuen Objekte bereitstehen, denen übrigens die gleichen Parameter wie den Originalfunktionen zu übergeben sind, lassen sich mit einem kleinen Eingriff in unseren GEM-Dispatcher (aus [1] für die erweiterte form_do-Funktion) die neuen Objekte in allen Programmen verwenden. Eine angenehme Begleiterscheinung ist zudem die Tatsache des sofortigen Anzeigens der neuen Objekte während der Arbeit mit einem Resource-Construction-Set. Natürlich war eine Revidierung der kompletten Oberfläche von »TOS-ACC« unumgänglich.

In der nächsten Ausgabe: Drucker-Spooler, der auch den freien Speicher der RAM-Disk mitbenutzt. (ah)

Literaturhinweise:

[1] J. Lietzow: »Individuell«, Seite 90 ff., TOS Ausgabe 12/91, ICP-\ferlag teterstetten

Die BPB-Struktur

typedef struct
{ 
	int recsiz;	( 512)	Sektorgröße in Bytes
	int elsiz;	( 2)	Cluster-Größe in Sektoren
	int clsizb;	(1024)	Cluster-Größe in Bytes
	int rdlen;	( 7)	Wurzelverzeichnisgröße in Sektoren
	int fsiz;	( 5)	FAT-Größe in Sektoren
	int fatrec;	( 6)	Startsektor der zweiten FAT
	int datrec;	( 18)	Nummer des ersten Datensektors
	int numcl;	( 711)	Anzahl der Daten-Cluster
	int bflags;	( 0)	0 = 12-Bit-, 1 = 16-Bit-FAT
} BPB;		

Tabelle 1. Aufbau der Struktur «BPB», die von «hdv_bpb» geliefert wird.

Start Anzahl Bedeutung
0 1 Boot-Sektor enthält Informationen zur Größe und Organisationsstruktur des Mediums
1 5 erste FAT
6 5 zweite FAT
11 7 Wurzeiverzeichnis
18 1422 ab hier stehen die eigentlichen Daten

Tabelle 2. Organisation der Sektoren am Beispiel einer doppelseitigen Diskette (720 KBytes)


Jürgen Lietzow
Aus: TOS 03 / 1992, Seite 83

Links

Copyright-Bestimmungen: siehe Über diese Seite