Schnell wie der Wind: Neue Form_Dial Routine

In der Juni-Ausgabe der Zeitschrift wurde eine elegante File-Select-Box vorgestellt, die aber nur in eigenen Programmen verwendet werden kann. Mit dem hier vorliegenden Artikel wird gezeigt, daß es einfch ist, solche Änderungen bleibend (bis zum Reset) im Betriebssystem zu verankern, sodaß alle Programme davon profitieren. Als Beispiel wird hier die Form__Dial Routine des AES verwendet, die beschleunigt wird.

Zunächst werden wir die Arbeitsweise dieser Funktion erläutern. Soll in einem GEM-Programm eine Formular- oder Alert-Box dargestellt werden, wird vorher der dazu benötigte Bildschirmbereich durch den Aufruf der Form__Dial Routine reserviert. Äußerlich ist davon nichts zu merken, intern jedoch wird die Größe des Bereichs gespeichert.

Sobald der Programm-Nutzer seine Eingaben abgeschlossen hat und die Box nicht mehr benötigt wird, wird vom Programm erneut die Form_Dial-Routine aufgerufen, diesmal um den Bildschirmbereich wieder freizugeben. Jetzt kommt es jedoch zu einer unmittelbaren Reaktion des Betriebssystems: für alle von diesem Bildschirmbereich betroffenen Fenster (auch Accessories) wird eine Nachricht ausgesandt, damit die zerstörten Bereiche wieder neu gezeichnet werden.

Diese Wiederherstellung des Bildschirms geht bei einigen Programmen nur sehr langsam voran. Wenn beispielsweise beim Textverarbeitungsprogramm 1st-Word ein komplizierter Bildschirminhalt (mit verschiedenen Schriftarten, ...) erneuert werden muß, so kann dies einige Sekunden dauern. Diese unnötigen Wartezeiten werden mit dem im folgenden beschriebenen Programm vermieden.

Überblick

Die Funktionsweise ist dabei die: Das Programm wird zuerst resident in den Speicher geladen, wo es dann etwa 30 KByte wegnimmt. Wird die Form_Dial Routine zum Reservieren eines Bildschirmbereichs aufgerufen, wird die AES-Routine umgangen, statt-dessen wird der Bildschirmausschnitt in den, im Programm vorgesehenen Speicher geschrieben. Wenn der Bereich mit einem erneuten Aufruf der Funktion wieder freigegeben werden soll, wird er durch das Programm blitzartig wieder hergestellt. Es kommt zu keinen Meldungen des Betriebssystems an das Programm, daß ein Bildbereich zu erneuern ist.

Nun zu den Details: Das Programm besteht eigentlich aus zwei Teilen: dem Initialisierungsteil,-der nur einmal durchlaufen wird, und dem eigentlichen Programm, das bei jedem Aufruf einer AES- oder VDI Routine angesprungen wird.

Der Initialisierungsteil

Im Initialisierungsteil wird zuerst (wie in allen GEM-Programmen) der notwenige Speicherbedarf berechnet. Dabei wird auch ein beim Eingeben des Programms festzulegender Betrag von Bytes berücksichtigt, der als Pufferspeicher verwendet wird, um den Bildschirmausschnitt zwischenzuspeichern. Die Größe kann grundsätzlich frei gewählt werden - je mehr Bytes dazu verwendet werden dürfen, desto größere Bereiche können gesichert werden. Wenn zur Speicherung eines Bildbereiches nicht genügend Speicher vorhanden ist, wird die 'normale’ AES Routine verwendet. Für die meisten Anwendungen reichen etwa 20 KByte. Die obere Grenze stellen 32 KByte dar (das reicht für den gesamten Bildschirminhalt, größer wird ein Formular sicher nie sein).

Im Initialisierungsteil wird überprüft, ob das Programm bereits im Speicher verankert wurde - in diesem Fall wird die Initialisierung abgebrochen. Es wird durch diese Abfrage sichergestellt, daß das Programm nicht mehrfach im Speicher ist.

Wenn das Programm noch nicht resident ist, dann wird mit der BIOS Funktion Set_Exception der Ansprungvektor für AES- und VDI-Routinen auf den folgenden Programmteil GEMNEW gerichtet. Die alte Startadresse wird in der Program invariablen OLDGEM gespeichert und benötigt, wenn eine andere Routine als AES-Form_Dial aufgerufen wird.

