@C9Module-Programmierung Kurs Teil 4

@C7
Im letzten Teil haben wir eine Mglichkeit der Interaktion kennengelernt, die
ausschlielich Modulen bereitgestellt wird: die Service Calls. Diesesmal werden
wir uns mit Vektoren beschftigen. Genauer: mit Softwarevektoren.

Ein Softwarevektoren sind, einfach gesagt, eine Liste von Eintrgen die RISC OS
benutzt, um zu entscheiden, wem es in bestimmten Situationen zur Klrung der-
selben die Kontrolle gibt. Die meisten Vektoren sind da, um SWI's umzuleiten.
Wenn man einen solchen Vektor 'claimt' kann man auf die Ausfhrung bestimmter
SWI Einflu nehmen. Einen solchen SWI nennt man auch 'vektorisierten' SWI. Es
gibt natrlich auch Vektoren, die nicht zu SWI's gehren, wie z.B. den Ticker
Vektor. Alle Routinen, die sich auf diesen Vektor installiert haben, werden 100
Mal in der Sekunde aufgerufen. Das ist besonders ntzlich, wenn man bestimmte
Ablufe synchronisieren will. Fr eine volle Liste der Vektoren konsultiere man
bitte die PRM's oder den erweiterten StrongHelp.

Im folgenden werden wir das Module, das wir bisher benutzt haben, stark redu-
zieren. Die Kommandoeintrge und der ServiceCall-Handler werden entfernt, damit
wir uns auf den Init- und Exitcode konzentrieren knnen. Wir haben jetzt wieder
ein fast leeres Module:
@C3
  10 REM Beispielprogramm - Coder`s Revenge Modulprogrammierung Kurs Teil 4
  20 DIM Code% 4000
  30 FOR Durchlauf% = 4 TO 6 STEP 2
  40 P%=0
  50 O%=Code%
  60 [ OPT Durchlauf%
  70
  80 ; - Modulheader
  90 EQUD 0
 100 EQUD InitOffset                 ; InitCode
 110 EQUD ExitOffset                 ; ExitCode
 120 EQUD 0
 130 EQUD Titel                      ; Modultitelstring
 140 EQUD HilfeText                  ; Modulhilfestring
 150 EQUD 0
 160
 170 .Titel     EQUS "Beispielprogramm":EQUB 0
 180 .HilfeText EQUS "Beispielprogramm"+CHR$9+"4.00 (01 Feb 1996)":EQUB 0
 190 ALIGN
 200
 210 .InitOffset
 220 STMFD   R13!,{R14}
 230 LDMFD   R13!,{PC}
 240
 250 .ExitOffset
 260 STMFD   R13!,{R14}
 270 LDMFD   R13!,{PC}
 280
 290 ]
 300 NEXT Durchlauf%
 310 SYS "OS_File",10,"RAM::0.$.Beispiel",&FFA,0,Code%,O%
@C7
Im Moduleheader haben wir diesmal die Eintrge fr SWI's vllig weggelassen. Das
ist aber auch legal - wenn in ihnen keine sinvollen Werte stehen, geht RISC OS
davon aus, da von diesem Module keine SWI's bereitgestellt werden.

Zur Erklrung der Funktion von Softwarevektoren suchen wir uns ersteinmal ein
paar geeignete heraus: Nehmen wir den Event-Vektor (vom SWI "OS_GenerateEvent")
und den CLI-Vektor (vom SWI "OS_CLI").

Um einen Vektor zu claimen, braucht man den SWI "OS_Claim" oder "OS_AddToVector"
Der Unterschied zwischen den beiden ist, da OS_Claim beim Claimen berprft, ob
der Vektor bereits von einer Routine mit der selben Adresse und dem gleichen
Inhalt von R2 (siehe unten) geclaimt wurde. Ist dies der Fall, wird die alte
Routine entfernt und die neue stattdessen installiert. Der SWI "OS_AddToVector"
tut dies nicht - hier wren dann beide Routinen aktiv. Beide SWIs haben die
gleichen Aufrufskonditionen:
@C2
  R0 = Vektornummer
  R1 = Adresse der Handler Routine
  R2 = Wert, der beim Aufruf der Routine in R12 bergeben wird
@C7
Bei Nach SWI-Aufruf sind alle Register unverndert. Im Exitcode des Moduls mu
dann jeder geclaimte Vektor auch wieder freigegeben werden, da ja das Module,
und damit auch unsere Vektorroutine, danach aus der RMA entfernt wird. Dazu
benutzt man den SWI "OS_Release". Dieser hat dieselben Eingaberegister wie der
SWI "OS_Claim". Will man eine Routine entfernen, mu man auch genau dieselben
Werte angeben, die man beim SWI "OS_Claim" benutzt hat, da sonst die Routine
nicht identifiziert werden kann. (Auch R2 mu den selben Wert haben, wie beim
OS_Claim.)

Alle auf einem Vektor installierten Routinen werden in umgekehrter Installa-
tionsreihenfolge aufgerufen, d.h. die Routine, die den Vektor als letztes ge-
claimt hat, wird als erstes aufgerufen.

Wenn ein Vektor aufgerufen wird (durch einen vektorisierten SWI oder durch einen
anderen Umstand (TickerV ...)), wird zunchst die Rcksprungadresse zum Aufrufer
im Systemstack 'ganz oben' gespeichert. Eine installierte Vektor-Routine hat nun
3 Mglichkeiten, den Vektor zu bearbeiten:

1. Die installierte Vektorroutine wird ganz normal abgearbeitet, dann gibt man
   die Kontrolle an die nchste Routine des jeweiligen Vektors weiter. Dafr
   schiebt man ganz einfach die in R14 bergebene Rcksprungadresse in den PC:@C2
     MOVS PC,R14@C7
   Vorher knnte man auch die Eingaberegister verndern, um z.B. den Effekt
   eines SWIs zu beeinflussen.

2. Man unterbricht die Abarbeitung des Vektors. Vekorroutinen, die sich vor der
   eigenen auf diesem Vektor installiert haben, erhalten also nicht mehr die
   Kontrolle. Das kann man z.B. dazu nutzen, um vektorisierte SWIs umzuleiten.
   Dann mu man aber auch alles erfllen, was der Aufrufer vom Original-SWI er-
   wartet. Also auch die selben Ein- und Ausgabebedingungen bereitstellen. Die
   eigentliche Routine kann abweichen, z.B. schneller sein, mu aber essentielle
   Aktionen, die der Original-SWI unternimmt, ebenfalls ttigen. Sonst knnte es
   zu Abstrzen kommt. (Bei einigen SWI ist es aber gar nicht so schlimm, wenn
   die Funktionen der Originalroutine nicht genau emuliert werden, z.B. ist es
   beim 'Event-Vektor' blich, einen Tastendruck nicht weiterzuleiten, wenn man
   mchte, da kein anderer darauf reagieren kann.) Ist unsere Routine abgear-
   beitet, wird die Kontrolle also nicht weitergegeben, sondern die Rcksprung-
   adresse vom Stack direkt in den PC geladen:@C2
     LDMFD R13!,{PC}^@C7
   Es wird also nicht wie bei 1. die in R14 bergebene Rcksprungadresse ver-
   wendet.

3. Man lt erst alle anderen Vektor-Routinen arbeiten und fngt die Kontrolle
   dann ganz am Ende ab. Dann knnte man die Ausgabekonditionen verndern oder
   z.B. beim 'OS_File-Vektor' ein in den Speicher geladenes File nachtrglich
   nochmal bearbeiten. Schlielich gibt man die Kontrolle an den Aufrufer
   zurck. Dieses Abfangen bewerkstelligt man, in dem man die Adresse der ei-
   gegen Abfang-Routine ganz oben auf den Stack speichert:@C2
     ADR    R0,Abfangroutine
     STMFD  R13!,{R0}@C7
   Allerdings mu diese Adresse direkt nach der Originalrckgabeadresse (siehe
   2.) stehen. Man mu also mit dem Abspeichern von anderen Registern auf den
   Stack vorsichtig sein. Wenn dann nmlich alle auf einem Vektor installierten
   Routinen abgearbeitet wurden, ldt RISC OS die Rcksprungadresse zum Aufrufer
   des Vektors vom Stack. Da aber an dieser Stelle jetzt die Adresse unserer
   Abfangroutine steht, erhalten wir die Kontrolle. Ein kleines Beispiel:@C2
     .ClaimRoutine
     STR    R0,merke_R0         ; Inhalt von R0 zwischenspeichern
     ADR    R0,Abfangroutine    ; Adresse der Abfangroutine...
     STMFD  R13!,{R0}           ; ...auf den Stack speichern
     LDR    R0,merke_R0         ; Inhalt von R0 wiederherstellen
     MOVS   PC,R14              ; und Kontrolle an andere Routinen weitergeben
     .merke_R0 EQUD 0@C7
   Die Abfangroutine holt dann, nachdem sie den Vektor bearbeitet hat, die Ori-
   ginalrcksprungadresse zum Vektor-Aufrufer wieder vom Stack:@C2
     .Abfangroutine
     LDMFD    R13!,{PC}^
@C7
Vektor-Routinen sollten ein paar Regeln beachten:

 1. Bei Beendigung der Vektor-Routine mssen die Prozessorflags unverndert
    weitergegeben werden. Das macht man mit einem
      MOV@CFS@C7 PC,R14 bzw. LDMFD R13!,{PC}@CF^@C7
    Das S bzw. ^ ist wichtig, weil es dafr sorgt, da alle Prozessorflags mit
    in den PC geladen werden.
 2. Grundstzlich drfen nur X-SWI's aufgerufen werden. Wenn ein Fehler auf-
    tritt, mu das V-Flag gesetzt werden aber auf keinen Fall die Kontrolle
    weiter- sondern mit auf den Fehlerblock gesetztem R0 an den Aufrufer zurck-
    geben. Das funktioniert aber nicht mit allen Vektoren, einige (z.B. der IRQ
    Vektor) haben niemanden, dem sie den Fehler mitteilen knnen. Also gibt`s da
    nur eins: Alles abbrechen, Fehler ignorieren, alle Register und Prozessor-
    flags wiederherstellen und mit  MOVS PC,R14  weitergeben.
 3. Der Prozessormode, in dem die Routine aufgerufen wird, hngt vom Vektor ab:
    - eine IRQ-Vektor-Routine (Unbekannter IRQ) wird immer im IRQ Modus ange-
      sprungen
    - alle Vektorroutinen zwischen &10 und &16 (EventV, InsV, RemV und CnpV)
      sowie der TickerV werden im IRQ Modus aufgerufen, es sei denn, sie wurden
      durch den SWI 'OS_CallAVector' ausgelst. Dann ist der Prozessor im SVC-
      Modus.
    - Alle anderen Vektoren sind grundstzlich im SVC Modus (weil es ja in Wirk-
      lichkeit auch nur SWI's sind).
 4. Man sollte nur SWI's benutzen, die in den PRM's als 'Re-entrant' gekenn-
    zeichnet sind. Zu erklren, warum das so ist, wrde hier zu weit fhren, es
    ist nur kurz zu sagen, da ein sozusagen rekursiv aufgerufener SWI seinen
    Arbeitsspeicher versaut und dann bei der Ausfhrung des ersten SWI's ab-
    strzt.

Nun genug der grauen Theorie. Wir hatten vor, die beiden oben genannten Vektoren
(TickerV, OS_CLI) zu claimen, also:

@C3.InitOffset
STMFD   R13!,{R0-R2,R14}
MOV     R0,#&10               ; EventVektor Nummer
ADR     R1,events             ; Routine adressieren
MOV     R2,#0                 ; R12 ist unwichtig
SWI     "XOS_Claim"           ; und los
MOV     R0,#5                 ; CLI Vektor Nummer
ADR     R1,cli                ; Routine adressieren
MOV     R2,#0                 ; R12 beim Aufruf der Routine
SWI     "XOS_Claim"           ; und los
LDMFD   R13,{R0-R2,PC}^

.ExitOffset
STMFD   R13!,{R0-R2,R14}
MOV     R0,#&10               ; EventVektor Nummer
ADR     R1,events             ; Routine adressieren
MOV     R2,#0                 ; R12 war unwichtig
SWI     "XOS_Release"         ; und loslassen
MOV     R0,#5                 ; CLI Vektor Nummer
ADR     R1,cli                ; Routine adressieren
MOV     R2,#0                 ; R12 beim Aufruf der Routine
SWI     "XOS_Release"         ; und loslassen
LDMFD   R13,{R0-R2,PC}^@C7

Nun wre es noch gut, wenn wir wten, was wir berhaupt beabsichtigen :->
Wir wollen ein Module schreiben, das alle CLI-Befehle mitschreibt. Jeder CLI-
Befehl wird ber den SWI "OS_CLI" aufgerufen. Dieser SWI ist ein vektorisierter
SWI, das heit also, da die CLI-Befehle ber den OS_CLI-Vektor abgearbeitet
werden.

Der Befehlsstring wird, siehe SWI "OS_CLI", in R0 bergeben. Wir mssen ihn also
nur noch in ein File speichern. Das geht am besten, indem man im Initcode ein
File mittels des SWI "OS_Find" ffnet, bei jedem CLI-Kommando mit SWI "OS_GBPB"
den Befehlsstring in das File schiebt und im Exitcode das File mit dem SWI
"OS_Find",0 wieder schliet. Da man nun whrend des Betriebes das File nicht
laden kann, zum Beispiel in einen Editor, bieten wir noch die Mglichkeit an,
mittels eines Tastendrucks das Mitschreiben zu unterbrechen und das File bis zum
nchsten Tastendruck zweitweilig zu schlieen. Und dazu brauchen wir den Event-
Vektor, speziell den Event mit der Nummer 11 (eine Taste wurde gedrckt bzw.
losgelassen).

Allerdings mu man solche ber den SWI 'OS_GenerateEvent' verschickten Events
erst 'enablen', = einschalten, das heit, man teilt dem Betriebssystem mit, da
eine Routine an einem bestimmten Event interresiert ist. Dieses 'Enablen',
sprich Einschalten wird gezhlt, das heit bei jedem Einschalten wird eine Sys-
temvariable erhht, dieselbe wird beim 'Disablen' (Ausschalten) verringert, und
wenn diese 0 ist, wird der Event erst gar nicht zum Handler geschickt. Man soll-
te sich nicht darauf verlassen, da der entsprechende Event schon von einem
anderen Module eingeschaltet wurde, sondern jeden Event, den man bearbeiten will
mit dem OS_Byte 14 enablen. Genauso sollte man im Sinne eines schnellen Betrie-
bes des RISC OS die Events auch wieder disablen, wenn man sie nicht mehr braucht
(OS_Byte 13), da sonst unntigerweise der Standardeventhandler aufgerufen wird,
der die Kontrolle eh' nur zurckgibt. Der gesamte 'OS_GenerateEvent' SWI ist
sowieso (fast) nur dazu gedacht, geclaimt zu werden, der eigentlich Code tut
(fast) gar nichts. Zurck zur Praxis:

@C3.InitOffset
STMFD   R13!,{R0-R6,R14}
BL      FileOeffnen                      ; das File vorbereiten
....
SWI     "XOS_Claim"
MOV     R0,#14
MOV     R1,#11                           ; Key Up/Down - Event
SWI     "XOS_Byte"                       ; Einschalten
LDMFD   R13!,{R0-R6,PC}

.ExitOffset
STMFD   R13!,{R0-R2,R14}
BL      FileSchliessen                   ; das File zumachen
....
SWI     "XOS_Release"
MOV     R0,#13
MOV     R1,#11                           ; Key Up/Down - Event
SWI     "XOS_Byte"                       ; Ausschalten
LDMFD   R13!,{R0-R2,PC}

Wie man sieht, wurde auer den 'OS_Byte's auch noch BL's zu FileOperationen ein-
gebaut. Im ersten (.FileOeffnen) wird geschaut, ob ein File, welches man im
String  .FileName  spezifiziert, bereits existiert, dies tut man mit OS_File 5.
Dieser gibt in R0 an, ob er das File gefunden hat (=1) oder nicht. In diesem
Fall wird ein leeres Textfile kreirt, dazu ist der OS_File 11 da. Nheres ber
ihn in den bekannten Quellen. Danach wird das File geffnet (OS_Find), und im
Sinne der Erweiterbarkeit wird der FilePointer, also die Adresse im File, an die
die nchsten tranferierten Bytes geschrieben werden, auf das Ende des Files ge-
setzt. Dazu liest man mit dem SWI OS_Args 2 die Lnge des Files aus und setzt
den FilePointer mit OS_Args 1 dorthin. Sollte irgendwo etwas schiefgehen, wird
die Fehlerroutine angesprungen, welche dafr sorgt, da das Module nocht initi-
alisiert wird, gibt man mit dem V-Flag zurck, wird das Module als nicht exis-
tent betrachtet.


@C3.FileHandle EQUD 0
.FileName   EQUS "ADFS::x.$.DestFile"

.FileOeffnen
STMFD   R13!,{R14}                       ; R14 speichern, da SWI' benutzt werden
MOV     R0,#5                            ; Schauen, ob es das File schon
ADR     R1,FileName                      ; einmal gibt
SWI     "XOS_File"
CMP     R0,#1                            ; wenn nicht,
MOVNE   R0,#11                           ; dann kreiren
ADRNE   R1,FileName                      ; des Files
MOVNE   R2,#&FF0                         ; mit dem Type
ADDNE   R2,#&F                           ;     &FFF = Text
MOVNE   R4,#0                            ;   Lnge
MOVNE   R5,#0                            ;      und Loadadresse = 0
SWINE   "XOS_File"                       ; los
LDMVSFD R13!,{R14}                       ; bei Fehler Stack wiederherstellen
BVS     FileFehler                       ; wenn etwas schieflief, abbrechen
MOV     R0,#&C0                          ; File ffnen - Code
ADR     R1,FileName
SWI     "XOS_Find"                       ; Das File oeffnen
LDMVSFD R13!,{R14}                       ; bei Fehler: Stack wiederherstellen	
BVS     FileFehler                       ; wenn was schieflief, abbrechen
STR     R0,FileHandle
MOV     R1,R0                            ; FileHandle bergeben
MOV     R0,#2
SWI     "XOS_Args"                       ; Ende des Files lesen
MOV     R0,#1
SWI     "XOS_Args"                       ; FilePointer auf Ende des Files setzen
LDMFD   R13!,{R14}                       ; bei Fehler: Stack wiederherstellen
BVS     FileFehler                       ; wenn was schieflief, abbrechen
MOV     PC,R14                           ; zum Initcode zurck

.fehlertext EQUD 10:EQUS "File war nicht zu ffnen!":EQUB 0:ALIGN

.FileFehler
LDMFD   R13!,{R0-R6,R14}
ADR     R0,fehlertext
ORRS    PC,R14,#1<<28                    ; Fehlerflag setzen und Init abbrechen@C7

Das Schlieen des Files ist kurz und schmerzlos, der FileHandle wird geladen,
und der OS_Find 0 schliet das File.

@C3.FileSchliessen
STMFD   R13!,{R14}
MOV     R0,#0
LDR     R1,FileHandle
SWI     "XOS_Find"                       ; File schliessen
LDMFD   R13!,{PC}@C7

Mit den Kenntnissen aus Kursteil 2 knnte man auch ein CLI-Kommando schreiben,
mit dem man den Dateinamen des Zielfiles verndern kann. Allerdings gehrt das
nicht hierher. Interressierte haben damit eine Hausaufgabe.
Wie gesagt, wir wollen bei jedem OS_CLI - Aufruf den String abspeichern, die
Tastaturroutine kommt dann spter, jedoch kontrollieren wir bereits, ob das
Label  .PauseFlag  den Wert 1 enthlt, (das wird spter von der Tastaturroutine
gefllt,) dann drfen wir nichts in's File schreiben, da es zu dieser Zeit nicht
offen ist, sondern springen zurck.

@C3.PauseFlag  EQUD 0
.cli
STMFD   R13!,{R0-R5,R14}             ; Register Speichern
LDR     R0,PauseFlag                 ; Ist File offen
CMP     R0,#1                        ; wenn nicht...
LDMEQFD R13!,{R0-R5,PC}              ; zurueck
MOV     R3,#0                        ; Laenge des Kommandostrings = 0
.Laengenloop
LDRB    R1,[R0,R3]                   ; Ende des KommandoStrings
CMP     R1,#13                       ;        ....
ADDGT   R3,R3,#1                     ; herausfinden
BGT     Laengenloop                  ;
MOV     R2,R0
MOV     R0,#2
LDR     R1,FileHandle                ; FileHandle bernehmen
SWI     "XOS_GBPB"                   ; String ins File
MOV     R0,#&A                       ; LineFeed fr den Wortumbruch
SWI     "XOS_BPut"                   ; ins File senden
LDMFD   R13!,{R0-R5,PC}^@C7

Zur Erluterung: im 'Laengenloop' wird nach dem Terminator (<14) gesucht, R3 als
Zhlvariable wurde mit Bedacht gewhlt, weil es im OS_GBPB die Lnge des zu
speichernden Speicherblocks angibt. Der Rest ist nur noch Formsache, der String
wird ohne den Terminator gespeichert, statt dessen wird das Byte &0A in das File
geschoben, welches im Texteditor ein ordentliches LineFeed abgibt.
Nun kommen wir zum Keyhandler. Dazu schauen wir uns die Eingabekonditionen des
'OS_GenerateEvent' an,

R0 = Event nummer; wenn R0 = 11 :
                        R1 = 0 wenn Key losgelassen, 1 wenn Key gedrckt wird
                        R2 = Key nummer
                        R3 = Tastaturhandler Identifikation

denn das sind genau die Werte, die unsere Keyhandlerroutine bergeben bekommt.
Da unsere Routine bei jedem Event und damit auch bei jeder Tastaturbenutzung
aufgerufen wird, sollten wir schnell entscheiden, ob wir an dem jeweiligen
Event bzw. der gedrckten Taste interressiert sind. Dazu sollten wir aber wis-
sen, mit welcher Taste(nkombination) wir das Ein- und Ausschalten des Mitschrei-
bens belegen wollen. Wir entscheiden uns fr Alt-Print (Drucken) (diese Tasten-
kombination ist sehr wahrscheinlich noch unbenutzt, und es ist unwahrscheinlich,
da sie unabsichtlich gedrckt wird.) Dazu fangen wir den KeyDown-Event fr die
Taste 'Print' (Keynummer &0D) ab und kontrollieren danach mit dem OS_Byte 121,
ob der Anwender auch Alt gedrckt hlt. Die interne Keynummer hat nichts mit
irgendwelchen bekannten ASCII oder anderen Zeichen-listen zu tun, um die Key-
nummer einer Taste herauszufinden beginnt man bei Escape (&00) und zhlt dann
nach rechts ab (Enter zhlt zur untersten Reihe). Die 14. Taste ist Print, also
bekommt sie die Nummer 13 (oder &0D). Unser Test verluft folgendermaen:
 * Kontrolle, ob es der Event 11 ist (Tastaturevent)
 * Kontrolle, ob die Taste gedrckt (nicht losgelassen) wurde
 * Kontrolle, ob die Taste auch wirklich 'Print' war
Im code sieht das so aus:

@C3.events
CMP     R0,#11
CMPEQ   R1,#1
CMPEQ   R2,#&0D
MOVNES  PC,R14@C7

Das ist der schnellstmgliche Check und sollte so oder so hnlich immer aus-
sehen. Da wir als Eventvektorroutine im IRQ Modus sind und als Eventroutine
theoretisch einen SWI unterbrochen haben knnten, mssen wir in den SVC Modus
und dessen R14 abspeichern, bevor wir einen weiteren SWI aufrufen, da sonst der
unterbrochene SWI beim Versuch der Rckgabe eine Endlosschleife auslsen knnte.
Um vom IRQ Modus in den SVC Modus zu gelangen, benutzt man die <P> Option bei
Vergleichsmnemonics, vorzugsweise wird TEQP benutzt.

Also speichern wir den aktuellen Prozessormodus mittels MOV Rx,PC ab und berei-
ten ein weiteres Register darauf vor, da beim TEQP mit ihm nur der Prozessor-
modus gendert wird:
 MOV  Ry,Rx
 BIC  Ry,Ry,#3
Die unteren beiden Bits sind jetzt Null, in Rx bleibt der alte Prozessormodus
erhalten, er kann spter mit einem TEQP Rx,#0 wiederhergestellt werden, jetzt
mssen wir, um in den Modus %11 (SVC) zu kommen die Instruktion
 TEQP Ry,#3
benutzen. Jetzt knnen wir R14_svc speichern, mssen dann aber Rx unverndert
lassen oder zwischenspeichern, damit wir nachher in den Originalmodus zurck-
kehren knnen, also sieht die Routine jetzt ungefhr so aus:
.events
CMP    R0,#11                     ; Key Event?
CMPEQ  R1,#1                      ; Key DOWN Event?
CMPEQ  R2,#&0D                    ; Ist es 'Print'
MOVNES PC,R14                     ; Wenn irgendetwas von dem nicht stimmt, Ende
STMFD  R13!,{R0-R7,R14}
MOV    R7,PC                      ; Flags und Prozessormodus bernehmen
BIC    R6,R7,#3                   ; untere Bits werden zu %00
TEQP   R6,#3                      ; untere Bits : %00 EOR %11 ==> %11 = SVC
STMFD  R13!,{R14}                 ; R14_svc retten
MOV    R0,#121                    ; OS_Byte - code Checke Taste
MOV    R1,#2 EOR &80              ; Taste Alt
SWI    "XOS_Byte"                 ;
CMP    R1,#&FF                    ; Ist Alt gedrckt?
BNE    verlassen                  ; Wenn nicht, gleich zurck
ADR    R0,callback                ; Callback
MOV    R1,#0                      ;          Routine
SWI    "XOS_AddCallBack"          ;                  installieren
.verlassen
LDMFD  R13!,{R14}                 ; R14_svc wiederholen
TEQP   R7,#0                      ; bei dieser Instruktion wird der alte Modus
                                  ; modus mit 0 geEORt, also wiederhergestellt
LDMFD  R13!,{R0-R7,PC}^

Unschwer zu erkennen, da wir eine CallBack-Routine installiert haben, warum?
Da wir eine Routine installiert haben, die (fast) alles und jeden unterbrechen
kann, z.B. auch SWI's gibt es einige Sachen die man nicht darf, z.B. extrem
lange Operationen, FileOperationen gehren dazu, genau wie lange Routinen. Mit
ein Grund dafr ist, da whrend der Zeit der Ausfhrung die Interrupts ausge-
schaltet sind, also einige essentielle Routinen nicht an die Kontrolle kommen,
solange wir dieselbe haben. Ein CallBack ist nun eine Routine, die sofort aufge-
rufen wird, wenn der Prozessor dazu die Zeit hat, das heit zum Beispiel wenn
er in den Usermodus zurckkehren will, oder gerade keine Interruptroutine jeg-
licher Art aktiv ist. In einer CallBackroutine hat man viel weniger Beschrn-
kungen, man mu keine fremden R14's abspeichern und hat kein Zeitlimit. Aller-
dings darf man R13 nicht verndern (auer bei Stackoperationen natrlich), da
Routinen, die im selben Prozessormodus wie unserem Aktuellen aufgerufen werden,
in R13 den Stackpointer erwarten. Man mu nur bei der Rckgabe alle Register
unverndert zurckgeben. Der OS_CallBack verlangt folgendes:
 R0 - Adresse der CallBackroutine
 R1 - R12 bei Aufruf der Routine

Wir mssen, bevor wir versuchen, das File wieder zu ffnen, kontrollieren, ob es
vom Anwender inzwischen gelscht wurde, auerdem fr den Fall, da hier etwas
schiefgeht knnen wir auch nicht einfach einen Fehler ausgeben, wir lassen
einfach das PauseFlag auf 1 und der Anwender mu entweder den Schreibschutzsta-
tus umsetzen oder das File schliessen, falls es inzwischen von einem anderen
Programm geffnet wurde, und es dann nochmal versuchen. Jetzt ist aber nur noch
pures Kopieren der obigen Routinen, wie gesagt, diesmal mit anderen Fehlerhand-
lern:

.callback
STMFD  R13!,{R0-R6,R14}
LDR    R0,PauseFlag                      ; Ist das File zur Zeit
CMP    R0,#1                             ; fr uns offen?
BEQ    FileOeffnen2                      ; Nein - dann aufmachen
B      FileSchliessen2                   ; Ja   - dann schlieen
.zurueck_oeffnen                         ; der Rcksprunglabel, da SWI's theore-
                                         ; retisch R14 berschreiben knnten,
                                         ; kein BL
MOV    R0,#0                             ; Fr's nchste Mal und den CLI-Handler
STR    R0,PauseFlag                      ; das Flag auf 'Mitschreiben' setzen
LDMFD  R13!,{R0-R6,PC}^
.zurueck_schliessen
MOV    R0,#1                             ; Fr's nchste Mal und den CLI-Handler
STR    R0,PauseFlag                      ; das Flag auf 'Pause' setzen
LDMFD  R13!,{R0-R6,PC}^

.FileOeffnen2
MOV     R0,#5                            ; Schauen, ob es das File schon
ADR     R1,FileName                      ; einmal gibt
SWI     "XOS_File"
CMP     R0,#1                            ; wenn nicht,
MOVNE   R0,#11                           ; dann kreiren
ADRNE   R1,FileName                      ; des Files
MOVNE   R2,#&FF0                         ; mit dem Type
ADDNE   R2,#&F                           ;     &FFF = Text
MOVNE   R4,#0                            ;   Lnge
MOVNE   R5,#0                            ;      und Loadadresse = 0
SWINE   "XOS_File"                       ; los
LDMVSFD R13!,{R0-R6,PC}^
MOV     R0,#&C0                          ; File oeffnen
ADR     R1,FileName
SWI     "XOS_Find"                       ; Das File oeffnen
LDMVSFD R13!,{R0-R6,PC}^
STR     R0,FileHandle
MOV     R1,R0                            ; FileHandle bergeben
MOV     R0,#2
SWI     "XOS_Args"                       ; Ende des Files lesen
MOV     R0,#1
SWI     "XOS_Args"                       ; FilePointer auf Ende des Files setzen
LDMVSFD R13!,{R0-R6,PC}^
B       zurueck_oeffnen

.FileSchliessen
MOV     R0,#0
LDR     R1,FileHandle
SWI     "XOS_Find"                       ; File schliessen
MOV     R0,#0
STR     R0,FileHandle
B       zurueck_schliessen

Wie schon in den Kommentarzeilen erwhnt, benutzen wir Rcksprunglabels, denn
da die CallBackroutine im SVC Modus aufgerufen werden kann, wrden SWI's R14
korrumpieren, nun entfllt das Stackgeschiebe.

Damit ist das Module eigentlich komplett, jedoch wre ein besseres Handling,
sprich CLIkommandos, eine lohnenswerte Erweiterung, einmal ein 'LeseStatus'-
Kommando, das den Inhalt vom  .PauseFlag  ausgibt, dann ein 'SetzeFile'-
Kommando, mit dem man das Zielfile umsetzen kann. Und wer sich die Mhe macht,
und das Module entsprechend erweitert, kann es einsenden, das komfortabelste
wird mit einem kostenlosen Abo fr 3 Ausgaben prmiert.

Damit wren wir am Ende dieses Kursteils angelangt. Da es in diesem Teil um sehr
komplexe Sachverhalte ging, die sich auch sehr schwer erklren lassen (zumindest
auf Deutsch), sind wir fr Nachfragen und Verbesserungsvorschlge sehr dankbar.
Fr smtliche Probierereien mit Vektoren und CallBacks empfehlen wir, nichts
Wichtiges ungespeichert zu lassen, uns ging es nmlich so, da wir an diesem
Text ungefhr 5x soviel geschrieben habe, wie wir jetzt lesen knnen, hauptsch-
lich, weil eine Endlosschleife oft alles zum Abstrzen brachte, aber der Normale
durchschnittsanwender schreibt ja auch keinen Modulekurs whrend des Programm-
ierens. Nun gut... wir sehen uns dann im nchsten Kursteil, wenn es um SWI's
geht

                                                                      @G"^.red"
