Assembler-Einbindung in Pascal

Manchmal möchte man in einer Hochsprache wie z.B. Pascal Programmfunktionen realisieren, die nur schwer oder mit erheblichem Aufwand zu verwirklichen sind. Für solche Fälle hat MAXON-Pascal einen sogenannten Inline-Assembler. Es bietet sich also an, diesen für bestimmte Aufgabenstellungen zu nutzen.

Gleich vorweg noch ein Hinweis. Die an dieser Stelle vorgestellten Routinen sind zwar für MAXON-Pascal geschrieben, sollten aber auch in anderen Hochsprachen, die über einen Inline-Assembler verfügen, verwendbar sein. Allerdings müssen eventuell die sprach-spezifischen Elemente (Functions-, Procedure- und Unit-Deklarationen, Typen, Variablen und Konstanten) angepaßt werden.

Wie's gemacht wird

Die in diesem Artikel vorgestellten Routinen sind wegen der besseren Verfügbarkeit und Übersicht in einer eigenen Unit mit der Bezeichnung ASMLIB angelegt. In deren globalem Teil wurden auch die Typen und Konstanten deklariert, welche zur Anwendung der Assembler-Routinen benötigt werden. Das hat den Vorteil, daß auf eine Deklaration im Hauptprogramm verzichtet werden kann, wodurch unnötiges Suchen nach der korrekten Typ- oder Konstantendefinition erspart bleibt. Da im Handbuch von MAXON-Pascal der Aufbau einer Unit nur kurz angerissen wird, will ich zum besseren Verständnis des Listings zu diesem Thema etwas weiter ausholen.

Eine Unit stellt im Prinzip eine Sammlung von Routinen dar, die von verschiedenen Programmen genutzt werden können. In der Regel liegen sie schon in compilierter Form vor, wodurch die benötigten Funktionen nur noch durch den Linker dem eigentlichen Programm hinzugefügt werden müssen. Durch dieses Konzept ist es leicht möglich, ein Programm in einzelne Module zu zerlegen und dadurch z.B. die Übersetzungszeiten zu verkürzen.

Ebenso stehen dadurch Routinen, welche für verschiedene Programme verwendbar sind, so auf einfache Art zur Verfügung. Nun zum Aufbau einer Unit.

Genau wie ein Pascal-Programm zuallererst mit der Anweisung PROGRAM name einen Namen erhält, wird auch einer Unit mit der Anweisung UNIT name ein Name zugeteilt. Dieser Name muß mit dem Dateinamen identisch sein, sonst wird die Unit beim späteren Compilieren des Programmes nicht gefunden.

Danach wird mit der Anweisung INTERFACE der globale Teil der Unit begonnen, dessen Inhalt später dem Programm, das diese Unit nutzt, zugänglich sein soll. Im folgenden werden hier die Konstanten- und Typendeklarationen getroffen, die zur Verwendung der implementierten Funktionen notwendig sind. Ebenso werden hier die Procedure- und Functions-Köpfe der ansprechbaren Routinen eingetragen, um dem aufrufenden Programm den Zugriff auf diese zu ermöglichen.

Ab der darauf folgenden Anweisung IMPLEMENTATION beginnt der lokale Teil der Unit. Die hier deklarierten Konstanten und Variablen sind nur noch den Routinen innerhalb der Unit zugänglich. Die Funktionen und Prozeduren derselben werden hier wie in einem normalen Programm eingegeben. Bei den Assembler-Routinen in der Unit ASMLIB werden durch das Schlüsselwort ASSEMBLER, welches den Functions- und Procedure-Köpfen nachgestellt ist, diese als reine Assembler-Funktionen definiert. Dies spart einige Bytes an Code ein, da die Parameterübergabe sich einfacher gestaltet (siehe Handbuch). Ebenso kann dadurch die Anweisung BEGIN entfallen. Mit der Anweisung ASM beginnt dann der eigentliche Assembler-Quelltext und wird mit END; abgeschlossen. Das Ergebnis einer Funktion wird in der Variablen @result abgelegt, welche für eine Adresse auf dem Stack steht.

Von einer Unit können auch andere Units zusätzlich verwendet werden, indem diese nach den Compilieranweisungen INTERFACE oder IMPLEMENTATION durch USES name beim Compilieren hinzugefügt werden. Ob diese dann auch dem Hauptprogramm zugänglich sind, hängt davon ab, ob dies im globalen oder lokalen Teil der Unit geschehen ist (siehe Fisting: USES GemDecl, GemAES, usw.).

Das Ende der Unit bildet das Hauptprogramm, welches durch die Anweisungen BEGIN und END. eingefaßt wird. Diese Anweisungen müssen auch dann eingegeben werden, wenn kein Hauptprogramm benötigt wird (genaugenommen kann auch BEGIN entfallen, siehe Listing).

Allgemeine Übersicht

Bevor ich mit der Beschreibung der einzelnen Module beginne, stelle ich zuerst die in Assembler realisierten Funktionen vor.

DO_SOUND
Erzeugen verschiedener Tonfolgen und Melodien

XY_SIZE
Ermitteln der aktuellen Auflösung und Farbtiefe

FREE_MEM
Ermitteln des nutzbaren und insgesamt freien Speichers

RES_MEM
Reservieren des kleinsten passenden Speicherblockes

DESK_AKT
Ermitteln, ob das Desktop aktiv ist oder nicht

F_SELECT
Ausgeben der normalen oder erweiterten Fileselectbox

SCREEN
Sichern und Retten eines Bildschirmbereiches

Selbstverständlich sind viele dieser Funktionen auch in Pascal zu realisieren, würden dann aber auf jeden Fall erheblich längeren Code erzeugen oder langsamer arbeiten.

Jetzt zu den einzelnen Routinen. Eine ausführliche Beschreibung der Assembler-Befehle spare ich mir, da die Listings mehr als ausreichend kommentiert sind.

Der Ton macht die Musik

So mancher hat schon Alert- oder Dialogboxen gesehen oder programmiert, welche sich mit einem hellen ,Ping‘ bemerkbar machen. Nun wird dieser Ton in Pascal zum Beispiel durch den Befehl write-(chr(7)) erzeugt. Das hat bei einem sauberen GEM-Programm häufig zur Folge, daß vom Linker der Objektcode für write nur zu diesem Zweck in das Programm eingebunden wird. Bedingt durch die Mächtigkeit dieses Befehls hat das zur Folge, daß sich der Programmcode mehr oder weniger stark aufbläht.

Im Vergleich dazu weniger speicherfressend ist das Assembler-Modul DO_SOUND. In der vorgestellten Fassung beinhaltet es fünf verschiedene Tonfolgen, welche sich durchweg besser anhören als das bekannte ,Ping‘. Mit etwas Programmiererfahrung in Assembler ist es sogar beliebig erweiterbar. Voraussetzung dafür ist lediglich ein Musikprogramm, das in der Lage ist, Musikstücke oder Töne im XBIOS-Format zu speichern (Musix32 o.a.). Diese Dateien müssen dann durch ein Hilfsprogramm vom Hex- ins ASCII-Format übertragen und in den Quellcode des Assembler-Moduls eingebunden werden.

Um eine bestimmte Tonfolge aufzurufen, muß der als Procedure definierten Assembler-Routine eine Funktionsnummer übergeben werden, welche den jeweils gewünschten Sound identifiziert. Vordefiniert sind schon die Nummern 10-14 im globalen Teil der Unit mit den Konstanten BIGBEN bis BIMBAM. Diese stehen für Glockenspiel (BIGBEN), Fehlermeldung (FEHLER), Warnung (WARNUNG) und zwei nicht festgelegte Tonfolgen (SIGNAL und BIMBAM). Die gewünschte Tonfolge wird dann durch die XBIOS-Funktion DOSOUND (XBIOS 14) als Hintergrundausgabe erzeugt.

