|
Bugfixes am Forum
Subdomains aktiviert
Counterscript entfernt
|
| |
|
|
Benutzerhandbuch |
|
|
Kapitel 10
Weiterführende Themen
·Residente Dateien
·Betriebssystem-Aufrufe
·Auffinden von Variablen und Labels im Speicher
·Konstanten
·Bedingte Compilierung
·Makros
·Inline Assembler
Residente Dateien
Programme, die mit einer großen Anzahl von NewTypes, Makros oder Konstanten arbeiten, können einfacher geschrieben werden, in dem sogenannte residente Dateien verwendet werden.
Eine residente Datei enthält alle NewTypes, Makros und Konstanten in vor-compilierter, d.h. binärer Form. Dadurch können alle diese Definitionen aus dem eigentlichen Programmcode weggelassen werden, wodurch der Code kleiner wird und sich schneller compilieren läßt.
Um eine solche residente Datei zu erzeugen, müssen alle NewType-Definitionen, Makros und Konstanten in einem Programm zusammengefasst werden. Dieses Programm wird dann in residente Form übersetzt. Es folgt ein Beispiel für ein solches Programm:
NEWTYPE.test
a.l
b.w
End NEWTYPE
Macro mac
NPrint "Hallo"
End Macro
#const=10
Jetzt muß das Programm lediglich mit 'COMPILE & RUN' compiliert werden und anschließend mit 'CREATE RESIDENT' (Compiler-Menü) in eine residente Datei gewandelt werden. Es erscheint dann ein File-Requester, der Sie auffordert, einen Namen für die Datei einzugeben. Mehr muß nicht gemacht werden.
Eine residente Datei kann in ein beliebiges Programm eingebunden werden, indem der entsprechende Dateiname in eines der RESIDENT-Felder des Compiler Options Menüs eingetragen wird. Damit sind alle in der residenten Datei enthaltenen NewTypes, Makros und Konstanten automatisch für das Programm verfügbar.
Die mitgelieferte Datei AMIGALIBS.RES enthält alle Strukturen und Makros, die für den Umgang mit dem Betriebssystem erforderlich sind. Wer schon einmal nahe am Betriebssystem programmiert hat, wird es zu schätzen wissen, daß das übliche Einlesen der Header-Dateien entfällt und damit Minuten beim Compilieren gespart werden.
Betriebssystem-Aufrufe
Es wurde bei der Entwicklung von Blitz2 viel Mühe darauf verwendet, dem Programmierer möglichst weitgehenden Zugriff auf das mächtige Amiga-Betriebssystem zu gewähren.
Der Aufruf von Betriebssystem-Bibliotheken
Nicht alle Befehle des Betriebssystem werden von den internen Blitz2-Befehlen abgedeckt. Dennoch stehen dem erfahrenen Programmierer alle Routinen der Exec-, Intuition-, DOS-, und Graphics-Libraries durch Blitz2 zur Verfügung (s. Anhang 5 des Blitz2 Referenzhandbuchs).
Der Zugriff auf die Standard-Libraries ist durch das "Blitz2 Advanced Programmers Pack" von Acid Software gewährleistet.
Im folgenden Beispiel werden Routinen der Amiga ROM-Graphics und der Intuition Libraries aufgerufen.
FindScreen 0 ;workbench screen verwenden
;Window oeffnen
Window 0,0,10,320,180,$408,"",1,2
rp.l=RastPort(0) ;Rastport fuer window ermitteln
win.l=Peek.l(Addr Window(0)) ;Window-Struktur ermitteln
DrawEllipse_ rp,100,100,50,50 ;graphics library
MoveWindow_ win,8,0 ;intuition library
BitMap 1,320,200,2 ;Arbeitsbitmap erstellen
Circlef 160,100,100,1 ;etwas zeichnen
;Jetzt in das Window transferieren
BltBitMapRastPort_ Addr BitMap(1),0,0,rp,0,0,100,100,$c0
WaitEvent
Mit dem letzten Befehl BltBitMapRastPort_ kann eine Graphik, die mit den schnelleren Blitz2 Bitmap-Befehlen erzeugt wurde, in einem Window ausgegeben werden. Dadurch können die Blitz2-Befehle noch universeller angewendet werden.
Zugriff auf Strukturen des Betriebssystems
Wenn die residente Datei AMIGALIBS.RES vorhanden ist (s. Anfang des Kapitels), ist sogar ein noch weitergehenderer Zugriff auf das Betriebssystem möglich. Im folgenden Beispiel wird auf Strukturen des Betriebssystems zugegriffen:
;Variable *exec zeigt auf die ExecBase Struktur
;Variable *mylist zeigt auf den Typ List
;Variable *mynode zeigt auf den System Knoten
*exec.ExecBase=Peek.l(4)
*mylist.List=*exec\LibList
*mynode.Node=*mylist\lh_Head
While *mynode\ln_Succ
a$=Peek$(*mynode\ln_Name) ;Knotennamen drucken
NPrint a$
*mynode=*mynode\ln_Succ ;gehe zum naechsten Knoten
Wend
MouseWait
Durch die Angabe des Sterns in *variablenname.typ wird Blitz2 angewiesen, statt einer Variablen dieses Typs lediglich einen Zeiger (Pointer) auf eine solche Variable anzulegen. Anschließend kann die Variable (Struktur) behandelt werden wie eine interne Blitz2-Variable.
Mit dem Befehl Peek$ wird ein String aus einer Betriebssystem-Struktur direkt in eine String-Variable eingelesen, bis eine Null (CHR$(0)) gelesen wurde.
Das Auffinden von Variablen und Labels im Speicher
Mit dem kommerziellen Und-Zeichen ('&', engl. "ampersand") wird die Adresse einer Variablen im Speicher referenziert:
;
; Ein Beispiel fuer '&' um die Adresse einer Var. zu finden
;
Var.l=5
Poke.l &Var,10
NPrint Var
MouseWait
Diese Methode entspricht der Funktion VarPtr in anderen BASIC-Versionen.
Wird die Adresse einer String-Variablen ermittelt, so zeigt der Pointer auf das erste Zeichen des Strings. Die Länge des Strings ist in einem Doppelwort an 'Adresse-4' abgespeichert.
Das Fragezeichen '?' dient zum Auffinden der Adresse einer Sprungmarke (Label) des Programms im Speicher. Zum Beispiel so:
;
; Ein Beispiel das die Adresse eines Programms findet
;
MOVE #10,There ;Sogar Assembler-Code hier
NPrint Peek.w(?There)
MouseWait
End
;
There:Dc.w 0 ;und hier auch
Die oben beschriebenen Methoden sind im Grunde nur für Programmierer mit Assembler-Erfahrung von Interesse, die ihre Ziele auf unkonventionelle Weise verfolgen.
Konstanten
Unter einer Konstanten versteht man in BASIC einen Wert, der sich während der gesamten Ausführung des Programms nicht ändert. Die Zahl 5 in der Anweisung a=5 z.B. ist eine Konstante.
Das "Lattenkreuz" '#' (engl. "Hash sign") vor einem Variablennamen kennzeichnet diese als eine Konstante, d.h. ihr Wert kann sich im Laufe des Programms nicht mehr ändern. Die Anweisung #weite=320 legt die Variable #weite endgültig auf den Wert 320 fest.
Konstanten besitzen bestimmte Eigenschaften, sie:
· sind schneller als Variablen und verbrauchen keinen Speicherplatz.
· erhöhen die Lesbarkeit des Programmcodes.
· können ebenfalls im Assembler verwendet werden.
· können für die Auswertung von bedingten Compilier-Anweisungen verwendet werden.
· dürfen nur Integerwerte enthalten.
· vereinfachen die Verwendung eines konstanten Wertes, der an verschiedenen Stellen im Programm verwendet wird.
· können im Quellcode zum Zeitpunkt der Compilierung verändert werden, aber nicht mehr danach während der Laufzeit.
Der wichtigste Aspekt bei der Verwendung von Konstanten, vom Standpunkt des Programmierers aus, ist die Tatsache, daß bestimmte "Naturkonstanten", die an unterschiedlichen Stellen des Programms benötigt werden, durch aussagefähige Namen ersetzt werden, wie z.B. #weite.
Sollte der Wert von "weite" jemals geändert werden müssen, braucht dann lediglich an einer Stelle die Zuweisung #weite=320 in z.B. #weite=640 geändert zu werden, anstatt den gesamten Programmcode nach der Zahl 320 abzusuchen.
Bedingte Compilierung
Die bedingte Compilierung gestattet es, den Compiler quasi ein- und auszuschalten, während dieser sich durch den Programmcode arbeitet.Dadurch ist zu kontrollieren, welche Teile des Programmcodes übersetzt werden und welche nicht.
Dies kann dazu gebraucht werden, um unterschiedliche Versionen eines Programms zu erzeugen, ohne zwei Versionen des Quellcodes pflegen zu müssen. Außerdem kann damit z.B. eine abgemagerte Demo-Version eines Programms erzeugt oder das Programm auf unterschiedliche Hardware-Konfiguration eingestellt werden.
Ebenso kann das bedingte Übersetzen bei der Fehlersuche hilfreich sein, indem - für diesen Zweck - unwichtige Teile des Codes ausgeschaltet werden oder zusätzliche Überprüfungen eingebaut werden. Wir hoffen allerdings, daß der Blitz2 Debugger dies nicht erforderlich macht.
Folgende Compiler-Direktiven stehen zur Verfügung:
CNIF
|
-
|
Compiler einschalten, wenn der numerische Vergleich wahr ist.
|
CSIF
|
-
|
Compiler einschalten, wenn der String-Vergleich wahr ist.
|
CELSE
|
-
|
schaltet den Compiler in den entgegengesetzten Zustand, also ein=>aus und aus=>ein.
|
CEND
|
-
|
Ende eines Blocks der bedingt übersetzt werden soll, der vorherige Zustand des Compilers wird wiederhergestellt.
|
Der Compiler besitzt einen internen Ein/Aus-Schalter. Dieser wird durch die Anweisungen CNIF und CSIF ein- oder ausgeschaltet, CELSE wechselt den Zustand des Schalters und CEND stellt den Zustand vor der letzten CNIF- oder CSIF-Anweisung wieder her. CNIF/CEND-Blöcke können auch verschachtelt werden.
Da die Compiler-Direktiven bereits beim Übersetzen ausgewertet werden, können in den Anweisungen CNIF und CSIF nur Konstanten getestet werden, also '5' oder '#test'. Der Wert von Variablen ist hingegen zur Compilezeit nicht bekannt, sondern erst bei der Ausführung des Programms, daher können Variablen nicht in Verbindung mit diesen Anweisungen verwendet werden.
Im folgenden Beispiel wird der Gebrauch der bedingten Compilierung erläutert:
#demover=1 ;dies ist eine Demoversion
NPrint "Goo Goo Software (c)1993"
CNIF #demover=1
NPrint "DEMONSTRATIONS-VERSION"
CELSE
NPrint "REGISTRIERTE VERSION"
CEND
;
; und spaeter im Programm...
;
.SaveRoutine
CNIF #demover=0 ;nur wenn keine Demoversion
;
;Save-Routine ausfuehren
;
CEND
Return
Der Vorteil dieses Vorgehens gegenüber einer einfachen "If demover=0...EndIf"-Konstruktion besteht darin, daß hierbei der Code für die Abspeicher-Routine überhaupt nicht im Programm enthalten ist und daher auch nicht von Hackern entschlüsselt werden kann.
Die entscheidenden Vorteile des bedingten Compilierens kommen jedoch erst bei der Makro-Programmierung zum tragen.
Makros
Makros werden vorwiegend bei der Assembler-Programmierung oder in maschinenorientierten Sprachen verwendet. Sie werden zu verschiedenen Zwecken verwendet: um Schreibarbeit zu sparen, um einfache Routinen durch schnellere 'Inline'-Versionen zu ersetzen, oder auch um Code zu erzeugen, der in Hochsprachen nicht herzustellen ist.
Ein Makro wird durch die Befehlsfolge "Macro name ... End Macro" definiert. Der Code, der zwischen Macro und EndMacro steht, wird im Gegensatz zu normalem Programmcode nicht übersetzt, sondern zunächst vom Compiler gespeichert. Wird nun ein Makro mit !macroname aufgerufen, ersetzt der Compiler den Makronamen mit dem Inhalt des entsprechenden Makros im Quellcode.
Das folgende Beispiel
Macro mymacro
a=a+1
NPrint "Viel Glueck"
End Macro
NPrint "Dummes Beispiel v1.0"
!mymacro
!mymacro
MouseWait
wird vom Compiler erweitert, sodaß sich folgendes ergibt:
NPrint "Dummes Beispiel v1.0"
a=a+1
NPrint "Viel Glueck"
a=a+1
NPrint "Viel Glueck"
MouseWait
Parameterübergabe bei Makros
Es können auch Parameter an ein Makro übergeben werden, dadurch werden sie noch nützlicher. Parameter werden in geschweiften Klammern { und } eingeschlossen. Bei der Auswertung des Makros werden zunächst die Parameter in den Text des Makros eingefügt und dann das Makro ersetzt.
In der Definition eines Makros wird die Stelle, an der der Parameter eingefügt werden soll, mit dem umgekehrten Apostroph (auf der Amiga-Tastatur oberhalb der TAB-Taste) gefolgt von einer Zahl oder einem Buchstaben (1-9 oder a-z) markiert.
Das folgende Beispiel erläutert die Übergabe von zwei Parametern an ein Makro:
Macro distance
Sqr(`1*`1+`2*`2)
End Macro
NPrint !distance{20,30}
MouseWait
Der Compiler ersetzt nun jedes Auftreten vom `1 durch den ersten und `2 durch den zweiten Parameter.
NPrint Sqr(20*20+30*30)
Wenn mehr als 9 Parameter übergeben werden sollen, werden Buchstaben verwendet: "`a" stellt den zehnten, "`b" den elften Parameter dar usw.
Beim Aufruf können die Parameter aus beliebigem Text bestehen, statt {20,30} hätte im obigen Beispiel auch {x,y} stehen können.
Achtung: Bei der Übergabe von mathematischen Ausdrücken als Parameter an Makros muß unbedingt darauf geachtet werden, daß diese korrekt geklammert sind! Es treten sonst ungewollte Seiteneffekte auf, die sehr schwer zu ermitteln sind, wie hier:
!distance{x*10+20,(y*10+20)}
Diese Zeile wird zu folgendem Ausdruck expandiert:
Sqr(x*10+20*x*10+20+(y*10+20)*(y*10+20)}
Da in dem Makro die beiden Parameter jeweils mit sich selbst multipliziert werden, wird der erste Parameter nicht richtig ersetzt. Dadurch, daß die Klammern fehlen, wird die Multiplikation falsch ausgeführt. Der zweite Parameter hingegen wird korrekt ausgewertet, da der Ausdruck in Klammern angegeben ist.
Der `0-Parameter
Der Parameter mit der Nummer `0 besitzt eine spezielle Bedeutung: er gibt die Anzahl der nachfolgenden Parameter an. Dies kann sowohl zur Überprüfung der korrekten Anzahl der Parameter verwendet werden, als auch dafür, bewußt eine variable Anzahl von Parametern an das Makro zu übergeben.
Das folgende Makro prüft, ob genau zwei Parameter übergeben wurden und erzeugt gegebenenfalls eine Compiler-Meldung:
Macro Vadd
CNIF `0=2
`1=`1+`2
CELSE
CERR "Falsche Anzahl Parameter in '!Vadd'"
CEND
End Macro
!Vadd{a}
Wenn Sie dieses Programm übersetzen lassen, gibt der Compiler die entsprechende Meldung aus, sobald die Stelle "!Vadd{a}" erreicht ist. Die Anweisung CERR ist eine spezielle Compiler-Direktive, die zur Erzeugung von Anwender-spezifischen Fehlermeldungen dient.
Rekursive Makros
Makros können auch rekursiv programmiert werden, d.h. sie können sich selbst aufrufen. Im folgenden Beispiel druckt das Makro den ersten Parameter und ruft sich dann anschließend selbst ohne den ersten Parameter auf. Dadurch wird praktisch die Liste der Parameter nacheinander abgearbeitet, bis das Null-Zeichen, d.h. kein Parameter erreicht ist.
Macro dolist ;bis zu 16 Variablen auflisten
NPrint `1
CSIF "`2">""
!dolist{`2,`3,`4,`5,`6,`7,`8,`9,`a,`b,`c,`d,`e,`f,`g}
CEND
End Macro
!dolist {a,b,c,d,e,f,g,h,i}
MouseWait
Funktionen durch Makros ersetzen
Makros eignen sich besonders dafür, Funktionen zu ersetzen, die keine lokalen Variablen benötigen, dafür aber mehr als einen Wert zurückliefern sollen. Im folgenden Makro "project" werden die Parameter x, y und z auf eine zweidimensionale x-y-Ebene projiziert. Das Ergebnis kann dann wiederum als Parameter für Zeichenbefehle verwendet werden.
Macro project #xm+`1*9-`2*6,#ym+`1*3+`2*6-`3*7:End Macro
#xm=320:#ym=256
Screen 0,28:ScreensBitMap 0,0
For z=-15 To 15
For y=-15 To 15
For x=-15 To 15
Circlef !project{x,y,z},3,x&y&z
Next
Next
Next
MouseWait
Das CMake-Zeichen
Das Zeichen "~" hat eine besondere Bedeutung: es dient dazu, das Ergebnis eines Ausdrucks als Literal (d.h. nicht als Wert, sondern als Zeichen) wiederum in den Code einzufügen. Dies kann sehr nützlich sein, wenn z.B. Variablennamen oder Labels erzeugt werden sollen, die sich zum einen Teil aus einem Parameter und zum anderen Teil aus einem konstanten Ausdruck zusammensetzen.
var2=20
var3=30
Macro lvar
NPrint var~`1~
End Macro
!lvar{2+1}
MouseWait
In diesem Beispiel würde ohne das CMake-Zeichen der Wert 21 ausgegeben werden, da der Ausdruck var`1 mit dem Parameter 2+1 zu var2+1 ersetzt werden würde. Durch das CMake-Zeichen wird nun aber der als Parameter übergebene Ausdruck zunächst ausgewertet und erst dann erfolgt die Ersetzung: es ergibt sich var3.
Inline Assembler
Es besteht die Möglichkeit, Maschinenbefehle des 68000 innerhalb von Blitz2-Programmen mit Hilfe des Inline-Assemblers aufzurufen. Dadurch können erfahrene Programmierer ihre Programme beschleunigen, indem sie bestimmte Blitz2-Routinen durch die entsprechenden schnelleren Maschinenbefehle ersetzen.
Es gibt drei Methoden, Assembler in Blitz2 zu integrieren:
· zeilenweise auf Variablen mit den Befehlen GetReg und PutReg zugreifen.
· ganze Befehle und Funktionen schreiben.
· eigene Blitz2-Libraries erzeugen.
GetReg & PutReg
Mit den Befehlen GetReg und PutReg kann auf die BASIC-Variablen zugegriffen werden. Im folgenden Beispiel wird die Benutzung dieser beiden Befehle erläutert:
a.w=5 ;Word-Variablen benutzen
b.w=10
GetReg d0,a ;Wert von a=>d0
GetReg d1,b ;Wert von b=>d1
MULU d0,d1
PutReg d1,c.w ;Wert von d1=>c
NPrint c
MouseWait
Im nächsten Beispiel wird die erste Bitplane der Bitmap 0 invertiert. Dabei kann ein beliebiger Ausdruck zusammen mit dem GetReg-Befehl verwendet werden. Da GetReg nur mit Datenregistern arbeiten kann, muß die Bitmap-Struktur zunächst in d0 angelegt und dann nach a0 verschoben werden.
Screen 0,3
ScreensBitMap 0,0
While Joyb(0)=0
VWait 15
Gosub inverse
Wend
End
inverse: ;Speicheradress der Bitmap-Struktur=>d0
GetReg d0,Addr BitMap(0)
MOVE.l d0,a0
MOVEM (a0),d0-d1
MULU d0,d1
LSR.l#2,d1
SUBQ#1,d1
MOVE.l 8(a0),a0
loop:
NOT.l (a0)+
DBRA d1,loop
Return
Prozeduren in Assembler
Noch effizienter ist es, ganze Funktionen und Befehle als Assembler-Routinen zu schreiben. Die Parameter werden automatisch in den Registern d0-d5 abgelegt und bei Funktionen wird der Wert in d0 zurückgegeben.
Da das Adress-Register a4 die Basis der lokalen Variablen darstellt, muß zu Beginn der Prozedur der Befehl UNLK a4 stehen. Weiterhin muß die Prozedur hundertprozentig aus Assembler-Befehlen bestehen und die Adress-Register a4 bis a6 dürfen nicht verändert werden.
Im folgenden Beispiel wird gezeigt, wie der Befehl qplot {} in Assembler realisiert und von einer BASIC-Routine aufgerufen wird. Qplot setzt ein Pixel auf der ersten Bitplane der angegebenen Bitmap. Wie man sieht, können mehrere Assembler-Befehle auf einer Codezeile stehen.
Statement qplot{bmap.l,x.w,y.w}
UNLK a4
MOVE.l d0,a0:MULU (a0),d2
MOVE.l 8(a0),a0:ADD.l d2,a0
MOVE d1,d2:LSR#3,d2:ADD d2,a0:BSET.b d1,(a0)
RTS
End Statement
Screen 0,1
ScreensBitMap 0,0
bp.l=Addr BitMap(0)
For y.w=0 To 199
For x.w=0 To 319
qplot{bp,x,y}
Next
Next
MouseWait
Es können auch ganze Bibliotheken mit Routinen in Machinensprache angelegt werden. Dies ist mit dem "Advanced Programmers Pack" von Acid Software möglich, das ein sehr mächtiges Library-System enthält. Es stellt eine effektive Entwicklungsumgebung für die fortgeschrittene Programmentwicklung zur Verfügung.
|
|
|
|
|
|