Anschließend werden die Program invariablen STARTMEM und ENDMEM mit der Start- und Endadresse des verfügbaren Pufferspeichers belegt - dieser Speicher beginnt nach dem Ende des Programms, seine Länge wird durch PUFFLEN angegeben. Danach erfolgt mit der GEMDOS-Funktion Keep_Proccess der Rücksprung in das Programm, aus dem das Programm gestartet wurde (gewöhnlich GEM-Desktop). Mit dieser GEMDOS-Funktion wird für das Programm der in D4 angegebene Speicherbedarf reserviert, der Rest wird an die GEM-Speicherverwaltung zurückgegeben. Das Programm bleibt im Speicher und kann jederzeit aufgerufen werden.

Das Hauptprogramm

Der zweite Programmteil beginnt bei dem Label GEMNEW. Dort wird eine Identifikationsnummer gespeichert, mit der verhindert werden soll, daß das Programm mehrfach installiert werden kann (siehe oben).

Dieser Programmteil wird nun (wegen der Veränderung des Einsprungvektors) bei jedem Aufruf einer AES- öder VDI-Routine aufgerufen. Es muß zuerst festgestellt werden, ob eine AES-oder VDI-Routine aufgerufen wurde. Dazu wird das Register DO ausgewertet, das den Wert $C8 haben muß, wenn eine AES-Routine aufgerufen wird. Wenn dies nicht der Fall ist, dann wird (unter Verwendung der gespeicherten alten Einsprungadresse) die zuständige Routine des Betriebssystems aufgerufen. Gleiches geschieht, wenn zwar eine AES-Routine aufgerufen wurde, aber eine andere Funktiosnummer, als die von Form__Dial verwendet wird.

Handelt es sich bei dem Aufruf hingegen tatsächlich um die Form_Dial-Routine, muß das Intin-Feld für AES-Routinen ausgewertet werden. Dort wird im ersten Wort angegeben, auf welche Weise die Funktion verwendet werden soll: mit 0 zum Reservieren eines Bildschirmteiles, mit 3 zum Freigeben desselben. Möglich sind auch die Werte 1 und 2 - so wird die Funktion dazu verwendet, schrumpfende oder wachsende Rechtecke zu zeichnen, damit es aussieht, als kämen die Boxen,... aus der Unendlichkeit oder würden eben dorthin wieder verschwinden.

In den ersten beiden Fällen werden die, bei den Labeln GET und PUT beginnenden Programmteile aufgerufen, die den Bildschirm speichern oder wiederherstellen sollen. In den beiden anderen Fällen erfolgt der Rücksprung ins Programm, von dem die Form_Dial Routine aufgerufen wurde. Es werden also keine schrumpfenden,... Rechtecke gezeichnet. Wenn jemand darauf Wert legt, muß lediglich die Anweisung BRA RETURN durch BRA CONTAES ersetzt werden - es erfolgt in diesen Fällen der Aufruf der normalen Form_Dial Routine des Betriebssystems.

Der Programmteil GET

Statt einen Bildbereich zu reservieren, wird er in diesem Programmteil im RAM zwischengespeichert. Dazu muß zuerst die Maus abgeschaltet werden, damit diese (falls sie bewegt wird), keine Störungen verursacht. Das dazu verwendete Unterprogramm benutzt die Line-A Grafik-Befehle. Es wird darin auch die Adresse des Control-Feldes dieser Befehle ermittelt und in A3 gespeichert - die Adresse wird später benötigt, wenn die Maus wieder eingeschaltet werden soll.

Im Unterprogramm CALC werden dann aus den Daten im Intin-Block der AES-Funktion die Breite des zu rettenden Biidschirmbereiches in Worten (D1), die Zahl der Zeilen dieses Bereiches (D2), der Speicherbedarf zur Speicherung des Rechtecks (D3), die Startadresse des Bereiches am Bildschirm (A0), sowie eine Kenn-Nummer (D0) errechnet.

Der Grund, weshalb die Breite des Rechteckes in Worten angegeben wird, ist leicht zu verstehen: es wird nicht genau das angegebene Rechteck gespeichert, sondern aus Gründen der Einfachheit und Geschwindigkeit ein (meistens) etwas größerer Bereich mit Wort-Grenzen. Andernfalls hätten einzelne Bits am Rand separat berücksichtigt werden müssen.