Zum Einbinden weiterer Tonfolgen muß zunächst ein neues Label definiert werden, unter dem dann die entsprechenden Sound-Daten einzutragen sind. Zum Aktivieren dieser Tonfolgen ist danach noch eine neue Funktionsnummer festzulegen und hinter dem Label @sound5 der Befehl bne @return durch bne @sound6 zu ersetzen. Ab dem neu anzulegenden Label @sound6 ist nun die Abfrage für die neue Tonfolge einzubauen und für den Fall, daß die übergebene Funktionsnummer nicht paßt, mit bne @return abzuschließen. Für weitere Sounddaten ist in gleicher Weise zu verfahren.

Die Auflösung, bitte

Im Zeitalter von Grafikkarten ist ein Programm, welches nur die drei Standardauflösungen des ST unterstützt, selbst im PD-Bereich nicht vor Kritik sicher. Zwar werden vom VDI alle benötigten Parameter der Ausgabegeräte (auch die des Monitors) bei der Anmeldung des Programmes durch den Aufruf von v_opnvwk (VDI 100) zur Verfügung gestellt, dies nutzt einem jedoch überhaupt nichts in einem Programm, in dem diese Anmeldung nicht durchgeführt wird (TOS-Programme oder ähnliche).

Generell besteht die Möglichkeit, die Parameter der x- und y-Auflösung noch nachträglich vom VDI in Erfahrung zu bringen, es gibt aber auch noch einen anderen Weg an diese Werte zu gelangen. Die gewünschten Informationen stehen nämlich auch in den Line-A-Variablen, deren Adresse man durch den Aufruf von A_Init (OP-Code $A000h) erhält. Nun sollen ja die Line-A Routinen in einem Programm nicht mehr verwendet werden, wenn dieses auch für spätere TOS-Versionen kompatibel bleiben soll. Speziell die Abfrage dieser Variablen dürfte aber nach meiner Meinung unkritisch sein, zumal auch viele Grafikerweiterungen ihre Parameter darin noch hinterlegen.

Die Assembler-Routine XY_SIZE ermittelt die gewünschten Werte folglich aus diesen Variablen, und zwar aus dem negativen Teil davon (siehe [1]). Relativ zur Startadresse der Line-A-Variablen mit einem negativen Offset von -4 (y-Auflösung) und -12 (x-Auflösung) Worten befinden sich die die gesuchten Parameter. In den übergebenen Variablen der Procedure (x, y und planes) werden die Werte für die aktuelle Pixel-Zahl in x- und y-Richtung sowie die Zahl der Bitplanes für die Anzahl der Farben zurückgeliefert. Der Wert für planes befindet sich genau an der Startadresse der Line-A-Variablen und gibt die Farbenanzahl im Binärcode an (s/w =1,4 Farben = 2, 16 Farben = 4, usw.).

Freier Speicher ist nicht freier Speicher

Für den einen oder anderen Leser mag das ein Widerspruch sein, das Rätsel kann aber schnell aufgeklärt werden. Wenn nämlich durch die GEMDOS-Funktion Malloc der freie Speicher abgefragt wird, erhält man als Ergebnis nicht den tatsächlich freien Speicher, sondern immer nur den größten freien Speicherblock zurückgeliefert. Das kann nach dem Booten auch tatsächlich der gesamte freie Speicher sein; je länger der Rechner aber arbeitet, umso unwahrscheinlicher wird dies. Wenn nämlich von verschiedenen Programmen Speicher belegt und nicht in der gleichen Reihenfolge wieder freigegeben wird, kommt es zu mehr oder weniger vielen freien und belegten Speicherblöcken (siehe [1]). Abhängig von den verwendeten Programmen wundert man sich dann, warum der freie Speicher immer kleiner wird, je länger man arbeitet.

Klarheit schafft hier nun die Procedure EREE_MEM. Das geschieht nach folgendem Prinzip. Mit der ersten Abfrage nach dem freien Speicher wird die Größe des größten freien Blockes zurückgeliefert, also der übliche Wert. Dieser wird in den Variablen mblk und mges gepuffert. Danach wird dieser Speicherblock durch einen Aufruf der GEMDOS-Funktion Malloc belegt. Die Adresse dieses Blockes bei wird an den Anfang desselben geschrieben und der Adreßzähler für den nächsten Eintrag erhöht. Dadurch wird die Verwaltung der einzelnen Blockadressen recht flexibel, ohne Speicherplatz vom eigentlichen Programm in Form von Variablen zu verwenden.

Danach wird wieder der freie Speicher abgefragt. Bei einem Rückgabewert größer Null ist noch Speicher frei, und der Wert wird zum Inhalt von mges addiert. Dieser Vorgang von Speicher belegen und Abfragen wiederholt sich solange, bis als freier Speicher Null zurückgeliefert wird. Danach werden die belegten Blöcke wieder freigegeben und das Ergebnis an die Variablen frei und g_frei übergeben. Somit erhält man in einem Aufruf einen kompletten Überblick über den freien Arbeitsspeicher, wobei der Wert von g_frei die Größe des tatsächlich freien Speichers angibt.

Komfortable Speicherreservierung

Nach einer Routine, die den gesamten freien Speicher abfragt, bietet sich eigentlich eine Funktion an, mit der ein Speicherblock reserviert werden kann, ohne daß automatisch der Arbeitsspeicher immer mehr zerstückelt wird. Die Funktion RES_MEM erfüllt weitgehend diese Forderung. Sie umgeht nämlich das schon beschriebene Verhalten des Betriebssystems, sich bei der Speicherreservierung immer vom größten freien Block zu bedienen. Im Gegensatz zu diesem wird durch RES_MEM der angeforderte Speicher immer vom kleinsten passenden Block reserviert.

Die Vorgehens weise dabei ist ähnlich wie bei der Abfrage nach dem gesamten freien Speicher. Zuerst wird die Größe des größten freien Speicherblockes ermittelt und mit der zu reservierenden Speichergröße aus der Variablen block verglichen.

Ist dieser kleiner als der gewünschte Wert, wird die Funktion beendet und als Ergebnis Null zurückgeliefert. Reicht der freie Speicher hingegen aus, wird dieser jetzt belegt und dessen Adresse nach dem gleichen Verfahren, wie es die Funktion FREE_MEM benutzt, gesichert. Danach erfolgt wieder eine Abfrage des freien Speichers. Ist dieser immer noch größer als der angeforderte Wert, wird dessen Größe gesichert und der Block anschließend belegt. Dieser Vorgang wiederholt sich solange, bis der freie Speicher kleiner als der angeforderte Wert ist. Daraufhin wird der zuletzt reservierte Block durch einen Aufruf der GEMDOS-Funktion MSHRINK um die Größe der angeforderten Speichermenge reduziert und der freigewordene Bereich anschließend für das aufrufende Programm reserviert.

Dieses Vorgehen bewirkt, daß der angeforderte Speicher immer am Ende des am besten passenden freien Speicherblockes zu finden ist. War dies auch gleichzeitig der größte freie Block, kann sich kein weiteres Programm (normalerweise das TOS) hinter diesem Block einklinken und den Arbeitsspeicher dadurch zerstückeln. Sollte andererseits ein kleinerer Speicherblock ausgereicht haben, wird durch dessen Belegung ebenfalls einer Zerstückelung des Arbeitsspeichers vorgebeugt.

Die Adresse des reservierten Blockes wird nach dem Verlassen der Funktion als Ergebnis zurückgeliefert. Danach werden alle bis dahin belegten Speicherblöcke wieder freigegeben und die Funktion beendet. Um den Speicherblock freizugeben der für das Programm reserviert wurde, muß die GEMDOS-Funktion MFREE benutzt werden, der als Parameter die Adresse des reservierten Speicherbereiches übergeben wird.

Desktop oder nicht?

Das ist für manchen Anwendungsfall die Frage. Speziell für Accessories kann es recht nützlich sein zu wissen, ob das Desktop aktiv ist oder nicht. Wie ja bekannt sein dürfte, ist es zum Beispiel nicht möglich, für ein Accessory dauerhaft Speicher zu reservieren, da dieser mit dem Ende der aktiven Hauptapplikation freigegeben wird.

