Ich denke ich hab jetzt eine gute Lösung, die mit keinerlei Verwaltungsaufwand (die VTable wird in einen Data-Block abgelegt) seitens des Programm kommt und auch Objekte als Members akzeptiert, ohne das es Probleme gibt. Zudem kann man Klassen in Modulen definieren. Eine kleine Einschränkung gibt es allerdings doch: Parent und Child müssen zum selben Modul gehören!
Wie man eine Klasse deklariert:
Code: Alles auswählen
Macro DeclareClass_res()
Method(i,set,(a.i=0))
Method(i,add,(b.i))
Method(i,get,())
Member(i,_value)
EndMacro
class::Declare(res)
Member-Variablen sind durch die Methodik von PB immer geschützt, man kann auf sie zwar direkt Zugreifen, das ist aber immer mit ein bischen aufwand verbunden ( *self.sres=obj)
Nach dem Aufruf von class::declare(res) ist die Klasse schon einsatzbereit.
Die Klasse wird immer mit <klassenname>_create() erzeugt und wird wird mit <objektname>\dispose() vernichtet.
Code: Alles auswählen
obj.res=res_create()
Debug obj\get()
obj\set(10)
Debug obj\get()
obj\add(20)
Debug obj\get()
obj=obj\dispose()
Debug "----"
Code: Alles auswählen
Procedure.i res_set(*this.sres,value.i=0)
Debug "res_set"
*this\_value=value
ProcedureReturn *this\_value
EndProcedure
Procedure.i res_add(*this.sres,value.i)
Debug "res_add"
;this\_value+value
*this\self\set(*this\self\get()+value)
ProcedureReturn *this\_value
EndProcedure
Procedure.i res_get(*this.sres)
Debug "res_get"
ProcedureReturn *this\_value
EndProcedure
Procedure res_new(*this.sres)
Debug "konstruktor res"
ProcedureReturn *this
EndProcedure
Procedure res_dispose(*this.sres)
Debug "destruktor res"
EndProcedure
class::Define(res)
class::Define(res) muss als letztes ausgeführt werden. Er erstellt dann die vtable und die Haupt-Konstruktoren/Destruktoren.
Die Proceduren müssen immer <Klasse>_<Member> heißen und der Pointer *this.s<klasse> muss immer als erster Parameter vorhanden sein. Er enthält das Objekt selbst. Wenn man eine andere Methode der Klasse aufrufen will, kann man das einfach durch *this\self\<method>() aufrufen. *this\self zeigt Quasi auf sich selbst. man könnte statt *this\self\get() auch res_get(*this) schreiben. Nur, wenn das Objekt ein Child von res ist, kann es sein, dass das Child den Member Get umdefiniert hat. Will man sicherstellen, das die aktuelle Methode aufgerufen wird, sollte man immer *this\self\get() nutzen, wenn man zwingend die eigene Routine nutzen muss, res_get(*this).
Es gibt zwei besondere Methoden <klasse>_new(*this) und <klasse>_dispose(*this).
_new() ist der Konstruktor. Er wird aufgerufen, wenn der Speicher reserviert wurde und alles andere Initalisiert wurde (bspw. Parent-Klassen, Objekte in Objekte). Er muss als Rückgabewert sich selbst zurückgeben! Er kann genutzt werden, um Member-Variablen mit Defaultwerten zu füllen. Da die Parent-Konstruktoren schon aufgerufen sind, braucht er sich um vererbte Member nicht kümmern.
_dispose() ist der Destruktor. Er wird aufgerufen, bevor der Speicher freigeben wird, bevor die Parent-Klassen Destruktoren aufgerufen und bevor Objekte in Objekte gelöscht werden. Auch hier gilt, das er sich nicht um Parent-Member kümmern muss, das macht der Parent-Destruktor.
Die beiden Methoden dürfen nicht oben deklariert werden und sind optional. Genauso kann ein Parent einen Destruktor haben und das Child nicht und umgekehrt.
Objekte in Objekten werden mit mit Object(typ,name) erstellt:
Code: Alles auswählen
Macro DeclareClass_res2()
Method(i,set,(a.i=0))
Method(i,add,(b.i))
Method(i,get,())
object(res,_value)
EndMacro
class::Declare(res2)
Procedure.i res2_set(*this.sres2,value.i=0)
Debug "res2_set"
ProcedureReturn *this\_value\set(value)
EndProcedure
Procedure.i res2_add(*this.sres2,value.i)
Debug "res2_add"
ProcedureReturn *this\_value\add(value)
EndProcedure
Procedure.i res2_get(*this.sres2)
Debug "res2_get"
ProcedureReturn *this\_value\get()
EndProcedure
Procedure res2_new(*this.sres2)
Debug "konstruktor res2"
ProcedureReturn *this
EndProcedure
Procedure res2_dispose(*this.sres2)
Debug "destruktor res2"
EndProcedure
class::Define(res2)
obj2.res2=res2_create()
Debug"-"
Debug obj2\get()
Debug"-"
obj2\set(10)
Debug"-"
Debug obj2\get()
Debug"-"
obj2\add(20)
Debug"-"
Debug obj2\get()
Debug"-"
obj2=obj2\dispose()
Debug "----"
Code: Alles auswählen
Macro DeclareClass_res3_extends_res2()
method(i,dummy,())
EndMacro
class::Declare(res3,res2)
Procedure res3_dummy(*this)
Debug "dummy"
EndProcedure
Procedure res3_new(*this)
Debug "konstructor3"
ProcedureReturn *this
EndProcedure
Procedure res3_add(*this.sres3,value)
Debug "res3_add"
ProcedureReturn *this\_value\add(value*2)
EndProcedure
class::Define(res3)
obj3.res3=res3_create()
Debug"-"
Debug obj3\get()
Debug"-"
obj3\set(10)
Debug"-"
Debug obj3\get()
Debug"-"
obj3\add(20)
Debug"-"
Debug obj3\get()
Debug"-"
Debug obj3\dummy()
obj3=obj3\dispose()
Bei den Beispiel wird zudem die Methode "get" von res2 durch eine Methode von res3 ersetzt.
Natürlich kann man noch eine eben weiter vererben und man kann Maps, Listen, und Felder benutzen. Bei den letzten drei kann man aber nicht direkt Objekte nutzen, aber Pointer auf Objekte. Die müsste man in new() füllen und dispose() löschen.
Code: Alles auswählen
Debug "-----------------"
Macro DeclareClass_Res4_extends_Res3()
method(s,setmap,(k$,v$))
method(s,getmap,(k$))
member(i,feld,[10])
member(i,List liste,())
member(s,Map karte,())
EndMacro
class::Declare(res4,res3)
Procedure.s res4_setmap(*this.sres4,k$,v$)
*this\karte(k$)=v$
ProcedureReturn *this\karte(k$)
EndProcedure
Procedure.s res4_getmap(*this.sres4,k$)
ProcedureReturn *this\karte(k$)
EndProcedure
Procedure.i res4_get(*this.sres4)
ProcedureReturn *this\feld[3]
EndProcedure
Procedure.i res4_set(*this.sres4,value.i)
*this\feld[3]=value
ProcedureReturn *this\feld[3]
EndProcedure
Procedure.i res4_new(*this.sres4)
Debug "konstrucutro res4"
ProcedureReturn *this
EndProcedure
class::Define(res4)
*obj4.res4=res4_create()
*obj4\setmap("hallo","duda")
*obj4\setmap("haha","gaga")
Debug *obj4\getmap("hallo")
Debug *obj4\getmap("haha")
Debug *obj4\getmap("tusch")
*obj4\set(123)
Debug *obj4\get()
*obj4\dispose()
Code: Alles auswählen
Interface res4
dispose(force=#True)
set.i (a.i=0)
add.i (b.i)
get.i ()
dummy.i ()
setmap.s (k$,v$)
getmap.s (k$)
EndInterface
Structure sres4
*__vtable
*self.res4
*_value.res
feld.i [10]
List liste.i ()
Map karte.s ()
EndStructure
Procedure.s res4_setmap(*this.sres4,k$,v$)
*this\karte(k$)=v$
ProcedureReturn *this\karte(k$)
EndProcedure
Procedure.s res4_getmap(*this.sres4,k$)
ProcedureReturn *this\karte(k$)
EndProcedure
Procedure.i res4_get(*this.sres4)
ProcedureReturn *this\feld[3]
EndProcedure
Procedure.i res4_set(*this.sres4,value.i)
*this\feld[3]=value
ProcedureReturn *this\feld[3]
EndProcedure
Procedure.i res4_new(*this.sres4)
Debug "konstrucutro res4"
ProcedureReturn *this
EndProcedure
Declare res4_free_ (*self,force=#True)
Procedure res4_create ( *self . sres4 =0)
If *self=0
*self=AllocateStructure(sres4)
EndIf
If *self And *self\__vtable=0
*self\__vtable=?res4_vtable
*self\self=*self
EndIf
If *self
*self=res3_create(*self)
EndIf
If *self
ProcedureReturn res4_new (*self)
EndIf
ProcedureReturn 0
DataSection
res4_vtable:
Data.i @res4_free_ ()
Data.i @res4_set ()
Data.i @res3_add ()
Data.i @res4_get ()
Data.i @res3_dummy ()
Data.i @res4_setmap ()
Data.i @res4_getmap ()
EndDataSection
EndProcedure
Procedure res4_free_ ( *self . sres4 ,force=#True)
res3_free_(*self,#False)
If force
FreeStructure(*self)
EndIf
ProcedureReturn 0
EndProcedure
*_value.res wird übrigens in res2 angelegt und initialisiert, der Haupt-Konstruktor (_create) hangelt sich da durch.
Die Procedure "res4_free_" ist der Haupt-Destruktor. Er wird eigentlich aufgerufen, wenn man <object>\dispose() aufruft. Genauso wie beim Konstruktor hangelt er sich durch alle Parents hindurch.
so und hier die nötigen Dateien:
http://game.gpihome.eu/PureBasic/objtest2.7z
Ein paar Tricks, die ich hier anwende:
Neben XIncludeFile gibt es ja noch IncludeFile, wo man die gleiche Datei immer und immer wieder einfügen kann. Klingt banal, ist aber hier extrem wichtig. Ich nutze die Macros dazu, eben die hinzugefügte Datei umfassend zu ändern. Gerade weil man hier ungestört Macros erstellen kann.
Es gibt ja die Möglichkeit mittels Macros Macros in Macros zu erzeugen. Dabei hab ich festegestellt, das diese auch Zeilenumgreifen funktionieren, obwohl das eigentlich nicht möglich sein solle.
Code: Alles auswählen
Macro JoinMacroParts (P1, P2=, P3=, P4=, P5=, P6=, P7=, P8=) : P1#P2#P3#P4#P5#P6#P7#P8 : EndMacro
Macro CreateMacro (name,macroBody=)
class::JoinMacroParts (Macro name, class::MacroColon, macroBody, class::MacroColon, EndMacro) :
EndMacro
Macro CreateQuote (name)
class::JoinMacroParts (class::MacroQuote,name,class::MacroQuote)
EndMacro
Macro CreateSingleQuote (name)
class::JoinMacroParts (class::MacroSingleQuote,name,class::MacroSingleQuote)
EndMacro
;class_extendsvtable
Macro combinelist(name,name2,name3)
class::CreateMacro(name (), name2()
name3())
EndMacro
Macro ifthen(name,class,b,elsemacro)
class::CreateMacro( name (a), CompilerIf Defined(class#_#b,#PB_Procedure)
Data.i @class#_#b ()
CompilerElse
elsemacro (a)
CompilerEndIf)
EndMacro
Und funfact - wenn man in der "module_class_declaraion.pbi" die Reihenfolge der am Anfang leicht ändert von
Code: Alles auswählen
CompilerIf #PB_Compiler_IsMainFile
CompilerError "don't compile me"
CompilerEndIf
class::CreateMacro( __class_extends_#class() , extends() ) :
CompilerIf class::CreateQuote( extends() )<>"$nil"
class::combinelist( __class_declare_#class() ,__class_declare_#extends() , DeclareClass_#class()_extends_#extends() ) :
class::combinelist( __class_current_declare ,__class_declare_#extends() , DeclareClass_#class()_extends_#extends() ) :
CompilerElse
class::combinelist( __class_declare_#class() , DeclareClass_#class() , class::macronil ) :
class::combinelist( __class_current_declare , DeclareClass_#class() , class::macronil ) :
CompilerEndIf
Code: Alles auswählen
CompilerIf #PB_Compiler_IsMainFile
CompilerError "don't compile me"
CompilerEndIf
CompilerIf class::CreateQuote( extends() )<>"$nil"
class::combinelist( __class_declare_#class() ,__class_declare_#extends() , DeclareClass_#class()_extends_#extends() ) :
class::combinelist( __class_current_declare ,__class_declare_#extends() , DeclareClass_#class()_extends_#extends() ) :
CompilerElse
class::combinelist( __class_declare_#class() , DeclareClass_#class() , class::macronil ) :
class::combinelist( __class_current_declare , DeclareClass_#class() , class::macronil ) :
CompilerEndIf
class::CreateMacro( __class_extends_#class() , extends() ) :
Module_class_declaration.pbi - Line 5: Data can only be declared in a Datasection.
Und das hier in Macro Fenster:
Code: Alles auswählen
Macro Extends(): $nil: EndMacro : :
Macro class(): res: EndMacro : :
IncludeFile(class: : #class_includepath+"module_class_declaration.pbi")
Wer gerne die Autovervollständigung nutzen will, kann ja mittels meiner module_class und http://www.purebasic.fr/german/viewtopi ... preprocess eine macrofreie Version der Klassen erzeugen. Also erstmal alle Klassen in einer Datei, PreProcess aufrufen und dann diese in den eigenen Programmen nutzen. Die "test.pb.pre.pb" in der 7z wurde so erzeugt. Die Reste des Module "class" kann man mehr löschen, da taucht eh nur eine Konstante auf, die nirgends mehr gebraucht wird. Ist auch ganz praktisch, wenn man sehen will, wie das ganze Arbeitet.