Der Speicherbedarf (in Byte) ergibt sich grundsätzlich aus der Multiplikation Breite in Worten mal Zeilenanzahl mal zwei. Es muß aber berücksichtigt werden, daß sowohl die Zeilenanzahl als auch die Breite um eins reduzierte Werte sind, und daß außer der eigentlichen Bildinformation auch noch die eine Adresse, sowie die Kenn-Nummer gespeichert werden muß.

Die Kenn-Nummer ist notwendig, damit der PUT-Programmteil feststellen kann, ob der freizugebende Bildschirmbereich mit dem gespeicherten Bereich identisch ist (dies ist dann nicht der Fall, wenn nicht ausreichend Speicher vorhanden war, um das Rechteck zu speichern).

Nachdem alle diese Werte ermittelt wurden, sollte man feststellen, ob genug Speicher vorhanden ist, um den Bereich zu speichern. Wenn dies der Fall ist, dann wird in einer einfachen Schleife der Bildschirminhalt wortweise in den Pufferspeicher übertragen. Außerdem wird noch die Kenn-Nummer und die Startadresse des gerade gespeicherten Blocks im Puffer abgelegt - beide Informationen werden vom Programmteil PUT benötigt. Auch die Startadresse des Pufferspeichers muß erhöht werden, damit es möglich ist, auch mehrere (kleinere) Boxen zugleich zwischenzuspeichern (dies ist möglich, kommt in der Praxis aber ziemlich selten vor).

Anschließend verzweigt das Programm zum Label MAUSEIN, wo die Maus wieder sichtbar gemacht wird, und die Rückkehr in das Programm erfolgt, von dem die Form_Dial Routine aufgerufen wurde. Dabei ist zu beachten, daß der Rücksprung über RTE und nicht RTS erfolgt, weil Betriebssystem-Routinen beim ST über Traps aufgerufen werden und sich der Prozessor dann im ’Exception-Modus’ befindet.

Wenn der Speicher nicht ausreicht, erfolgt über den Programmteil ERROR der Aufruf der Form_Dial-Routine des Betriebssystems, wozu die Maus wieder sichtbar gemacht werden muß.

Der Programmteil PUT

Dieser Programmteil wird aufgerufen, um einen Bildschirmbereich wieder frei zu geben. Wie bei GET wird zuerst die Maus unsichtbar gemacht und das Unterprogramm CALC aufgerufen. Sodann wird festgestellt, ob die errechnete Kenn-Nummer mit dem letzten Wert im Pufferspeicher identisch ist - wenn dies nicht der Fall ist, wird (über ERROR) die Form_Dial Routine des Betriebssystems aufgerufen.

Andernfalls wird aus dem Pufferspeicher die Startadresse des Blocks gelesen, in einer ähnlichen Schleife wie bei GET wird der Bildschirminhalt wieder erneuert. Die Startadresse des Pufferspeichers wird verkleinert - der Speicher kann somit wieder verwendet werden. Anschließend erfolgt, wie bei GET der Rücksprung in das Programm, welches die Form_Dial Routine verwendet hat.

Anwendung des Programmes

