Folgende Punkte waren mir wichtig:
- Bei DLLs darf keine Befehls-Zeile außerhalb von Proceduren sein
- IDE soll die Programmierung unterstützen, z.b. Autovervollständigung
- Es sollen keine Dateien rumkopiert werden (Module)
- Vererbung
- Unabhängig von Modulen etc.
- Keine vordefinierten Methoden/Member
- Optionaler Startparameter
- Es soll sich wie eine PB-Syntax anfühlen
- Unterstützung von Konstruktor, Destruktor und Kopiekonstruktor
Eine Klasse ist schnell deklariert. Man erstellt ein Interface und eine Structure. Die Structure muss den Namen des Interface haben, aber mit _ beginnen. Idealerweise sollten sie mit EXTENDS die BaseClass bzw. _BaseClass definieren. Alternativ kann man bei der Structure auch als erstes Element ein __VT.i nutzen.
mit DefineClass(<ClassName>[,<Extends>]) deklariert man die Klasse. Das Sieht dann bspw. so aus:
Code: Alles auswählen
Interface Counter Extends BaseClass
Get()
Set(Value)
Add(Value=1)
EndInterface
Structure _Counter Extends _BaseClass
_Value.i
EndStructure
DeclareClass(Counter)
AllcoateObject(<Class> [,(<StartParameter>)])
FreeObject(<object>)
Jetzt kann man schon die Klasse benutzen. Mit AllocateObject(<ClassName>) oder <ClassName>() kann man ein Objekt erzeugen. Mit FreeObject(<Object>) wird das Objekt zerstört. FreeObject gibt übrigens immer 0 zurück, so das man es gut zum Löschen des Pointers nutzen kann.
Code: Alles auswählen
Define *test.Counter
*test=AllocateObject(Counter)
Debug "obj:"+Hex(*test)
Debug *test\Get()
*test\set(20)
Debug *test\get()
*test\add(20)
Debug *test\Get()
*test=FreeObject(*test)
Etwas Trickreicher ist die Erstellung der Methoden. Es wird die normale Procedure-Kommando benutzt, als Procedure-Namen nimmt man das Maco Class(<ClassName>,<Method>,<Parameterblock>). Der Parameterblock muss zwingend als erstes Element das Object enthalten, als bspw. *Self._Counter . Will man statt eines Integers ein String zurückgeben, muss man ClassS benutzen, bei den anderen Typen entsprechend.
Sieht dann so aus:
Code: Alles auswählen
Procedure Class(Counter,Get, (*self._counter))
ProcedureReturn *self\_Value
EndProcedure
Procedure Class(Counter,Set, (*self._counter,Value))
*self\_Value=Value
EndProcedure
Procedure Class(Counter,Add, (*self._counter,Value=1))
*self\_Value+Value
EndProcedure
Alternativ kann man auch
AsMethod(<Class>,<Method>)
Einfach eine Procedure mit den Namen <Class>_<Method> und anschließend ein AsMethod einfügen. bspw so:
Code: Alles auswählen
Procedure Counter_Add(*self._counter.Value=1)
*self\_Value+Value
EndProcedure:AsMethod(Counter,Add)
Wenn übrigens eine Klasse vererbt wurde, kann man die SuperClass (=Parent) -Methoden hier auch überschreiben.
ClassConstructor(<Class>,(<Parameter>))
ClassDestructor(<Class>,(<Parameter>))
ClassCopyConstructor(<Class>,(<Parameter>))
Für die Konstruktoren/Destruktoren muss man entsprechend ClassConstructor(<ClassName>,<Parameter>), ClassDestructor(<ClassName>,<Parameter>) und ClassCopyConstructor(<ClassName>,<Parameter>) verwenden. Alternativ kann man auch direkt <Class>___Constuctor(), <Class>__Destructor() und <Class>___CopyConstructor(). Achtet auf die drei Unterstriche. Ein "AsMethod()" ist hier nicht nötig, die Konstruktoren/Destruktoren werden automatisch erkannt.
Wobei man hier Aufpassen muss, das eine SuperClass-Constructor nicht überschrieben wird, sondern beide aufgerufen werden. Gleiches beim Destructor. Der CopyConstructor erhält zwei Parameter. Das aktuelle neue Object und das alte. Das Object wurde schon kopiert! Es muss nur die Klasse überprüft werden, ob sie Member noch Sinn machen. Bspw. wenn ein Image in der Klasse gespeichert wurde, dann muss man eine Kopie erstellen und diese in neuen Object abspeichern.
WICHTIG: Ein (Copy)Constructor muss #True zurückliefern, ansonsten wird das Objekt sofort wieder zerstört!
Code: Alles auswählen
Procedure ClassConstructor(counter, (*self._counter))
Debug "New Counter:"+Hex(*self)
Procedurereturn #True
EndProcedure
Procedure ClassDestructor(counter, (*self._counter))
Debug "Free Counter:"+Hex(*self)
EndProcedure
Procedure ClassCopyConstructor(Counter, (*self._Counter,*org._Counter))
Debug "Copy Counter:"+Hex(*self)+" from "+Hex(*org)
Procedurereturn #True
EndProcedure
Wenn man alle Methoden definiert hat, muss man die Klasse noch mit einen DefineClass(<Classname>[,<Extends>]) finalisieren. Wichtig hier ist, das <Extends> wirklich optional ist, es reicht völlig aus, ein Extends bei Declare anzugeben, er wird übernommen. Gibt man ihn bei DeclareClass auch an, wird der Code ein bisschen einfacher.
Code: Alles auswählen
DefineClass(counter)
Hier ein Beispiel für ein Vererbung:
Code: Alles auswählen
Interface CounterPlus Extends Counter
GetTimestamp()
EndInterface
Structure _CounterPlus Extends _Counter
_timestamp.i
EndStructure
DeclareClass(CounterPlus,Counter)
Procedure Class(CounterPlus,Set, (*self._CounterPlus,value));-counterplus set
*self\_Value=value
*self\_timestamp=12
EndProcedure
Procedure Class(CounterPlus,Add, (*self._CounterPlus,value))
*self\_Value+value
*self\_timestamp=1
EndProcedure
Procedure Class(CounterPlus,GetTimestamp, (*self._CounterPlus))
ProcedureReturn *self\_timestamp
EndProcedure
Procedure ClassConstructor(CounterPlus, (*self._CounterPlus))
Debug "New Counterplus:"+Hex(*self)
EndProcedure
Procedure ClassDestructor(CounterPlus, (*self._CounterPlus))
Debug "Free Counterplus:"+Hex(*self)
EndProcedure
Procedure ClassCopyConstructor(CounterPlus, (*self._CounterPlus,*org._CounterPlus))
Debug "Copy Counterplus:"+Hex(*self)+" from "+Hex(*org)
EndProcedure
DefineClass(CounterPlus)
ReferenceObject(<Object>)
Erhöht den Referenz-Zähler des Objekts. Für jede Referenz muss ein separates FreeObject() aufgerufen werden, bevor das Objekt wirklich freigegeben wird.
Zurück wird übrigens der Zähler zurückgegeben.
CountReferenceObject(<Object>)
Gibt die Anzahl der Referenzen des Objekts zurück.
CopyObject(<Object>)
Erstellt eine Kopie von Objekt und gibt es zurück. Falls es Fehlschlägt, #Null.
IsClassObject(<ClassName>,<Object>)
Überprüft, ob das Objekt zur Klasse gehört. Wichtig: Objekte gehören auch immer zu der SuperClass (=Parent)!
IsObject(<Object>)
Überprüft, ob das Ding wirklich ein Objekt ist, das mit meinen Routinen erstellt wird. *WICHTIG* es wird hier mit einen PEEKI ein Integer unter den Objekt gelesen und kontrolliert, ob es einen bestimmten Wert hat. Das ganze kann also ein Programm zum Absturz bringen, wenn man bspw ein Test mit den Wert 8 macht! (Null wird abgefangen)
ClassId(<Class>)
Gibt die Klassen-ID zurück. Ist im Prinzip die Adresse der VTable.
ObjectId(<Object>)
Gibt die Klassen-ID eines Objekts zurück. Damit kann man überprüfen, ob ein Objekt wirklich exakt zu einer Klasse gehört. ClassId(<Class>)=ObjectId(<Object>)
CountObject(<Class>)
Gibt zurück, wieviele Objekte zu dieser Klasse gehören. Kann man nutzen um Speicherlecks rauszufinden.
GetClassMethod(<class>,method.s)
Ermittelt zu einer Klasse eine bestimmte Methode und gibt die Adresse der Procedure zurück, oder 0 wenn es die Methode nicht gibt.
Code: Alles auswählen
Debug "Method CounterPlus\Timestamp "+Hex(GetClassMethod(test::CounterPlus,"GetTimeStamp"))
Wie oben, nur wird hier nach einer Methode eines Objekts gesucht.
Code: Alles auswählen
Debug "Method x4\text "+Hex(GetObjectMethod(x4,"text"))
wie oben, nur wird anstelle der Adresse ein Index in der VT-Table (beginnend mit 0) zurückgegeben. Wenn der Index nicht vorhanden ist, wird eine -1 zurückgegeben.
DeclareDebugObject()
DefineDebugObject()
DebugObject(<Object>)
Leider muss man den Befehl erst Definieren, sonst ist er nicht einsetzbar (REF-Version/bei der Includeversion ist das schon passiert). Er gibt in Debug-Fenster aus, zu welchen Klasse das Objekt gehört und welche Methoden es kennt.
Code: Alles auswählen
DebugObject(x4)
DeclareGetClassNameMethod()Output hat geschrieben:OUTPUT OBJECT x4 @49B0C70 of SteuerP
Set @14000AF7C
get @140008B18
Text @1400077CD
OUTPUT END
DefineGetClassNameMethod()
GetClassNameMethod(ClassName.s,Method.s)
Auch hier muss man zuerst die Procedure definieren (REF-Version/bei der Includeversion ist das schon passiert). Funktioniert ähnlich wie GetClassMethod(), nur das hier zwei Strings übergeben werden. Wichtig beim Klassenname ist, das der Modulname *IMMER* (falls vorhanden) angegeben werden muss, auch wenn das Modul eigentlich mit UseModule verfügbar ist.
Code: Alles auswählen
Debug "Class test::counter get:"+Hex(GetClassNameMethod("teSt::CounTeR","Get"))
Gibt den Klassenname des Objekts zurück.
GetClassIdName(classid)
Gibt den Klassenname einer ClassID (=VT Table) zurück.
Well man ein Objekt Threadsafe machen, sollte man bei der Declaration Threadsafe_DeclareClass(), Parameter sind identisch wie bei DeclareClass().
Die Methoden muss man allerdings manuell absichern:
LockObject(Object) und UnlockObject(Object)
Das kann man auch ohne Threadsafe nutzen, es wird dann automatisch ein Mutex erstellt und später freigegeben. Konstruktoren und Destruktoren müssen nicht abgesichet werden.
Wer oben richtig gelesen hat, wird sich erinnern, das ich was von Startwerten gesagt hab. Es gibt nur eine Einschränkung, man kann keine Optionale Parameter benutzen. Einfach eine Parameter-Angabe bei Declare/DefineClass einfügen. Den Constructor nicht vergessen, sonst machts keinen Sinn
Hier mal ein Beispiel:
Code: Alles auswählen
Interface Steuer Extends BaseClass
set()
get()
EndInterface
Structure _Steuer Extends _BaseClass
value.i
EndStructure
DeclareClass(Steuer,BaseClass,(StartValue))
Procedure Class(Steuer,set,(*self._steuer,value))
*self\value=value
EndProcedure
Procedure Class(Steuer,get,(*self._steuer))
ProcedureReturn *self\value
EndProcedure
Procedure ClassConstructor(Steuer,(*self._steuer,StartValue))
Debug "Steuer-Constructor "+Hex(*self)+" with Value:"+StartValue
*self\value=StartValue
ProcedureReturn #true
EndProcedure
DefineClass(Steuer,BaseClass,(StartValue),(*self,StartValue))
Code: Alles auswählen
Define x1.steuer=Steuer(10)
Debug x1\get()
Define x2.steuer=AllocateObject(Steuer,(20))
Debug x2\get()
Define x3.steuer=AllocateObject(Steuer,(30))
Debug x3\get()