Wird diese Reservierung jedoch vorgenommen, solange das Desktop aktiv ist, bleibt sie bis zu einem Auflösungswechsel oder Reset bestehen. Das liegt daran, daß das Desktop auch bei einem Programmstart nicht beendet wird, da es ein Teil des Betriebssystems ist. Bleibt die Frage, wie man feststellen kann, ob momentan ein Programm oder das Desktop arbeitet. Dazu gibt es eine sichere und einfache Möglichkeit. In der Systemvariablen sysbase an der Adresse $04F2h befindet sich ein Zeiger auf die Basepage des aktiven Prozesses. In dieser sind alle für das jeweilige Programm relevanten Daten eingetragen. Unter anderem auch die Länge des sogenannten Textsegmentes, in welchem der eigentliche Programmcode steht. Im Falle des Desktop ist dieser Wert immer Null und kann daher zur Identifikation desselben benutzt werden.

Dieses Verfahren benutzt folglich auch die Funktion DESK_AKT. Zuerst wird der Prozessor in den Supervisormodus geschaltet und überprüft, ob die verwendete TOS-Version kleiner 1.02 ist. Ist das der Fall, steht im System-Header noch nicht die PD-Adresse des aktuellen Programmes (Zeiger auf die Basepage) und es muß auf eine inzwischen offiziell dokumentierte feste Adresse zugegriffen werden. Andernfalls wird die im System-Header angegebene Adresse verwendet.

Nachdem jetzt die PD-Adresse des aktiven Programmes bekannt ist, wird die Länge des Textsegmentes auf Null überprüft. Ist dies der Fall, befinden wir uns im Desktop und von der Funktion wird TRUE zurückgegeben, andernfalls FALSE.

Immer die optimale Fileselectbox

Wenn jemand mit einem TOS der Version 1.04 oder größer arbeitet, wird er sicher schon festgestellt haben, daß die wenigsten Programme die neue Fileselectbox dieser Versionen, welche die Möglichkeit bieten, eine Kommentarzeile mitzugeben, unterstützen. Dies kann bei Verwendung der Funktion F_SELECT nicht passieren. Wenn das aufrufende Programm auf einem Rechner mit TOS 1.04 oder größer läuft, wird von ihr automatisch die Box mit Kommentarzeile aufgerufen, im anderen Fall die normale Box ohne Kommentarzeile.

Der Funktion werden als Parameter je ein Zeiger (VAR-Deklaration) auf den voreingestellten Pfad und Dateinamen (Typ String[ 128]) sowie auf einen Kommentar (Typ String[40]) übergeben. Zu beachten wäre dabei, daß der Pfadname zwingend erforderlich ist (also kein Leer-String), da es sonst unter Garantie zu Bombenwürfen durch das AES kommt. Die Angabe eines Dateinamens oder einer Kommentarzeile kann dagegen unterbleiben. Als Rückmeldung erhält man TRUE Betätigen des OK-Knopfes und FALSE bei Betätigen des ABBRUCH-Knopfes oder beim Auftreten eines Fehlers. Durch Anwahl von OK werden in der Pfadvariablen der aktuelle neue Pfad und in der Dateivariablen die gewählte Datei inklusive des Zugriffspfads zurückgegeben. Der String mit der Kommentarzeile bleibt unverändert.

Jetzt zur Realisierung. Nach dem Aufruf der Funktion F_SELECT wird der Datei-und Pfadname vom Pascal-String zum C-String (TOS-Format) konvertiert. Das geschieht durch Verschieben des Pascal-Strings um ein Zeichen nach links und Einfügen eines Null-Bytes (chr(0)) am String-Ende. Das ist erforderlich, da ein C-String keine Längenangabe kennt, sondern zum Erkennen des String-Endes besagtes Null-Byte benutzt. Danach erfolgt die Kontrolle der TOS-Version unter Verwendung der Systemvariablen sysbase, wozu in den Supervisor-Modus geschaltet wird. Bei einer Versionsnummer kleiner 1.04 ($0104h) wird die altbekannte Fileselectbox verwendet, andernfalls die erweiterte neue Box, wobei für diese zuvor noch die Kommentarzeile vom Pascal-String zum C-String konvertiert werden muß. Die einfache Box wird danach durch einen Aufruf der AES-Funktion FSEL_INPUT (AES 90), die erweiterte Box durch einen Aufruf von ESEL_EXINPUT (AES 91) erzeugt. Hierfür muß jeweils der AES-Parameterblock entsprechend ausgefüllt werden (siehe [1]). Die Variablen, welche für die AES-Aufrufe benutzt werden (control, intout, usw.), sind in der Unit Gem-Decl deklariert (siehe [2]).

Nachdem die Fileselectbox verlassen wurde, werden die übergebenen Rückmeldungen ausgewertet. Steht in der Variablen intout[0] eine Null, ist ein Fehler aufgetreten (Speichermangel laut DR-Dokumentation), und die Funktion wird verlassen. Andernfalls wird die Variable intout[1] abgefragt, um den betätigten Button zu ermitteln. Enthält diese den Wert Null, wurde ABBRUCH betätigt, und die Funktion wird ebenfalls verlassen. Bei Betätigung von OK wird der zurückgelieferte Pfadname mit dem Dateinamen verknüpft und gleichzeitig wieder in einen Pascal-String konvertiert. Anschließend wird auch der Pfadname rückkonvertiert und die Funktion beendet.

Bildschirm-Sicherung leicht gemacht

In einem GEM-Programm kommt es immer wieder vor, daß bestimmte Bildschirmbereiche restauriert werden müssen. Diese Aufgabe wird jedoch nicht automatisch vom Betriebssystem übernommen, sondern obliegt alleine dem jeweiligen Anwenderprogramm. Das VDI stellt lediglich Funktionen zur Verfügung, mit denen unter anderem das Sichern und Restaurieren des Bildschirmes möglich sind. Da sich der Umgang mit diesen Funktionen recht aufwendig gestaltet, wurde in der Unit ASMLIB auch eine Procedure implementiert, die diese Aufgaben erfüllen kann.

Die Procedure SCREEN verwendet zur Sicherung und Restaurierung des Bildschirmes die VDI-Funktion VRO_CPYFM (VDI 109), mit der ganze Speicherblöcke schnell verschoben werden können. Als Parameter werden der Procedure in den Variablen x,y, w, h die Koordinaten des zu bearbeitenden Bildschirmbereiches übergeben. Mit der Variablen modus wird durch vordefinierte Konstanten die Betriebsart festgelegt. Es besteht dabei die Möglichkeit, den gesamten Bildschirm zu sichern oder zu restaurieren (S_ALL, R_ALL) oder nur einen bestimmten Teil davon (S_XY, R_XY). Soll nur ein Teil bearbeitet werden, ist darauf zu achten, daß die Koordinaten für die Restaurierung mit denen des gesicherten Bildbereiches übereinstimmen, da es sonst zu überraschenden, aber unbrauchbaren Ergebnissen kommen kann. Es dürfen auch keine Werte übergeben werden, die über die physikalischen Bildschirmgrenzen hinausgehen. Bei Angabe von S_ALL oder R_ALL spielen die Koordinaten keine Rolle, da sie nicht beachtet werden. In der Variablen scr_adr wird schließlich die Adresse des Speicherblockes zurückgegeben (Sichern) oder übergeben (Restaurieren), in den der Bildschirminhalt gerettet wurde. Dieser Speicherblock muß durch einen Aufruf der GEM-DOS-Funktion MFREE wieder freigegeben werden, wenn der gesicherte Bildbereich nicht mehr benötigt wird.

