2.12 Verwendung von Libraries^2.12.1 KonzeptionellesLibraries (deutsch: Bibliotheken) sind Sammlungen von Funktionen, SUBs, Konstanten und Struktur-Typen, die von anderen Programmen und Libraries verwendet werden können. Libraries können keine statischen UI-Objekte enthalten, aber UI-Objekte können zur Laufzeit erzeugt werden (siehe unten).Libraries bieten folgende Vorteile:
Um eine Library mit R-BASIC zu schreiben, gehen Sie folgendermaßen vor:
IncludeDie Anweisung Include bindet eine Library ein. Jedes Programm kann bis zu 32 Libraries einbinden, wobei auch die Libraries zählen, die von eingebundenen Libraries verwendet werden.Include erwartet den GEOS Namen der Library, das heißt, die Groß- und Kleinschreibung als auch eventuelle Leerzeichen im Namen müssen beachtet werden.
Syntax: INCLUDE "LibraryName" Beispiel: |
INCLUDE "Demo Library" |
customErrorDie numerische Systemvariable customError (Datentyp INTEGER) kann benutzt werden um eine selbstdefinierte Fehlernummer zu speichern. Sie kann geschrieben und gelesen werden. Das ist insbesondere für Libraries, auch für SDK-Libraries, interessant. R-BASIC selber verwendet diesen Wert nicht.Beispiel: |
customError = 12 |
IF customError = 4 THEN Print "Fehler 4 aufgetreten." |
|
Beispiel: Wir wollen eine Library schreiben, die zwei Routinen und zwei Konstanten exportiert. Die Funktion Schummel soll eine Zahl von 1 bis 6 liefern, wobei die 6 aber häufiger vorkommt. Die Sub Meldung soll in Abhängigkeit von einer Zahl, die der Routine übergeben wird, zwei unterschiedliche Meldungen ausgeben. Dazu brauchen wir die beiden Konstanten.
Den kompletten Code für dieses Beispiel finden Sie im Ordner "Beispiel\Library", Dateien "Library einfach" und "Library einfach Test Programm".
|
CONST STATE_WIN = 1 CONST STATE_LOSE = 2 DECL FUNCTION Schummel() AS Real DECL SUB Meldung(state as Real) |
| Den Code für die beiden Routinen schreiben wir im BASIC-Code Fenster: |
FUNCTION Schummel() AS Real
DIM x
x = 10*Rnd() ' 0 ... 9.9999999
x = Int(x)+1 ' 1 ... 10
IF x > 6 THEN x = 6
Return x
END Function
SUB Meldung(state as Real)
IF state = STATE_WIN THEN
MsgBox "Wow, du hast gewonnen. " \
+ "Das hätte ich dir gar nicht zugetraut."
ELSE
WarningBox "Du hast ja so mies gespielt! " \
+ "Kein Wunder, dass du verloren hast."
END IF
End SUB
|
|
Wir speichern die Library unter einem aussagekräftigen Namen, z.B. "Test Lib" und compilieren sie dann. Der Name wird für die Include-Anweisung benötigt.
Unser Testprogramm könnte so aussehen: |
Include "Test Lib"
ClassicCode
DIM x
x = schummel()
Print x
IF x > 4 THEN
Meldung(state_win)
ELSE
Meldung(state_lose)
End IF
|
|
Die Anweisung ClassicCode bewirkt, dass R-BASIC automatisch einige UI-Objekte anlegt (ein Primary mit einem View und einem BitmapContent), so dass die Print-Anweisung verwendbar ist und außerdem der Code beim Programmstart automatisch abgearbeitet wird (sogenannter klassischer BASIC Modus).
Wenn wir jetzt eine Änderung an der Library in der Library vornehmen, müssen wir sie neu compilieren. Beim nächsten Start unseres Testprogramms wird die geänderte Library verwendet. Es ist dazu nicht nötig (aber trotzdem sinnvoll), die Library zu speichern. Beim Schreiben einer Library können Sie alles tun, was sie auch beim Schreiben eines normalen Programms tun können. Es ist aber ist folgendes zu beachten:
Wenn ein Programm eine Routine (Sub, Function, Actionhandler) aus einer Library verwendet, so wird die Routine über eine Nummer identifiziert. Diese Nummer entspricht der Position in der Liste der exportierten Routinen. Dabei zählen nur Routinen. Vereinfacht gesagt zählt R-BASIC die DECL-Anweisungen im EXPORTS Fenster. Das bedeutet konkret:
Beispiel. Das ursprüngliche EXPORT-Fenster sieht so aus: |
CONST ZAHL_1 = 12 CONST TEXT_1 = "Hallo Welt" DECL SUB Routine1 ( ) DECL FUNCTION Routine2(x as REAL) as REAL |
| Inkompatible Änderung: Reihenfolge vertauscht |
CONST ZAHL_1 = 12 CONST TEXT_1 = "Hallo Welt" DECL FUNCTION Routine2(x as REAL) as REAL DECL SUB Routine1 ( ) |
| Inkompatible Änderung: Neue Routine eingeschoben |
CONST ZAHL_1 = 12 CONST TEXT_1 = "Hallo Welt" DECL SUB Routine3 (z as INTEGER ) DECL SUB Routine1 ( ) DECL FUNCTION Routine2(x as REAL) as REAL |
| Kompatible Änderung: Neue Routine angefügt |
CONST ZAHL_1 = 12 CONST TEXT_1 = "Hallo Welt" DECL SUB Routine1 ( ) DECL FUNCTION Routine2(x as REAL) as REAL DECL SUB Routine3 (z as INTEGER ) |
| Kompatible Änderungen: Konstanten hinzugefügt und verschoben |
CONST ZAHL_1 = 12 CONST ZAHL_2 = 559.8 DECL SUB Routine1 ( ) DECL FUNCTION Routine2(x as REAL) as REAL CONST X_1 = 77 CONST X_2 = 85.7 DECL SUB Routine3 (z as INTEGER ) CONST TEXT_1 = "Hallo Welt" ' verschieben ist OK CONST TEXT_2 = "Hallo Jungs" |
Die Library-VersionsnummerJedes Mal wenn eine neue Version einer Library veröffentlicht wird sollten Sie die Versionsnummer ändern.Die Werte für Major und Minor können im Menüpunkt "Programm" geändert werden. Der Major-Wert sollte geändert (vergrößert) werden, wenn wesentliche neue Funktionen dazugekommen sind. Ein neuer Wert für Minor sollte Bugfixes oder kleinere Änderungen anzeigen. Insbesondere wenn Sie neue Routinen hinzugefügt haben sollen Sie die Versionsnummer (Major oder Minor) vergrößern. Programme werden mit neueren Versionen einer Library problemlos zusammenarbeiten, wenn Sie die oben angegebenen Hinweise zur Kompatibilität beachten. Eine ausführliche Beschreibung der Versionsnummern finden Sie im Kapitel 2.11.1.
ConvertObjForSDKDies ist eine Hilfsfunktion für Programmierer von SDK Libraries. "Normale" Objektvariablen speichern R-BASIC Objekte in einer Art und Weise, mit der man im SDK nichts anfangen kann. Die Funktion ConvertObjForSDK liefert eine "Objektvariable", die so angepasst wurde, dass man aus einer SDK-Routine heraus mit dem BASIC-Objekt kommunizieren kann.Syntax: <objVar> = ConvertObjForSDK ( <objExpression> ) Wichtige Hinweise:
R-BASIC Objektvariablen enthalten eine Referenz auf ein Objekt, die nicht identisch mit dem im SDK verwendeten Objekt-Pointer (optr) des Objekts ist. ConvertObjForSDK ersetzt diese BASIC interne Referenz durch den realen optr des Objekts. Weitere Details zu Objektvariablen und wie man ConvertObjForSDK einsetzt finden Sie in der Beschreibung, wie man SDK Libraries erstellt. Diese kann auf der R-BASIC Homepage heruntergeladen werden.
2.12.2 UI-Objekte in LibrariesDieses Kapitel richtet sich an fortgeschrittene Programmierer.Wie bereits oben erwähnt können Sie in einer Library keinen Objekt-Tree im UI Codefenster anlegen. Es ist jedoch zulässig Objekte zur Laufzeit des Programms anzulegen und später wieder zu vernichten. Die dazu benötigten Befehle CreateObject und DestroyObject sind im Kapitel 2.1.5 (Anlegen und Vernichten von Objekten zur Laufzeit) des Objekthandbuchs beschrieben. Außerdem sollten Sie die Kapitel 2.1.3 (Verwaltung von Objektblöcken, Befehle CreateObjBlock und DestroyObjBlock) sowie 3.3.8 (Hintertürchen für Programmierer, Befehle ObjAddHint und ObjRemoveHint) aus dem Objekthandbuch lesen. Um eine Library zu schreiben, die Objekte, z.B. eine Dialogbox, bereitstellt, sollten Sie folgendermaßen vorgehen:
Beispiel
Die folgenden Codefragmente sind an die Beispiele "UI Lib" und "Dialog Library" sowie den dazugehörigen Testprogrammen angelehnt. Die vollständigen Dateien finden Sie im Ordner "Beispiel\Library". Die beiden Beispiele beleuchten unterschiedliche Aspekte des Themas.
Erzeugen eines UI-Trees- Verwenden Sie CreateObjBlock um einen neuen Objektblock anzulegen und CreateObject um die gewünschten Objekte darin zu erzeugen. - Setzen Sie die Instance-Variablen der Objekte auf die Werte, die Sie sonst im UI-Code setzen würden. Verwenden Sie den Befehl ObjAddHint, um Instancevariablen zu setzen, die sonst nur im UI-Code verfügbar sind. Vergessen Sie nicht, die Objekte in einem Tree zu verlinken (Instancevariable parent setzen, Children ist read-only). Die Initialisierungs-Routine. Sie soll folgenden UI-Tree nachbilden: |
Dialog LibraryDialog Caption$ = "Notizen eingeben" Children = DialogClearButton End Object Button DialogClearButton Caption$ = "Text löschen" ActionHandler = DoClearText End Object |
| Dabei muss der Actionhandler "DoClearText" irgendwo in der Library implementiert sein. |
FUNCTION BuildLibUI () AS object
dim objBlock as handle
dim dlg, obj as object
! Anlegen des Objektblocks, der die Objekte aufnehmen soll
objBlock = CreateObjBlock()
! In diesem Objektblock erzeugen wir die UI-Objekte
! und setzen die entsprechenden Instancevariablen
dlg = CreateObject (objBlock, Dialog)
dlg.caption$ = "Notizen eingeben"
ob = CreateObject (objBlock, Button)
ob.caption$ = "Text löschen"
ob.actionHandler = DoClearText
ob.parent = dlg, 0 ' Button in Tree einbinden
|
Zugriff auf Objekte in der LibraryNur die Library selbst kennt die genaue Struktur des UI-Trees. Deshalb sollten Sie das "Finden" von Objekten als Routine in der Library programmieren. Falls Sie später die Struktur des UI-Trees ändern, müssen Sie nur Code in der Library ändern. Die Programme, welche die Library nutzen, brauchen dann nicht neu compiliert werden.Die folgende Routine liefert den Button, der oben, in der Routine BuildLibUI, angelegt wurde. Ihr wird das Top-Objekt des Library-Trees übergeben. Dieses ist das einzige, dass das aufrufende Programm wirklich kennt. |
FUNCTION LibFindButton (topObj as OBJECT) AS OBJECT ' Wir wissen: Der Button ist das erste Child des Dialog-Objekts return topObj.Children(0) END FUNCTION |
Im Beispielcode der Library "UI Lib" wird erklärt, wie man von einem ActionHandler aus ein Objekt im UI-Tree der Library finden kann.
Vernichten des UI-TreesUm das System beim Beenden des Programms sauber zu halten sollten Sie dafür sorgen, dass alle Objekte und Objektblöcke, die Sie angelegt haben, auch wieder vernichtet werden. Die folgende Routine erledigt das für einen kompletten Tree. Ihr wird das Top-Objekt des zu vernichtenden Trees übergeben. Anschließend müssen Sie noch den leeren Objektblock freigeben. Den Code dafür finden Sie in den Beispielen.Die Routine DestroyTree arbeitet rekursiv. Das heißt, sie ruft sich selbst auf. Rekursive Programmierung ist schwer zu verstehen und schwer zu erklären. Aber sie ist sehr leistungsfähig und der übliche Weg, Baumstrukturen durchzugehen. Sie können diese Routine einfach benutzen. Eine kurze Erklärung finden Sie im Code der Beispiele. |
SUB DestroyTree (obj as object)
dim ch as object
WHILE obj.numChildren ' noch children da?
ch = obj.children(0) ' erstes Child holen
DestroyTree(ch) ' ... und samt seiner Unter-Children vernichten
' das ehemals zweite Child ist jetzt das erste Child
WEND
' Das Objekt hat jetzt keine Children mehr
obj.parent = NullObj(),0 ' Objekt aus dem Tree nehmen
DestroyObject(obj) ' Objekt vernichten
END SUB
|
Beachten Sie, dass diese Beispielroutine nur den regulären Tree vernichtet. Sie prüft nicht ab, ob das zu vernichtende Objekt ein View ist, das ein Content hat. Bei Bedarf können Sie vor dem eigentlichen Vernichten des Objekts die Instancevariable Class$ abfragen. Sie enthält die Objektklasse im Klartext (z.B. "VIEW") und Sie können dann das Content oder den damit verbundenen VisTree vernichten. Die Routine DestroyTree arbeitet auch für den VisTree.
Verwendung im ProgrammIm Programm müssen Sie den Library UI-Tree anlegen (am Programmstart) und wieder vernichten (am Programmende). Dazu verwenden Sie die OnStartup und OnExit Handler des Application-Objekts. Die einfachste Variante sieht so aus: |
SYSTEMACTION AppStartup DIM ob as OBJECT ob = BuildLibUI() ob.parent = DemoPrimary, 0 ' Einbinden in den GenTree END ACTION |
SYSTEMACTION AppExit DIM libTopObj as Object libTopObj = DemoPrimary.Children(0) DestroyLibUI(libTopObj) END ACTION |
|
Jedes Mal, wenn das Programm startet, wird die Library UI erzeugt und bei jedem Programmende wird sie vernichtet. Der wesentliche Nachteil bei diesem einfachen Vorgehen ist, dass das Vernichten und Erzeugen auch passiert, wenn GEOS bei offenem Programm herunterfährt. Das kostet nicht nur Zeit, sondern es gehen auch alle Eingaben (z.B. Texte oder Auswahlfelder), die in der Library UI gemacht wurden, verloren.
Deswegen ist es eine bessere Idee, abzufragen ob GEOS herunterfährt oder nach einem System-Shutdown neu startet. Der Parameter "flags" enthält spezielle Bits, die genau das anzeigen. Das Flag AF_RESTORE ist gesetzt wenn das Programm nach einem System Shutdown wieder öffnet. Das Flag AF_SHUTDOWN ist gesetzt wenn das System herunterfährt, unser Programm aber noch offen ist. Um zu testen, ob das Flag gesetzt ist, verwenden wir die logische Operation AND. Ergibt diese Operation den Wert Null, so ist das Bit nicht gesetzt und wir müssen handeln. |
SYSTEMACTION AppStartup dim ob as object IF (flags AND AF_RESTORE) = 0 THEN ' Das Programm startet neu. ' Wir müssen die Library-UI erzeugen. ob = BuildLibUI() ob.parent = DemoPrimary, 0 ' Einbinden in den GenTree END IF END ACTION |
SYSTEMACTION AppExit DIM libTopObj as Object IF (flags and AF_SHUTDOWN) = 0 THEN ' Das Programm wird geschlossen. ' Wir müssen die Library-UI vernichten libTopObj = DemoPrimary.Children(0) DestroyLibUI(libTopObj) ' Cleanup-Routine END IF END ACTION |
Es ist möglich, die UI-Objekte der Library in globalen Variablen zwischenzuspeichern. Das erleichtert den Zugriff auf sie. Sie müssen aber beachten, dass globale Variablen einen Systemneustart nicht überleben. Im Beispielcode des Programms "Dialog Library Test Programm" ist beschrieben, wie Sie in diesem Fall vorgehen können.
^ |