Das Programm läuft in dieser Form nur in der höchsten Auflösungsstufe des ST. Es kann jedoch an die anderen Modi angepaßt werden, wozu im Unterprogramm CALC die Berechnung der Breite des Rechtecks in Worten geändert werden muß (in Mid-Res: die beiden Rotierbefehle ASR mit #3 statt #4, in Low-Res mit #2).

Auch die Berechnung der Bildschirm-Startadresse muß ein wenig verändert werden: die Multiplikation muß sowohl in Low-Res, als auch in Mid-Res mit =#=160 statt #80 erfolgen.

Abgesehen davon läuft das Programm ohne Einschränkung mit allen anderen Programmen, die die Form_Dial Routine vorschriftsmäßig verwenden. Es gibt allerdings einige Programme, bei denen eine ähnliche Pufferung des Bildinhaltes schon von sich aus durchgeführt wird (z. B. GfA-BASIC bei A1ert-Boxen, Tempus-Editor bei kleinen Boxen). Bei diesen Programmen kann daher keine Beschleunigung erreicht werden.

Accessories, die nur aus einem Formular bestehen, arbeiten gewöhnlich problemlos mit dem Programm zusammen. Keinen Effekt hat das Programm hingegen auf Gem-Desktop. Dort wird die Form_Dial Routine anscheinend nicht verwendet, oder der Einsprung erfolgt nicht über den GEM-Vektor. Auch die GEM-File-Select Box, die ebenfalls einen Bildschirmausschnitt reserviert und später wieder frei gibt, nützt die Form__Dial Routine entweder gar nicht, oder umgeht zumindest den GEM-Vektor.

Grundsätzlich kann nach dem hier demonstrierten Muster jede AES- oder VDI-Funktion durch eigene, schnellere oder vielseitigere Routinen ersetzt werden. Neben der Initialisierung, bei der kaum Änderungen notwendig sind, muß darauf geachtet werden, daß die alte Startadresse der Betriebssystem-Routinen gespeichert wird, daß dorthin mit JMP gesprungen wird, wenn eine andere Routine, als die zu ersetzende aufgerufen wurde, und daß ansonsten der Rücksprung ins Programm, aus dem der Aufruf kommt, mit RTE erfolgt. Es können auch mehrere solche Programme zugleich im Speicher sein. Nur sollte darauf geachtet werden, daß diese möglichst rasch wieder verlassen werden, wenn eine andere Funktion aufgerufen wird - sonst wird das gesamte Betriebssystem noch langsamer.

; Michael Kofler, August 87

; das Programm umgeht AES-Routine Form_Dial:
; der zu reservierenden Bildschirmbereich wird im RAM zwischenspeichert 
; und diesen Bereich bei der Freigabe blitzartig erneuert; dies bringt 
; bei GEM-Programmen mit langsamen Bildschirmaufbau eine klare 
; Beschleunigung des Programmablaufs, wenn Formulare, Alert-Boxen,..
; verwendet werden

		SECTION TEXT

INITGRAF	EQU	$A000	;Codes der Line-A Grafik Routinen
SHOWMAUS	EQU	$A009
HIDEMAUS	EQU	$A0OA
GEMDOS		EQU	1		;Trap-Nummer für GEMDOS-Funktionen
KEEP		EQU	$31		;Fn.nummer für KEEP PROCESS
BIOS		EQU	13		;Trap-Nummer für BIOS-Funktionen
SETEXC		EQU	5		;Fn.nummer für SETEXEC
GEMVEC		EQU	34		;Vektor des GEM-Traps
SCRADR		EQU	$44E	;hier steht die Bildschirmstartadresse
PUFFLEN		EQU	25000	;Größe des Pufferspeichers

; INITIALISIERUNGSTEIL
		MOVE.L	4(A7),A0	;Programmbeginn
		MOVE.L	#$100,D4	; +	Base Page
		ADD.L	12(A0),D4	;+	Prg.Länge
		ADD.L	20(A0),D4	; + Datensegement-Länge
		ADD.L	28(A0),D4	;+ Blocksegment-Länge
		ADD.L	#PUFFLEN,D4	;+ Raum für Pufferspeicher und Stack
							;D4 gibt benötigten Speicher an 
		MOVE.L	#-1,-(A7)	;GEM Exception-Vektor bestimmen
		MOVE.W	#GEMVEC,-(A7)
		MOVE.W	#SETEXC,-(A7)
		TRAP	#BIOS
		ADDQ.L	#8,A7
		MOVE.L	D0,A0		;Test, ob Programm bereits resident
		CMP.L	#$1A2B3C4D,2(A0)	;2. und 3. Wort der Execptionroutine
							; mit S1A2B3C4D vergleichen 
		BEQ.S	ABBRUCH		;wenn identisch ->> Programm abbrechen
		MOVE.L	#GEMNEW,-(A7)	;GEM Exception-Vektor ändern:
		MOVE.W	#GEMVEC,-(A7)	; zeigt jetzt auf den unten folgenden
		MOVE.W	#SETEXC,-(A7)	; Programmteil
		TRAP	#BIOS
		ADDQ.L	#8,A7
		MOVE.L	D0,OLDGEM	;alte GEM-Startadresse retten

		LEA		PUFFER,A0	,Adressen des Pufferspeicher bestimmen
		MOVE.L	A0,STARTMEM	;und in den vorgesehenen Programm-
		ADDA.L	#PUFFLEN,A0	; variablen speichern
		MOVE.L	A0,ENDMEM
		CLR.W	-(A7)		;Initialisierung beenden (Programm
		MOVE.L	D4,-(A7)	;bleibt aber im Speicher resident)
		MOVE.W	#KEEP,-(A7)
		TRAP	#GEMDOS
ABBRUCH	CLR.L	(A7)		;Programm abbrechen (Programm wird au:
		CLR.W	-(A7)		;Speicher entfernt)
		TRAP	#GEMDOS

; Start der Routine, die künftig immer dann durchlaufen wird, wenn eine 
; AES oder VDI Routine aufgerufen wird 
GEMNEW	BRA.S	WEITER
		DC.L	$1A2B3C4D	;Kennung der Routine
WEITER	CMPI.B	#$C8,D0		;wurde AES-Funktion aufgerufen ?
		BNE.S	JUMPAES		;nein, weiter im Betriebssystem
		MOVEM.L	D0-D4/A0-A3,-(A7) ;Register retten
		MOVEA.L	D1,A0		;A0 zeigt auf AES-Parameterblock
		MOVEA.L	(A0),A1		;A1 zeigt auf CONTRL-Feld
		CMPI.W	#51, (A1)	;-wurde Form_Dial aufgerufen ?
		BNE.S	CONTAES		;nein, weiter in AES
		MOVEA.L 8(A0),A2	;A2 zeigt auf INTIN-Feld, dort wird
							;Funktion der Form_Dial Routine bestimmt 
		CMPI.W	#0,(A2)		;bei 0 GET-Programmteil aufrufen
		BEQ.S	GET
		CMPI.W	#3,(A2)		;bei 3 PUT-Programmteil aufrufen
		BEQ.S	PUT
		BRA	RETURN			;ansonsten Rücksprung ins Hauptprogramm
							; (d.h. schrumpfende Rahmen,., werden 
							; nicht gezeichnet)
ERROR	MOVE.W	#0,2(A3)	;bei Fehler in GET/PUT Routinen:
		MOVE.W	#1,6(A3)	; Maus wieder sichtbar machen,
		DC.W	SHOWMAUS	; weiter in Betriebssystemroutinen
CONTAES	MOVEM.L	(A7)+,D0-D4/A0-A3 ;Register zurück
JUMPAES	MOVE.L	OLDGEM,A0	; .. Startadresse der GEM-Routinen
		JMP		(A0)		; dorthin springen

MAUSAUS	MOVE.L	A2,-(A7)	;A2 (zeigt auf Intin) retten
		DC.W	INITGRAF	;Grafik initialisieren
		MOVE.L	4(A0),A3	;A3 zeigt auf CONTRL-Array (für später
		DC.W	HIDEMAUS	;Maus ausschalten
		MOVE.L	(A7)+,A2	;A2 wieder zurück
		RTS

GET		;zu reservierenden Bild-Bereich lesen und im Puffer speichern
		BSR		MAUSAUS		;Maus abschalten
		BSR.S	CALC		;Adressen, Speicherbedarf,., errechnen
		ADD.L	STARTMEM,D0	; Feststellung, ob genug Speicher für
		CMP.L	ENDMEM,D0	; den Bildausschnitt vorhanden ist
		BGT		ERROR		;zuwenig Speicher - daher normale AES-
							; Routine verwenden 
		MOVE.L	STARTMEM,A1	;A1 zeigt auf Beginn des freien Speichers
		MOVE.L	A0,A2		;Bildschirmbereich in Pufferspeicher
LOOP1	MOVE.W	D1,D4		; schreiben; Register aus CALC bekannt
LOOP2	MOVE.W	(A0)+,(A1)+	;innere Schleife für eine	Bildschirm-
		DBF		D4,LOOP2	; Zeile, deren Länge in D4 bestimmt isc
		ADD.L	#80,A2
		MOVE.L	A2,A0
		DBF		D2,LOOP1	;äußere Schleife für Zahl der Zeilen(D2
		MOVE.L	STARTMEM,(A1)+	;Startadresse des Blocks speichern
		MOVE.L	D3,(A1)+	;-zuletzt Kennnummer speichern
		MOVE.L	D0,STARTMEM	;neuer Start
		BRA.S	MAUSEIN		;Maus wieder sichtbar, Rücksprung

PUT		;Bild-Bereich aus Puffer auf den Bildschirm schreiben
		BSR		MAUSAUS		;Maus abschalten
		BSR.S	CALC		;Adressen,.,	ausrechnen
		MOVE.L	STARTMEM,A1
		CMP.L	-4(A1),D3	;Kennnummer vergleichen
		BNE		ERROR		;nicht passend, daher weiter im AES
		MOVE.L	-8(A1),A1	;in A1: Startadresse des Blocks
		MOVE.L	A0,A2		;Bildschirmausschnitt vom Puffer-
LOOP3	MOVE.W	D1,D3		;Speicher auf Bildschirm übertragen
LOOP4	MOVE.W	(A1)+,(A0)+	;innere Schleife für eine Bildschirm-
		DBF		D3,LOOP4	; Zeile
		ADD.L	#80,A2
		MOVE.L A2,A0
		DBF		D2,LOOP3	;äußere Schleife für Zahl der Zeilen
		SUB.L	D0,STARTMEM	;Start des freien Speicher reduzieren
MAUSEIN	MOVE.W	#0,2(A3)	;Maus wieder sichtbar machen
		MOVE.W	#1,6(A3)	;A3 zeigt auf Grafik-Parameter
		DC.W	SHOWMAUS
RETURN	MOVEM.L	(A7)+,D0-D4/A0-A3 ;Register zurück, danach Rücksprung ins
		RTE					;Programm, aus dem Aufruf erfolgte

CALC	;Koordinaten auslesen, Wortgrenzen, Speicherbedarf
		;gegeben:	A2:	zeigt auf INTIN Feld
		;gesucht:	D0:	Speicherbedarf in Byte	D3: Kenn-Nummer
		; D1:	Breite in Worten	D2:	Höhe in Zeilen
		; A0:	Startadresse des Rechtecks am	Bildschirm
		LEA		10(A2),A0	;A0 zeigt auf Beginn der Parameterliste
		CLR.L	D0			; im Intin-Feld
		MOVE.W	(A0),D0		;D0: X1-Koordinate in Punkten
		MOVE.W 	D0,D1
		ADD.W	4(A0),D1	;D1: X2-Koordinate (Xl+Breite)in Punkten
		ASR.W	#4,D0		;D0: X1 in Worten (Division durch 16)
		ASR.W	#4,D1		;D1: X2 in Worten
		SUB.W	D0,D1		; —> D1: Breite minus 1 in Worten
		MOVE.W	6(A0),D2	; —> D2: Y-Breite
		MOVE.L	SCRADR,D3	;D3:	Bildschirmstartadresse
		ADD.W	D0,D0		;D0:	X1 in Byte
		ADD.L	D0,D3		;zur Startadr. addieren
		MOVE.W	2(A0),D0	;D0: Y1 in Zeilen
		MULU	#80,D0		;mal	80 .. in Byte
		ADD.L	D0,D3		;zur Startadr. addieren
		MOVE.L	D3,-(A7)	;am Stack Zwischenspeichern
		MOVE.W	D1,D0		;D0: Speicherbedarf in Wörtern
		ADDQ.W	#1,D0		;aus D1: Breite in Worten
		MULU	6(A0),D0	;mit Höhe (Zeilenanzahl) multiplizieren
		ADD.W	D1,D0		;nochmal Breite in Worten addieren
		ADD.W	#5,D0		;plus 5 Worte für Kenn-Nummer,...
		ADD.W	D0,D0		;mal 2 --> D0:Speicherbedarf in Byte
		MOVE.L	(A0),D3		;Kenn-Nummer bestimmen
		EOR.W	D0,D3		; --> D3: Kenn-Nummer
		MOVE.L (A7)+,A0		;Startadresse vom Stack in --> A0
		RTS

;Programmvariablen

STARTMEM	DS.L	1	;Beginn des Pufferspeichers
ENDMEM		DS.L	1	;Ende des Pufferspeichers
OLDGEM		DS.L	1	;Zeiger auf gewöhnliche GEM-Routinen
PUFFER		DS.W	1	;hier beginnt der Pufferspeicher,	dessen
						;durch PUFFLEN festgelegt ist

Listing 1: Die neue Form_Dialroutine zeigt dem Blitter wo es langgeht.


Michael Kofler
Aus: ST-Computer 11 / 1987, Seite 90

Links

Copyright-Bestimmungen: siehe Über diese Seite