Soll die Ausgabe auf einen bestimmten Bildschirmbereich beschränkt werden, ist vor dem Aufruf der Procedure SCREEN noch das Clipping mittels der VDI-Funktion VS_CLIP (VDI 129) auf die entsprechenden Koordinaten zu setzen. Die verwendeten Variablen bei den AES- und VDI-Aufrufen innerhalb der Procedure SCREEN sind in den Units GemDecl und GemVDI definiert (siehe [2]).

Nun zum Programmablauf im einzelnen. Als erstes wird nach dem Aufruf der Procedure die Größe des Bildschirmes in Pixeln ermittelt. Dies geschieht unabhängig davon, ob der ganze Bildschirm oder nur ein Teil von ihm bearbeitet werden soll, da die Anzahl der Bitplanes für die Farben auf jeden Fall benötigt wird, um den Speicherbedarf ausrechnen zu können. Die erforderlichen Daten werden durch einen Aufruf der Procedure XY_SIZE eingeholt, wobei dieser Procedure-Aufruf auf Assembler-Ebene geschieht und dadurch für manchen Leser vielleicht von besonderem Interesse sein dürfte. Danach erst wird verglichen ob der ganze Bildschirm oder nur ein Teil davon bearbeitet werden soll. Abhängig vom Ergebnis wird der Inhalt der Variablen x, y, vv, h mit den aktuellen Daten des Bildschirmes überschrieben. Sodann erfolgt eine Umrechnung der Breiten- und Höhenangaben (Pixel-Werte) in die Koordinaten x2 und v2.

Wenn in der Variablen modus ein Restaurieren des Bildschirmes angegeben ist, wird anschließend in die entsprechende Routine verzweigt (ab dem Label @Restore), andernfalls mit der Ermittlung des Speicherbedarfs für die Sicherung begonnen. Dazu wird die gewünschte Breite des Bildschirmausschnitts mit dessen Höhe (Pixel-Werte) multipliziert, dann durch 8 dividiert, um Byte-Werte zu erhalten, und wieder mit der Anzahl der Bitplanes für die Farben multipliziert. Da es bei Verwendung einer Grafikkarte jedoch leicht Vorkommen kann, daß der Bildschirmspeicher größer als 64 KB ist, kann zum Umrechnen von Pixel- auf Byte-Werte und dem anschließenden Multiplizieren mit den Farbebenen der Divisions- und Multiplikationsbefehl des MC68000 nicht verwendet werden. Dabei würde nämlich für einen dieser Befehle die Wortgrenze überschritten, die entweder für das Ergebnis oder die Parameter vorgegeben ist. Daher wird diese Berechnung durch bitweises Schieben nach rechts oder links vorgenommen, wobei jedes verschobene Bit nach rechts oder links einer Division oder Multiplikation mit 2 entspricht.

Ist diese Berechnung erfolgt, wird der benötigte Speicher durch einen Aufruf der Funktion RES_MEM (ebenfalls auf Assembler-Ebene) reserviert und dessen Adresse an die Variable scr_adr übergeben. Wenn der Wert 0 zurückgeliefert wurde, war ein Speicherblock in der benötigten Größe nicht zu reservieren, und die Procedure wird beendet. Andernfalls kann mit dem Sichern des Bildschirminhaltes begonnen werden.

Hierzu wird vorab schon das ptsin-Feld für den VDI-Aufruf ausgefüllt, da hier für Sicherung oder Restaurierung unterschiedliche Werte einzutragen sind. Danach werden die Adressen des Ziel- und Quell-MFDBs auf dem Stack abgelegt und zum Label @VroCpyf verzweigt. Hier wird zuerst das aktuelle Handle des Bildschirmes durch den AES-Aufruf GRAF_-HANDLE(AES 77) ermittelt. Danach wird die MFDB für Quelle und Ziel ausgefüllt, wobei deren Aufbau und Belegung in [1] oder [2] nachzulesen ist. Für den MFDB des Bildschirmes braucht nur ein Null-Pointer übergeben zu werden, vom VDI werden dann automatisch die richtigen Werte eingesetzt. Danach erfolgt die Sicherung des Bildschirmes durch einen Aufruf von VRO_COPYEM (VDI 109), wobei vorher noch das control- sowie das intin-Feld entsprechend besetzt werden.

Die einzelnen Parameter dieser VDI-Funktion zu erläutern, erspare ich mir. Wer sich für Einzelheiten interessiert, kann ebenfalls in [1] oder [2] nachschlagen. Nach erfolgter Sicherung wird die Unterroutine verlassen und die Procedure beendet.

Zum Restaurieren des Bildschirmes wird prinzipiell der gleiche Weg gegangen. Der einzige Unterschied besteht darin, daß für diesen Vorgang ab dem Label @Restore das ptsin-Feld entsprechend neu ausgefüllt sowie der Ziel- und Quell-MFDB miteinander vertauscht werden müssen. Danach wird ab dem Label @VroCpyf der oben beschriebene Vorgang wiederholt.

Zusammenfassung

Wie schon eingangs erwähnt, kann man etliche dieser Routinen auch in Pascal programmieren. Dies trifft für die einzelnen Routinen mehr oder weniger zu. Die Procedure XY_SIZE ist ein klassischer Fall für die Assembler-Programmierung. Mit Pascal an die Adresse der Line-A-Variablen heranzukommen, dürfte wohl einigen Aufwand erfordern. Die Funktion der Procedure DO_SOUND oder der Funktion DESK_AKT in Pascal umzusetzen, wäre schon einfacher, gefolgt von FREE_MEM und RES_MEM.

Eine Sonderstellung nehmen die Funktion F_SELECT sowie die Procedure SCREEN ein. Diese könnten ohne weiteres auch anders realisiert werden. Zumal der eigentliche Vorteil der Assembler-Programmierung, nämlich die Ausführungsgeschwindigkeit des Programmcodes, hier nicht zum Tragen kommen kann, da die Routinen durch das Betriebssystem ausgebremst werden. Für die Realisierung in Assembler spricht allerdings die Kürze des erzeugten Codes. Gerade dieser Punkt dürfte hier von Bedeutung sein, da diese Funktionen in fast jedem GEM-Programm benötigt werden. Ein anderer Aspekt ist noch der Umstand, daß nach Wissen des Autors bisher noch keine Assembler-Routinen für MAXON-Pascal veröffentlicht wurden, welche AES- oder VDI-Aufrufe tätigen. Daher dürften diese Funktionen mindestens aus diesem Blickwinkel interessant sein.

Zusammenfassend hoffe ich, daß dieser Artikel den einen oder anderen Leser dazu ermuntert, die Möglichkeiten, die der Inline-Assembler bietet, auch zu nutzen -zumal sich die Assembler-Programmierung mit diesem als recht angenehm und fehlertolerant herausgestellt hat. Es kam bei den unvermeidlichen Programmierfehlern in den seltensten Fällen zu einem Systemabsturz. Die fehlerhafte Stelle ließ sich in den meisten Fällen mit der ,Find Error‘-Funktion der Pascal-Shell selbst im Assembler-Quelltext zuverlässig finden. Daher kann diese Art der Assembler-Programmierung auch für Anfänger, die sich in die Materie einarbeiten wollen, empfohlen werden.

Literatur:

[1] Jankowski, Reschke, Rabich, ATARI ST-Profibuch, SYBEX-Verlag
[2] Handbuch von MAXON-Pascal

UNIT AsmLib;

{****************************************************}
{* Die Unit AsmLib enthält folgende                 *}
{* Assembler-Routinen:                              *}
{*                                                  *}
{* procedure Do_Sound                               *}
{* > ERZEUGT VERSCHIEDENE TONFOLGEN <               *}
{*                                                  *}
{* procedure XY_Size                                *}
{* > ERMITTELT BILDSCHIRMAUFLÖSUNG <                *}
{*                                                  *}
{* procedure Free_Mem                               *}
{* > ERMITTELT GESAMTEN FREIEN SPEICHER <           *}
{*                                                  *}
{* function Res_Mem                                 *}
{* > RESERVIERT SPEICHER <                          *}
{*                                                  *}
{* function Desk_Akt                                *}
{* > ERMITTELT OB DESKTOP AKTIV IST <               *}
{*                                                  *}
{* function F_Select                                *}
{* > ERZEUGT DIE OPTIMALE FILESELECTBOX <           *}
{*                                                  *}
{* procedure Screen                                 *}
{* > SICHERT/RETTET BILDSCHIRMINHALT <              *}
{*                                                  *}
{*                                                  *}
{* (c) 1992 MAXON Computer                          *}
{* by Jürgen Scherf                                 *}
{*                                                  *}
{****************************************************}

{----------------GLOBALER TEIL--------------------}
Interface 
{$F+,D+,R+,S+}

uses GemDecl,GemVDI;

    const { GLOBALE CONST. DER UNIT }
        BIGBEN = 10; { Const. für Sound }
        FEHLER = 11;
        WARNUNG= 12;
        SIGNAL = 13;
        BIMBAM = 14;
        S_XY   = 20; { Const. für Screen }
        R_XY   = 21;
        S_ALL  = 22;
        R_ALL  = 23;

    type { GLOBALE TYPEN DER UNIT }
        Str128 = String[128];
        Str40 = String[40];

    { FUNCTIONEN/PROCEDUREN DER UNIT BEKANNTMACHEN } 
    procedure Do_Sound(nummer: integer);

    procedure XY_Size(VAR x,y,planes: integer);

    procedure Free_Mem(VAR frei,g_frei: LongInt);

    function Res_Mem(block: LongInt): Pointer;

    function Desk_Akt: boolean;

    function F_Select(VAR pfad,datei: Str128;
                        VAR titel: Str40): boolean;

    procedure Screen(modus,x,y,w,h: integer;
                        VAR scr_adr: LongInt);

{------------------LOKALER TEIL----------------  }
Implementation 
{$F+, D+,R+,S+}

    const { LOKALE CONST. DER UNIT }
        AES_VDI = 02;   { GEM-Aufruf für AES/VDI }
        VDI     = 115;  { Funktionscode für VDI }
        AES     = 200;  { Funktionscode für AES }
        fs_inp  = 90;   { normale Fileselectbox }
        fs_exin = 91;   { erweiterte " }
        Gemdos  = 01;   { GEMDOS-Aufruf }
        XBios   = 14;   { XBIOS-Aufruf }
        Dosound = 32;   { Tonfolge ausgeben }
        Malloc  = 72;   { Speicherresevierung }
        Mfree   = 73;   { Speicherfreigabe }
        Mshrink = 74;   { Res. Speicher verkleinern } 
        Supexec = 38;   { XBIOS-Supexec }
        sysbase = $4f2; { Adr. der Var. SYSBASE }
        tos_102 = $102; { TOS 1.02 Kennung }
        def_pd  = $602c; { Adr. PD bei TOS < 1.02 }
        Y_DOT   = -4;   { Offset für Y-Pixel }
        X_DOT   = -12;  { Offset für X-Pixel }
        A_INIT  = $a000; { Adr. der Line-A Var. }
        CopRast = 109;  { VDI COPY RASTER }
        GrfHand = 77;   { AES GRAF_HANDLE }

var { LOKALE VARIABLEN DER UNIT }
    reg_A2 : LongInt;

    madr,mges,mblk      : LongInt;
    { VAR. FÜR FREE_MEM & RES_MEM } 
    akt_PD              : LongInt;
    { VAR. FÜR AKT_PD }
    t_pfd,t_dat         : Array[0..128] of char;
    t_tit               : Array[0..40] of char;
    tos_ver             : integer;
    { VAR. FÜR F_SELECT }
    akt_w, akt_h, akt_pl,
    handle,x2,y2        : integer;
    Mem_MFDB,Scr_MFDB   : MFDB;
    { VAR. FÜR SCREEN }

{-------------------------------------}
procedure Do_Sound(nummer: integer);
(* GIBT VERSCHIEDENE TONFOLGEN AUS *)
ASSEMBLER;

ASM
            (* PARAMETER AN REGISTER D1 *) 
            move.w  nummer,d1

            (* KONTROLLE OB BIGBEN GEWÜNSCHT *) 
@sound1:    cmpi.w  #BIGBEN,d1 { Bigben? }
            bne     @sound2 { wenn nicht weiter }
            pea     @bigben { Bigben auf Stack }
            bra     @go_sound { Tonfolge ausgeben }

            (* KONTROLLE OB FEHLER GEWÜNSCHT *) 
@sound2:    cmpi.w  #FEHLER,d1 { Fehler? }
            bne     @sound3 { wenn nicht weiter }
            pea     @fehler { Fehler auf Stack }
            bra     @go_sound { Tonfolge ausgeben }

            (* KONTROLLE OB WARNUNG GEWÜNSCHT *) 
@sound3:    cmpi.w  #WARNUNG,d1 { Warnung? }
            bne     @sound4 { wenn nicht weiter }
            pea     @warnung { Warnung auf Stack }
            bra     @go_sound { Tonfolge ausgeben }

            (* KONTROLLE OB SIGNAL GEWÜNSCHT *) 
@sound4:    cmpi.w  #SIGNAL,d1 { Signal? }
            bne     @sound5 { wenn nicht weiter }
            pea     @signal { Signal auf Stack }
            bra     @go_sound { Tonfolge ausgeben }

            (* KONTROLLE OB BIMBAM GEWÜNSCHT *) 
@sound5:    cmpi.w  #BIMBAM,d1 { Bimbam? }
            bne     @return { wenn nicht zurück }
            pea     @bimbam { Bimbam auf Stack }

(* SOUND ABSPIELEN *)

@go_sound:  move.w  #Dosound,-(sp)  { Funk.DOSOUND } 
            trap    #XBios          { XBIOS aufrufen }
            addq.l  #6,sp           { Stack korrigieren}
            bra     @return         { Procedure beenden }

(* SOUNDDATEN *)

@bigben:
        DC.w $0810,$0900,$0a00,$0b01
        DC.w $0c00,$0d09,$07f8,$0015
        DC.w $0102,$0c48,$0d09,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$00a0
        DC.w $0d09,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$0057,$0d09,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$0081
        DC.w $0103,$0d09,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8206,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$0d09,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$0057,$0102
        DC.w $0d09,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$0015,$0d09,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$00a0
        DC.w $0d09,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8206,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8206
        DC.w $8203,$8203,$8203,$0015
        DC.w $0d09,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$00a0,$0d09,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$0057
        DC.w $0d09,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$0081,$0103,$0d09
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8206
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$0d09
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $0057,$0102,$0d09,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$0015
        DC.w $0d09,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$00a0,$0c60,$0d09
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$8203,$8203,$8203
        DC.w $8203,$07ff,$8200 
@warnung:
        DC.w $0810,$0900,$0a00,$0b01
        DC.w $0c00,$0d09,$07f8,$00c0
        DC.w $0101,$0c0c,$0d09,$02c0
        DC.w $0301,$090c,$04a0,$0502
        DC.w $0a0c,$8206,$0d09,$8206
        DC.w $0d09,$8206,$0057,$0102 
        DC.w $0c48,$0d09,$0257,$0302
        DC.w $0481,$0503,$8224,$0900 
        DC.w $0a00,$8203,$07ff,$8200 
@fehler:
        DC.w $0800,$0900,$0a00,$0b01
        DC.w $0c00,$0d09,$07f8,$00c8
        DC.w $0100,$080c,$027b,$0301
        DC.w $090c,$04a3,$0502,$0a0c
        DC.w $8201,$00fd,$02c2,$0423
        DC.w $0503,$8201,$00c8,$027b
        DC.w $04a3,$0502,$8201,$00fd
        DC.w $02c2,$0423,$0503,$8201
        DC.w $00c8,$027b,$04a3,$0502
        DC.w $8201,$0800,$0900,$0a00
        DC.w $8201,$07ff,$8200 
@signal:
        DC.w $0810,$0900,$0a00,$0b01
        DC.w $0c00,$0d09,$07f8,$0015
        DC.w $0102,$0c80,$0d09,$8204
        DC.w $8204,$8204,$8204,$8204
        DC.w $8204,$8204,$8204,$8204
        DC.w $8204,$8204,$8204,$8204
        DC.w $8204,$8204,$8204,$8204
        DC.w $07ff,$8200 
@bimbam:
        DC.w $0810,$0900,$0a00,$0b01
        DC.w $0c00,$0d09,$07f8,$00c6
        DC.w $0100,$0clc,$0d09,$8207
        DC.w $8207,$00f9,$0c54,$0d09
        DC.w $8207,$8207,$8207,$8207
        DC.w $8207,$8207,$8207,$07ff
        DC.w $8200

@return: { Procedure verlassen }

END; {PROCEDURE SOUND }

{-------------------------------------------------}

procedure XY_Size(VAR x,y,planes: integer);

(* ERMITTELT DIE X- Y-AUFLÖSUNG, UND FARBEBENEN *) 
ASSEMBLER;

ASM

            (* XY-AUFLÖSUNG UND PLANES ERMITTELN *)
            DC.w A_INIT             { Adr.Lina-A Var. )
            move.l  x, a1           { Adr. von x }
            move.w  X_DOT(a0),(a1)  { Pixelzahl X } 
            move.l  y,a1            { Adr. von y }
            move.w  Y_DOT(a0),(a1)  { Pixelzahl Y }
            move.l  planes,a1       { Adr. von planes }
            move.w  (a0),(a1)       { Farbebenen }
END; { PROCEDURE XY-SIZE }

{-------------------------------------------------}

procedure Free_Mem(VAR frei,g_frei: LongInt);
(* LIEFERT GESAMTEN & GRÖSSTEN FREIEN SPEICHER *) 
ASSEMBLER;

ASM

            (* SPEICHERERMITTLUNG *)
            bsr     @MemFrei        { freier Speicher ? }
            cmpi.l  #0,d0           { noch Speicher frei ? }
            beq     @return         { nichts mehr frei }
            move.l  d0,mges         { Grösse sichern )
            move.l  d0,mblk
            bsr     @MemRes         { Speicher belegen }
            move.l  d0,madr         { Adresse sichern }
            movea.l d0,a1           { Adresse 1. Block }

@MemTest:   move.l  d0,(a1)+        { Adr. ablegen }
            bsr     @MemFrei        { freier Speicher ? }
            cmpi.l  #0,d0           { noch Speicher frei ? }
            beq     @Free           { nein, dann freigeben }
            add.l   d0,mges         { freien Speicher }
                                    { aufaddieren }
            bsr     @MemRes         { Speicher belegen }
            bra     @MemTest        { nächster f. Block }

            (* BELEGTEN SPEICHER FREIGEBEN *)
@Free:      move.l  -(a1),-(sp)     { Adr. belegter }
                                    { Speicherblock } 
            move.w  #Mfree,-(sp)    { Funktion MFREE } 
            trap    #Gemdos         { GEMDOS aufrufen }
            addq.l  #6,sp           { Stack korrigieren }
            move.l  madr,a0         { Adr. erster Block }
            cmpa.l  a0,a1           { alles freigegeben ? }
            bne     @Free           { noch nicht }
            bra     @return         { Procedure beenden }

            (* UP FREIER SPEICHER ABFRAGEN *) 
@MemFrei:   move.l  #-1,d0          { freier Speicher }
@MemRes:    move.l  d0,-(sp)
            move.w  #Malloc,-(sp)   { Funk. MALLOC } 
            trap    #Gemdos         { GEMDOS aufrufen }
            addq.l  #6,sp           { Stack korrigieren }
            rts                     { zurück zum Aufruf }

            (* ERGEBNISSE ÜBERGEBEN *)
@return:    move.l  frei,a0         { Adr. von FREI }
            move.l  g_frei,a1       { Adr. von G_FREI }
            move.l  mblk,(a0)       { freier Speicher }
            move.l  mges,(a1)       { gesamt freier )
                                    { Speicher }
                                    { Procedure verlassen }
END; { PROCEDURE FREE_MEM }

{-------------------------------------------------}

function Res_Mem(block: LongInt): Pointer;
(* RESERVIERT EINEN SPEICHERBLOCK *)
ASSEMBLER;

ASM

            (* SPEICHERERMITTLUNG *)
            clr.l   @result         { vorbesetzen }
            move.l  block,d1        { Param. übergeben }
            bsr     @MemFrei        { freier Speicher ? }
            move.l  d0,mblk         { Grösse sichern }
            cmp.l   d1,d0           { gross genug ? }
            blt     @return         { nein, also zurück }
            bsr     @MemRes         { Speicher belegen }
            move.l  d0,madr         { Adresse sichern }
            movea.l d0,a1           { Adresse 1. Block }
@MemTest:   move.l  d0,(a1)+        { Adr. ablegen }
            bsr     @MemFrei        { freier Speicher ? }
            cmp.l   d1,d0           { gross genug ? }
            blt     @Malloc         { nein, letzten Block }
                                    { belegen }
            move.l  d0,mblk         { Grösse sichern }
            bsr     @MemRes         { Block belegen }
            bra     @MemTest        { nächster f. Block }

            (* BLOCK AM SPEICHERENDE BELEGEN *) 
@Malloc:    sub.l   d1,mblk         { von Res. abziehen }
            move.l  mblk,-(sp)      { neue Länge }
            move.l  -4(a1),-(sp)    { Adr.des Blockes }
            move.w  #0,-(sp)        { Nullparameter >
            move.w  #Mshrink,-(sp)  { Funk.MSHRINK }
            trap    #Gemdos         { GEMDOS aufrufen }
            lea     12(sp),sp       { Stack korrigieren }
            move.l  d1,d0           { Blockgrösse an d0 }
            bsr     @MemRes         { Block belegen }
            move.l  d0,@result      { Adr. sichern }

            (* BELEGTEN SPEICHER FREIGEBEN *)
@Free:      move.l  -(a1),-(sp)     { Adr. belegter }
                                    { Speicherblock }
            move.w  #Mfree,-(sp)    { Funktion MFREE } 
            trap    #Gemdos         { GEMDOS aufrufen }
            addq.l  #6,sp           { Stack korrigieren }
            move.l  madr,a0         { Adr. erster Block }
            cmpa.l  a0,a1           { alles freigegeben ? }
            bne     @Free           { noch nicht }
            bra     @return         { Procedure beenden }

            (* UP FREIER SPEICHER ABFRAGEN *) 
@MemFrei:   move.l  #-1,d0          { freier Speicher }
@MemRes:    move.l  d0,-(sp)
            move.w  #Malloc,-(sp)   { Funk. MALLOC } 
            trap    #Gemdos         { GEMDOS aufrufen }
            addq.l  #6,sp           { Stack korrigieren >
            rts                     { zurück zum Aufruf }

@return:                            { Function verlassen }

END; { FUNKTION RES_MEM )

{-------------------------------------------------}

function Desk_Akt: boolean;
(* ERMITTELT OB DAS DESKTOP AKTIV IST *)
ASSEMBLER;

ASM

            (* KONTROLLE OB DESKTOP AKTIV *) 
            clr.b   @result         { vorbesetzen }
            bsr     @PD_Test        { PD-Adr. ermitteln }
            movea.l akt_PD,a0       { PD-Adr. nach a0 }
            cmpi.l  #0,$c(a0)       { Programmlänge 0 ? }
            bne     @return         { nein, nicht Desktop }
            move.b  #1,@result      { Desktop aktiv }
            bra     @return         { Funktion beenden }

            (* AKTUELLEN PD ABFRAGEN *)
@PD_Test:   pea     @Akt_PD         { Adr. der Routine }
            move.w  #Supexec,-(sp)  { Funk.SUPEXEC ) 
            trap    #XBios          { XBIOS aufrufen }
            addq.l  #6,sp           { Stack korrigieren }
            rts                     { zurück zum Aufruf }

@Akt_PD:    movea.l #sysbase,a0     { Adr. sysbase }
            movea.l (a0),a0         { Adr. Systemheader }
            movea.l #def_jpd,a1     { Def.-PD nach a1 } 
            cmpi.w  #tos_102,2(a0)  { TOS < 1.02 }
            bcs     @pd_adr         { ja, Def.-PD gültig }
            movea.l 40(a0),a1       { Zeiger auf PD }

@pd_adr:    move.l  (a1),akt_PD     { PD-Adresse }
            rts                     { zurück zum Aufruf }

@return:                            { Function verlassen }

END; { FUNCTION DESKAKT }

{-------------------------------------------------}
function F_Select(VAR pfad.datei: Str128;
                  VAR titel: Str40): boolean;

(* ERZEUGT DIE NORMALE ODER ERWEITERTE
   FILESELCTBOX (ABHÄNGIG VON DER TOS-VERSION) *) 
   ASSEMBLER;

ASM

            (* REGISTER A2 SICHERN *) 
            move.l  a2,reg_A2       { Register sichern } 
            clr.w   @result         { vorbesetzen }

            (* PFAD UND NAMEN INS TOS-FORMAT *) 
            movea.l pfad,a0         { Adr. Pfadstring }
            lea     t_pfd,a1        { Adr. TOS-String )
            bsr     @TosForm        { umwandeln }
            movea.l datei,a0        { Adr. Dateistring )
            lea     t_dat,a1        { Adr. TOS-String }
            bsr     @TosForm        { umwandeln }

            (* KONTROLLE AUF TOS-VERSION >= 1.04 *) 
            bsr     @TOS_Nr         { Versionsnr. holen }
            cmpi.w  #$0104,tos_ver  { V = 1.04 ? }
            bcc     @F_Exinp        { ja, erweiterte Box }

            (* FILESELCTBOX OHNE TITELZEILE *)
            lea     control,a2      { Adr.control-Feld }
            move.w  #fs_inp,(a2)    { Feld besetzen }
            clr.w   2(a2)
            move.w  #2,4(a2)
            move.w  #2,6(a2)
            clr.w   8(a2)
            lea     addrin,a2       { Adr.addrin-Feld }
            lea     t_pfd,a1        { Adr. Pfad }
            move.l  a1,(a2)         { übergeben }
            lea     t_dat,a1        { Adr. Datei }
            move.l  a1,4(a2)        { übergeben }
            bsr     @AESTrap        { AES aufrufen }
            bra     @button         { Werte übergeben }

            (* TITLESTRING INS TOS-FORMAT *)
@F_Exinp:   movea.l titel,a0        { Adr. Titelstring } 
            lea     t_tit,a1        { Adr. TOS-String }
            bsr     @TosForm        { umwandeln }

            (* FILESELCTBOX MIT TITELZEILE *)
            lea     control,a2      { Adr.control-Feld )
            move.w  #fs_exin,(a2)   { Feld besetzen }
            clr.w   2(a2)
            move.w  #2,4(a2)
            move.w  #3,6(a2)
            clr.w   8(a2)
            lea     addrin,a2       { Adr.addrin-Feld }
            lea     t_pfd,a1        { Adr. Pfad }
            move.l  a1,(a2)         { übergeben }
            lea     t_dat,a1        { Adr. Datei }
            move.l  a1,4(a2)        { übergeben }
            lea     t_tit,a1        { Adr. Titel }
            move.l  a1,8(a2)        { übergeben }
            bsr     @AESTrap        { AES aufrufen }

            (* RÜCKGABEWERTE ÜBERGEBEN *)
@button:    lea     intout,a2       { Adr.intout-Feld }
            move.w  (a2),d0         { Rückmeldung }
            cmpi.w  #0,d0           { Fehler aufgetreten ? }
            beq     @return         { ja,Funktion beenden }
            move.w  2(a2),d0        { betätigter }
            move.b  d0,@result      { Knopf übergeben }
            cmpi.w  #0,d0           { Knopf abfragen }
            beq     @return         { war ABBRUCH }

(* PFAD- UND DATEINAMEN VERBINDEN *) 
            movea.l datei,a0        { Adr. Dateistring } 
            lea     t_pfd,a1        { Adr. TOS-String }
            bsr     @StrForm        { Stringwandlung }

@ExtEntf:   subq.w  #1,d0
            cmpi.b  #'\',-(a0)      { Backslash suchen } 
            bne     @ExtEntf        { n.gef.-> weiter }
            addq.l  #1,a0           { Ende Pfadname gef. }
            addq.w  #1,d0
            lea     t_dat,a1        { Adr. TOS-String }

@concat:    move.b  (a1)+,(a0)+     { Stringwandlung }
            addq.w  #1,d0
            cmpi.b  #0,(a1)         { Ende Namen suchen }
            bne     @concat         { wenn nicht, weiter }
            move.b  d0,(a2)         { neue Stringlänge }

            (* PFAD VON TOS-FORMAT -> STRING *) 
            movea.l pfad,a0         { Adr. Pfadstring }
            lea     t_pfd,a1        { Adr. Tos-String }
            bsr     @StrForm        { Stringwandeln }
            bra     @return         { Funktion beenden }

            (* UP TOS VERSIONSNUMMER ABFRAGEN *) 
@TOS_Nr:    pea     @Load_Nr        { Adr. der Routine }
            move.w  #Supexec,-(sp)  { Funk.SUPEXEC }
            trap    #XBios          { XBIOS aufrufen }
            addq.l  #6,sp           { Stack korrigieren }
            rts                     { zurück zum Aufruf }
@Load_Nr:   movea.l #sysbase,a0     { Adr. sysbase }
            movea.l (a0),a0         { Adr. Systemheader }
            move.w  2(a0),tos_ver   { Versionsnr. } 
            rts                     { zurück zum Aufruf }

            (* UP AES AUFRUFEN *)
@AESTrap:   lea     AES_pb,a0       { Adr. Parameterbl. }
            move.l  a0,d1           { Adr. übergeben }
            move.l  #AES,d0         { AES gewünscht }
            trap    #AES_VDI        { AES aufrufen }
            rts                     { zurück zum Aufruf }

            (* UP STRING INS TOS-FORMAT BRINGEN *) 
@TosForm:   clr.l   d0              { Register löschen }
            move.b  (a0)+,d0        { Stringlänge n. d0 ) 
            cmpi.w  #0,d0           { Stringlänge = 0 ? }
            beq     @Tos_Ret        { ja, UP beenden }
            subq.w  #1,d0           { um 1 Zeichen kürzen }
@looptos:   move.b  (a0)+,(a1)+     { String übertr. }
            dbra    d0,@looptos     { Schleife bis -1 }
            clr.b   (a1)            { Null-Byte anhängen )
@Tos_Ret:   rts                     { zurück zum Aufruf }

            (* UP TOS-FORMAT IN STRING WANDELN *) 
@StrForm:   clr.l   d0              { Register löschen }
            movea.l a0,a2           { Adr. des String }
            addq.l  #1,a0           { auf erstes Zeichen }
@loopstr:   move.b  (a1)+,(a0)+     { Zeichen übertr. } 
            addq.w  #1,d0           { Zeichenzähler +1 }
            cmpi.b  #0,(a1)         { Stringende ? }
            bne     @loopstr        { nein, dann weiter }
            move.b  d0,(a2)         { Länge eintragen }
            rts

@return:    movea.l reg _A2,a2      { Reg. a2 zurück }
                                    { Funktion verlassen }

END; { FUNCTION F_SELECT }

{-----------------------------------------------}

Procedure Screen(modus,x,y,w,h: integer;
                 VAR scr_adr: LongInt);

(* SICHERT DEN ANGEGEBENEN ODER GESAMTEN BEREICH DES BILDSCHIRMINHALTS *)
ASSEMBLER;

ASM
            (* XY-WERTE BILDSCHIRM ERMITTELN *) 
            move.l  a2,reg_A2       { Register sichern } 
            pea     akt_w           { Var.Adr. auf Stack }
            pea     akt_h
            pea     akt_pl
            jsr     XY_Size         { Procedure aufrufen }
            cmpi.w  #S_ALL,modus    { ganzen Screen? }
            beq     @weiter         { ja, dann weiter }
            cmpi.w  #R_ALL,modus    { ganzen Screen? }
            bne     @xy_x2y2        { nein, d. umrechnen }
@weiter:    clr.w   x               { Variablen löschen }
            clr.w   y
            move.w  akt_w,w         { Bildschirmgrösse }
            move.w  akt_h,h         { übergeben }
            subi.w  #1,w            { ein Pixel kürzen da } 
            subi.w  #1,h            { Koordinaten bei Null }
                                    { beginnen }

            (* W,H NACH X2,Y2 WANDELN *)
@xyx2y2:    move.w  x,d0            { x-Koordinate nach d0 } 
            add.w   w,d0            { Breite aufaddieren }
            move.w  d0,x2           { Ergebnis ist x2 }
            move.w  y,d0            { y-Koordinate nach d0 } 
            add.w   h,d0            { Höhe aufaddieren }
            move.w  d0,y2           { Ergebnis ist y2 }

            (* SICHERN ODER ZURÜCKSCHREIBEN? *) 
            cmpi.w  #R_ALL,modus    { Bildschirm }
            beq     @Restore        { ganz oder }
            cmpi.w  #R_XY,modus     { teilweise }
            beq     @Restore        { zurückschreiben? }

            (* SPEICHER FÜR SICHERUNG ERMITTELN *) 
@Mem_Bed:   clr.l   d0              { d0 ablöschen }
            move.w  h,d0            { Höhe nach d0 }
            mulu.w  w,d0            { Breite * Höhe }
            asr.l   #3,d0           { drei Bit nach rechts }
                                    { ergibt Byteanzahl } 
            add.w   #2,d0           { Grösse aufrunden }
            and.b   #$FE,d0         { auf Wortgrenze }
            move.w  akt_pl,d1       { Farbebenen an d1 }
@pl_mul:    cmpi.w  #1,d1           { eine Farbebene ? }
            beq     @mul_end        { ja, dann fertig }
            asl.l   #1,d0           { durch bitschieben }
            asr.w   #1,d1           { mit Anz. Farbebenen }
            bra     @pl_mul         { multiplizieren }
            (* BENÖTIGTEN SPEICHER RESERVIEREN *) 
@mul_end:   clr.l   -(sp)           { Raum für Rückmeldung }
            move.l  d0,-(sp)        { Speichergrösse }
            jsr     Res_Mem         { Function auf rufen }
            move.l  scr_adr,a2      { Var.Adr. nach a2 } 
            move.l  (sp)+,(a2)      { Adr. eintragen }
            cmp.l   #0,(a2)         { Speicher res.? }
            beq     @return         { nein, abbrechen }

            (* BILDSCHIRMINHALT SICHERN *) 
            lea     ptsin,a2        { Adr. ptsin-Feld }
            move.w  x,(a2)          { x-Koord. Quelle }
            move.w  y,2(a2)         { y-Koord. Quelle >
            move.w  x2,4(a2)        { x2-Koord. Quelle }
            move.w  y2,6(a2)        { y2_Koord. Quelle }
            clr.w   8(a2)           { x-Koord. Ziel }
            clr.w   10(a2)          { y-Koord. Ziel }
            move.w  w,12(a2)        { x2-Koord. Ziel }
            move.w  h,14(a2)        { y2_Koord. Ziel }
            pea     Mem_MFDB        { Adr. Ziel-MFDB }
            pea     Scr_MFDB        { Adr. Quell-MFDB }
            bsr     @VroCpyf        { Block kopieren }
            addq.l  #8,sp           { Stack korrigieren }
            bra     @return         { Procedure beenden }

            (* BILDSCHIRMINHALT ZURÜCKSCHREIBEN *) 
@Restore:   lea     ptsin,a2        { Adr. ptsin-Feld }
            clr.w   (a2)            { x-Koord. Quelle }
            clr.w   2(a2)           { y-Koord. Quelle }
            move.w  w,4(a2)         { x2-Koord. Quelle }
            move.w  h,6(a2)         { y2_Koord. Quelle }
            move.w  x,8(a2)         { x-Koord. Ziel }
            move.w  y,10(a2)        { y-Koord. Ziel }
            move.w  x2,12(a2)       { x2-Koord. Ziel }
            move.w  y2,14(a2)       { y2_Koord. Ziel }
            pea     Scr_MFDB        { Adr. Ziel-MFDB }
            pea     Mem_MFDB        { Adr. Quell-MFDB }
            bsr     @VroCpyf        { Block kopieren }
            addq.l  #8,sp           { Stack korrigieren }
            bra     @return         { Procedure beenden }

            (* AKTUELLES HANDLE ERMITTELN *) 
@VroCpyf:   lea     control,a2      { Adr.control-Feld }
            move.w  #GrfHand,(a2)   { Feld besetzen }
            clr.w   2(a2)
            move.w  #5,4(a2)
            clr.w   6(a2)
            clr.w   8(a2)
            lea     AES_pb,a0       { Adr.Parameterbl. } 
            move.l  a0,d1           { Adr. übergeben }
            move.l  #AES,d0         { AES gewünscht }
            trap    #AES_VDI        { AES aufrufen }
            lea     intout,a2       { Adr.intout-Feld }
            move.w (a2),handle      { handle sichern }

            (* MFDB ZWISCHENSPEICHER AUSFÜLLEN *) 
            lea     Mem_MFDB,a2     { Adr. von MFDB }
            move.l  scr_adr,a1      { Var.Adr nach a1 }
            move.l  (a1),(a2)       { Adr.res. Speicher }
            move.w  w,4(a2)         { Breite des Blockes }
            move.w  h,6(a2)         { Höhe des Blockes }
            move.w  w,d0            { Pixelbreite nach d0 } 
            divu.w  #16,d0          { Wortbreite bilden } 
            move.w  d0,8(a2)        { Breite in Worten }
            clr.w   10(a2)          { geräteabh. Format }
            move.w  akt_pl,12(a2)   { Anz. Planes }

            (* MFDB BILDSCHIRM AUSFÜLLEN *) 
            lea     Scr_MFDB,a2     { Adr. von MFDB }
            clr.l   (a2)            { Nullpointer, autom. }
                                    { MFDB Bildschirmspeicher }

            (* SPEICHERBEREICH KOPIEREN *)
            lea     control,a2      { Adr.control-Feld }
            move.w  #CopRast,(a2)   { Feld besetzen }
            move.w  #4,2(a2)
            clr.w   4(a2)
            move.w  #1,6(a2)
            clr.w   8(a2)
            move.w  handle,12(a2)   { handle-Nr. }
            move.l  4(sp),14(a2)    { Adr.Quell-MFDB } 
            move.l  8(sp),18(a2)    { Adr.Ziel-MFDB }
            lea     intin,a2        { Adr. intin-Feld }
            move.w  #3,(a2)         { Ziel- = Quellpixel }
            lea     VDI_pb,a0       { Adr. Parameterbl. }
            move.l  a0,d1           { Adr. übergeben }
            move.w  #VDI,d0         { VDI gewünscht }
            trap    #AES_VDI        { VDI aufrufen }
            rts                     { zurück zum Aufruf }

@return:    move.l  reg_A2,a2       { Reg. a2 zurück }
                                    { Procedure verlassen }

END; { PROCEDURE SCREEN }

{-------------------------------------------------}

END.

Jürgen Scherf
Aus: ST-Computer 03 / 1993, Seite 92

Links

Copyright-Bestimmungen: siehe Über diese Seite