Seite 2 von 3

Re: Module oop und EnableClass

Verfasst: 30.10.2015 22:40
von GPI
NicTheQuick hat geschrieben:
GPI hat geschrieben:>Macht dein clone() prinzipiell nur ein simples CopyStructure() oder steckt da mehr dahinter? Was ist z.B., wenn sich in der
>Objektstruktur weitere Objekte befinden, die ja tatsächlich nur als Pointer abgebildet werden?
Basis ist immer ein CopyStructure. Aber mein Code beachtet auch Objekte - in der Fassung oben muss man ihn aber immer manuell Initalisieren. Wie gesagt, wenn du an meine zweite Variante interesse hast, sag bescheid.
Nutzt du dann die Runtime Structures dafür?
Äh ich weis jetzt nicht genau, was du meinst. In obrigen Code mach ich es so: Ein Object in den Properties muss ja in Initalize() manuell initalisiert werden. Dafür gibt es einen Befehl. Dieser merkt sich, wo das Object in der Structur ist (geht ja mit offsetof). Wenn jetzt clone oder dispose aufgerufen wird, weis er wo sich Objecte in den Properties sich befinden und behandelt diese entsprechend.
>Kann man auf die Methoden des Parents zugreifen?
logisch.
Auch, wenn man sie überschrieben hat? Das geht nämlich bei mir, weil für jede Basisklasse eine eigene vTable existiert.
Achso. Wenn man bevor man die Methode überschreibt die alte sichert - ein Alias erstellt - dann kann man auf die alten Methoden einfach zugreifen. Ansonsten ist sie gnadenlos überschrieben.
Parent und Child haben eigene vTables.
>Können nicht überschriebene Methoden einer Parent-Klasse auf überschriebene Methoden von der Child-Klasse zugreifen?
Jup. Es gilt immer die vTable des Objekts. Und bei einen Child können die Einträge des Parents "überschrieben". Die einzelnen Methoden können ja nicht wählen welche vTable jetzt aktiv ist.
Das geht schon. Angenommen man hat eine Klasse "Base" mit der Methode "test()", die ein "Debug 1" macht, und eine Klasse "Child", die "test()" mit "Debug 2" überschreibt, kann trotzdem "Child" aus jeder seiner Methoden mit "super()\test()" - oder falls weitere Parentklassen bestehen mit "super(Parent)\test()" - die überschriebenen Methoden nutzen. Wenn also innerhalb "Child" eine Methode "test2()" mit Inhalt "super()\test() : test()" existiert und diese von außen ausgerufen wird, werden beide Debug ausgegeben. :)
Bei mir müsste man beim Child mit
AliasMethod(test,test_parent)

die alte Methode sichern. Danach kann sie jederzeit verwendet werden. Finde ich persönlich auch eleganter. Wenn man eine Methode überschreibt, dann meist aus guten Grund.
>Kann man den Standard-Konstruktor bei dir privat schalten, sodass man die Klasse selbst zwar nicht ableiten, aber über andere
>Konstruktoren instantiieren kann?
Geht nicht.
Ich fand das ganz praktisch, wenn man Klassen erstellen will, bei denen es keinen Sinn macht sie mit einem leeren Konstruktur zu initialisieren. Wie zum Beispiel eine BufferedStream-Klasse, die einen Datenstrom zusätzlich im Speicher puffern soll. Dieser muss man im Konstruktor ja einen UnbufferedStream übergeben, damit sie darauf aufsetzen kann.
Ich versteh jetzt kein Wort :) - Was aber bei mir geht ist, das ein Parent keinen Konstruktor hat, das Child aber sehr wohl. Konstruktor, destructor und clone sind besondere Methoden, die werden bei mir nicht überschrieben! Wenn ein Objekt initalisiert wird, werden immer alle Konstruktoren von Parent bis zum letzen Child in genau dieser Reihenfolge aufgerufen.
>Kann man bei dir Methoden schreiben, die automatisch einen Per-Object-Mutex sperren?
Was genau meinst du hier? Also automatisch geht es nicht, aber du kannst natürlich ein "Mutex" in den Properties speichern und dann bei jeder Methode ein LockMutex(*self\mutex) und UnLockMutex(*self\mutex) einfügen. Den Mutex muss man bei der Methode Initalize() erzeugen und mit "Dispose()" zerstören.
Ein Mutex ist bei mir pro Objekt immer gratis dabei. Nutzt man statt dem Schlüsselwort "Method" einfach "Synchronized" und entsprechend auch "SynchronizedReturn" und "EndSynchronized", kann man nie zwei "Synchronized"-Methoden gleichzeitig aufrufen. Hat man daneben noch normale Methoden, können dieser aber trotzdem von mehreren Threads gleichzeitig verarbeitet werden.
Das ganze lehnt auch wieder an das "synchronized"-Schlüsselwort in Java an.
Wäre technisch kein Problem das einzufügen. Nur ist so eine Sache, ich möchte nicht für jedes Objekt einen Mutex automatisch erzeugen, auch wenn er überhaupt nicht gebraucht wird. Wobei mir da gerade was einfällt. Vielleicht komm ich ja morgen dazu.

>Bis auf Punkt 1 geht bei mir zumindest mal alles und ich finde es sehr praktisch. :)
Da hab ich lange rumgefummelt, bis ich das hingekriegt hab.
Ich hab auch immer noch das Problem mit den vTables. Da muss man dann eben immer noch händisch ran. Das ist auch das einzige, wo bei mir bei Erstellen einer Kindklasse noch etwas zur Laufzeit geändert wird. Gibt man in der Kindklasse in der vTable zum Beispiel eine 0 statt des Pointers zur Methode ein, wird nach dem "EndClass" die 0 durch den Pointer aus der Parent-Klasse ersetzt.
Wenn feststeht, dass die Klasse ein Child ist, kopier ich erstmal in die Child-Vtable die parent.
CopyMemory(parent_vtable,child_vtable,SizeOf(parent_interface))

Bei EndClass überprüfe ich, ob die vtable auch komplett ausgefüllt wurde, wenn nicht - weil bspw. eine Methode declariert aber nicht definiert wurde, breche ich das Programm mit einen end ziemlich abrupt ab. Die Überprüfung erfolgt aber nur mit eingeschalteten Debugger.

Die vTable fülle ich übrigens bei der Definition der Methoden. Deshalb müssen bei den obrigen Code auch die Methoden definiert sein, bevor man die Klasse nutzen kann. Bei meiner zweiten Variante hab ich das so gelöst, das in Deklarationsteil mit Gosub in den "Definitions"-Bereich gesprungen wird. (und natürlich, wenn der Programmzeiger zu den Definitionen kommt, wird mittels Goto über alle Definitionen gesprungen, damit nicht versehentlich nochmals versucht wird die vtable aufzubauen).
sieht verkürzt so aus

Code: Alles auswählen

Macro Method(ret,func,p1=,p2=,p3=,p4=,p5=,p6=,p7=,p8=,p9=,p10=) ;EndIndent
 Declare.ret  __currentClass()__Class__#func(*self.__currentClass()__Class__struc)
          PokeI(__currentClass()__Class__functions+OffsetOf(__currentClass()\func ()),@__currentClass()__Class__#func ())
          Procedure.ret  __currentClass()__Class__#func(*self.__currentClass()__Class__struc) ;EndIndent
currentClass() ist ein makro, das gerade den Namen der aktuellen Klassendefinition enthält. __Class__functions ist quasi die vtable und __class__struc ist die Struktur, die den link zur Vtable enthält und auch alle Objekt-Member-Variablen.

Übrigens: Niemand sagt, das in einer vtable nur Zeiger sein dürfen, ich hab da auch ein paar Integer-Daten gespeichert, die ich zur Verwaltung brauche. Den Eintrag sollte man dann aber auch entsprechend nennen, bspw. __SizeOf. Generell hab ich alles interne mit zwei __ beginnen lassen, damit ich mir da selbst nicht in die Quere komme.

Zeig doch mal deinen Code - mich würde interessieren, wie du so manches gelöst hast :)

Re: Module oop und EnableClass

Verfasst: 31.10.2015 11:18
von mk-soft
Wollte auch schon längere Zeit mein OOP-Precompiler mal neu schreiben.
Der ist noch aus der Zeit wo es noch keine Module in PB gab.

Der Trick ist das mein Pre-Compiler mehrere Includes erstellt. Ein Topfile, Interfacefiles für die Klassen und Methoden, sowie ein Bottomfile

Um im Fehlerfall nicht die Zeilen zu verschieben wird das Topfile am Anfang der ersten Zeile einfügt und das Bottomfile in der letzen Zeile
Im Topfile sind alle Includes der Intefacefiles, Macros und Declares. Im Bottomfile die DataSections.

Funktioniert soweit ganz gut.

P.S. Habe mal den Quellcode auf WebSpace gelegt. (../mytools)

Re: Module oop und EnableClass

Verfasst: 31.10.2015 12:29
von GPI
Das mit den Includes ist ein netter Trick - der leider bei Modulen nicht mehr funktioniert. Ein Modul kann leider nur einmal deklariert werden.

Wenn ich deinen Quellcode ansehe: Bei dir muss man das "*self" immer selbst mit angeben, oder?

An einen PreProcessor hab ich auch schon gedacht, leider würde ich da nicht drum herumkommen, den Quellcode zu verändern. Und dann wirds richtig kompliziert mit Fehlermeldungen des Compilers.
Bei mir geht ja folgendes:

Code: Alles auswählen

Procedure Irgendwas()
  Protected_Object(obj,cMeineKlasse)
  [...]
_EndProcedure
Hier wird das Object in der Procedure erzeugt und wie eine lokale Variable gehandhabt. Spricht bei "_EndProcedure" wird das Object zerstört und die entsprechende Methode des Objekts aufgerufen. Das ist auch mein großes Problem. Ein Fehlendes "_EndProcedure" kann ich mittlerweile erkennen. Aber wenn ein ProcedureReturn anstatt _ProcedureReturn verwendet wird, kann man so nicht feststellen.
Mir Macros kann man ja Schlüsselwörter nicht ersetzen.

Aktuell mach ich es auch so, das ich alle Objekte in einer Kette aufreihe (dafür gibts eine interne Member-Variable, die immer auf das nächste Objekt zeigt) und bei _EndProcedure diese dann auflöse.

Bei mir übernimmt AllocateObject(ClassName) den Part wie bei dir die Objekte erzeugt werden.

Re: Module oop und EnableClass

Verfasst: 31.10.2015 13:19
von mk-soft
Ich stelle mir das so vor

Der Parameter "*this" muss wie in "C" immer angegeben werden. Mit Macro Class(...) und EndClass() sind leere Macros und sind nur Schlüsselworte für den Pre-Compiler.

Code: Alles auswählen

Macro Class(ClassName, ClassExtends = 0)
  
EndMacro

Macro EndClass()
  
EndMacro


CLass(MyClass)

  DeclareModule MyClass
    
    Structure sData
      ; Immer an anlegen
      *vTable
      ; Daten
      Value.i
      text.s
    EndStructure
    
    Declare MyFunc1(*this.sData, arg.i)
    Declare MyFunc2(*this.sData, arg.s)
    
  EndDeclareModule
  
  Module MyClass
    
    Procedure.s MyFunc1(*this.sData, arg)
          
      ProcedureReturn Str(arg)
      
    EndProcedure
    
    Procedure.i MyFunc1(*this.sData, arg.s)
          
      ProcedureReturn Val(arg)
      
    EndProcedure
    
  EndModule

EndClass()
  
Das anlegen der Interfaces, DataSections, CreateObject erledigt der PreCompiler.

Re: Module oop und EnableClass

Verfasst: 31.10.2015 14:44
von GPI
Ich meinte das eher so:

Code: Alles auswählen

  DeclareModule TestModul1
    UseModule EnableClass
    
    DeclareClass(cTM1)
      DeclareMethods
        Get()
        Set(v.i)
      EndDeclareMethods
      Properties
        value.i
      EndProperties
    EndDeclareClass 
  EndDeclareModule

  Module TestModul1
    Class(cTM1)
      Method(i,Get)
        MethodReturn *self\value
      EndMethod
      Method(i,Set,v.i)
        *self\value=v
      EndMethod
    EndClass
  EndModule
  
  Procedure Test_Modul1()
    Protected_Object(obj1,TestModul1::cTM1)
    obj1\set(10)
    Test::i(obj1\get(),=,10)
  _EndProcedure
  
  DeclareModule TestModul2
    UseModule EnableClass
    Declare Output()
    Declare Output2()
  EndDeclareModule

  Module TestModul2
    DeclareClass(cTM2, TestModul1::cTM1)
      DeclareMethods
        Get2()
        Set2(v.i)
      EndDeclareMethods
      Properties
        Value2.i
      EndProperties
    EndDeclareClass
    Class(cTM2)
        Method(i,Get2)
          MethodReturn *self\Value2
        EndMethod
        Method(i,Set2,v.i)
          *self\Value2=v
        EndMethod
    EndClass
    
    Global_Object(obj2,cTM2)
    
    Procedure Output()
      obj2\set(11)
      ProcedureReturn obj2\get()
    EndProcedure
    
    Procedure Output2()
      obj2\set2(22)
      ProcedureReturn obj2\get2()
    EndProcedure
  EndModule
  
  DeclareModule TestModul3
    UseModule EnableClass
    Global_Object(obj1,TestModul1::cTM1)
  EndDeclareModule
  Module TestModul3
    obj1\set(33)
  EndModule
Da werden Klassen innerhalb der Module erzeugt. Einmal öffentlich von außen Zugreifbar, einmal nur innerhalb des Modul nutzbar.
Das einzige was du machen könntest, das das Class-Macro bei dir das Interface genau dieser einen Classe included. Dann erzeugt aber dein Precompiler unzählige Subdateien.

Re: Module oop und EnableClass

Verfasst: 31.10.2015 21:56
von GPI
ich bin gerade auf eine interessante Möglichkeit für dich (eventuell auch für mich) gestoßen.

z.b.:

Code: Alles auswählen

DeclareClass(cTM1)
      DeclareMethods
        Get()
        Set(v.i)
      EndDeclareMethods
      Properties
        value.i
      EndProperties
EndDeclareClass 
der Preprozessor erzeugt so eine Top-datei draus:

Code: Alles auswählen

declaremodule oop

macro DeclareClass(classname,parent=)
   oop::IncludeMacroFor#classname()
  
endmacro
macro EndDeclareClass
  
endmacro

macro IncludeMacroForcTM1()
  interface cTM1 extends basisclass
    [dasinterfacezeugs halt]
  endinterface
  structure cTM1_obj extends basisclass_obj
    [die Membervariablen]
  endstructure
enddeclaremodule

module oop
endmodule 
so bräuchte es nur eine Top-Datei, die zwar mit allerhand macros gefüllt sind und die Structuren etc. genau zur richtigen Zeile eingefügt werden. Über Module und ähnliche Scherze muss man sich dann auch keine Gedanken machen.
Eine Datasection kann ja auch mitten in Quellcode sein, das dürfte nicht stören.

Re: Module oop und EnableClass

Verfasst: 01.11.2015 14:37
von mk-soft
Mit den Macros gehen leider die ganzen Autovervollständigungen verloren.

Wir können ja ganz verschiedene Wege gehen.
Einmal eine komplette neu Syntax wie bei Dir und einmal eine Unterstützung
zum anlegen der Basisfunktionen, Interfaces und DataSections.

Bei mir würde das etwa so Aussehen.

Code: Alles auswählen


; ***************************************************************************************

; Standard Include BaseClass.pbi

Macro dq
  "
EndMacro 

Macro BeginClass(ClassName)
  ;XIncludeFile("BaseClass.pbi")
  ;IncludeFile(dq#Interface_#Classname#.pbi#dq)
EndMacro

Macro EndClass(ClassName)
  ;IncludeFile(dq#Data_#Classname#.pbi#dq)
EndMacro

DeclareModule BaseClass
  
  Declare Release(*this)
  Declare AddRef(*this)
  
  Structure sProperties
    *vTable
    ref.i
  EndStructure
  
EndDeclareModule

Module BaseClass
  
  Procedure Release(*this.sProperties)
    With *this
      If \ref = 0
        ; TODO Release
        FreeStructure(*this)
      Else
        \ref - 1
      EndIf
    EndWith
    
  EndProcedure
  
  Procedure AddRef(*this.sProperties)
    *this\ref + 1
    ProcedureReturn *this\ref
  EndProcedure
  
EndModule

; ***************************************************************************************

BeginClass(MyClass1)

DeclareModule MyClass1
  
  Declare.s Info(*this)
  
  Structure sProperties Extends BaseClass::sProperties
    ; Daten
    Info.s
  EndStructure
  
EndDeclareModule
  
Module MyClass1
  
  
  Procedure.s Info(*this.sProperties)
    
    Protected result.s
    
    result = "Hello World"
    ProcedureReturn result
    
  EndProcedure
  
EndModule

EndClass(MyClass1)

; ***************************************************************************************

BeginClass(MyClass2)

DeclareModule MyClass2
  
  Declare.s Get(*this)
  Declare.i Put(*this, arg.i)
  
  Structure sProperties Extends MyClass1::sProperties
    ; Daten
    Value.i
    text.s
  EndStructure
  
EndDeclareModule
  
Module MyClass2
  
  Procedure.s Get(*this.sProperties)
    
    Protected result.s
    
    With *this\
      result = *this\text
       
      ProcedureReturn result
    EndWith
    
  EndProcedure
  
  Procedure.i Put(*this.sProperties, arg.i)
    
    With *this
      \Value = arg
      \text = Str(arg)
    EndWith
    
  EndProcedure
  
EndModule

EndClass(MyClass2)

Re: Module oop und EnableClass

Verfasst: 01.11.2015 18:24
von NicTheQuick
Ich mache das etwas anders mit zwei Strukturen und einem Interface pro Klasse. In etwa so:

Code: Alles auswählen

; Klasse Object

Interface IObject
	free.i()
	toString.s()
EndInterface

Structure Object
	*vTable
	*super.Object_Inherited
	__self__.IObject
EndStructure

Structure Object_Inherited
	Object.Object
EndStructure

; Klasse Auto, von Object geerbt

Interface IAuto Extends IObject
	getPS.d()
EndInterface

Structure Auto
	*vTable
	*super.Auto_Inherited
	__self__.IAuto
EndStructure

Structure Auto_Inherited Extends Object_Inherited
	Auto.Auto
EndStructure
Ein Objekt der Klasse "Auto" wird erstellt, indem intern 'AllocateStructure(Auto_Inherited)" aufgerufen wird. Dann wird zunächst der DefaultConstructor der Basisklasse "Object" aufgerufen. Den DefaultConstructor gibt es in jeder Klasse und er kümmert sich darum die *vTable, *super und __self__ zu befüllen, und zuvor immer den DefaultConstructor der darüber liegenden Klasse aufzurufen. Somit hat jede Klasse ihre eigene vTable. *super ist der Pointer auf die Struktur 'Auto_Inherited'. Somit zeigt @*this\super\Auto auf *this selbst, aber gleichzeitig kann man z.B. mit *this\super\BeliebigerKlassenname\__self__\toString() die toString()-Methode einer beliebigen Basisklasse aufrufen. *this\__self__ hat den selben Wert wie *this, bloß mit einem Interface als Typ, sodass man auf die eigenen Methoden zugreifen kann.
Technisch steckt noch ein bisschen mehr dahinter, aber das werde ich euch noch an einem kompletten Beispiel zeigen. Habe heute noch ein paar Fehler ausgemerzt und ein paar Klassen implementiert, die ich mir von Java abgeguckt habe. :)

Re: Module oop und EnableClass

Verfasst: 01.11.2015 19:18
von GPI
mk-soft hat geschrieben:Mit den Macros gehen leider die ganzen Autovervollständigungen verloren.
Ja leider, das lässt sich leider kaum vermeiden. Ich hab bei mir immer mehr zusätzliche Sachen drin. Mein Focus liegt eindeutig auf eine einfache Definition, wo man möglichst wenig anschließend beachten muss. Irgendwie gibts da keine goldene Lösung.
Wir können ja ganz verschiedene Wege gehen.
Klar logisch. Ich finde es aber gerade sehr spannend, verschiedene Lösungen zu besprechen. Nic hat auch schöne Ideen. Mal schaun, wie ich das umsetzen kann.

Das mit Super ist bei ihm bspw. sehr interessant. Dummerweise gibts bei mir ein Problem: Module. Ein Klasse kann als Parent eine Klasse aus einen anderen Modul haben und beginnt damit immer modulname:: - das kann ich nicht automatisch in einen Modul unterbringen.

Irgendwie machen mir solche Problemlösungen mehr Spaß als ein Programm zu schreiben :)

aktuell denke ich über folgendes nach:

Code: Alles auswählen

Interface MyClass extends oop::object
[...]
Endinterface
InitalizeClass(MyClass,oop::object)
[...]
Class MyClass
  Properties
      [...]
  EndProperties
  Method
  EndMethod
EndClass
So bleibt das Interface für den Editor vorhanden, nur bei den Properties gibt keine Autovervollständigung, das dürfte aber dann das Problem der Methode sein und imo verschmerzbar. Die Membervariablen möchte ich persönlich lieber in der Definition sehen, bei der Declaration haben die eigentlich nichts zu suchen, weil man sie bei mir auch nicht direkt verändern soll...

Re: Module oop und EnableClass

Verfasst: 01.11.2015 19:41
von GPI
mit *this\super\BeliebigerKlassenname\__self__\toString() die toString()-Methode einer beliebigen Basisklasse aufrufen.
Bist du dir da sicher? Das Interface ist ja dumm, welche Procedure aufgerufen wird, bestimmt ja die vTable und die ist immer das erste Element der Datenstructur mit den Variablen.
Außer natürlich du machst noch eine Structur, wo die Daten abgespeichert werden und verlinkst (=Pointer!) die da rein. Dann wirds aber erstmal etwas kompliziert.
Jetzt weis ich aber wieder, warum ich in das Interface nicht in die Structure eingebunden habe - dann funktioniert Extend nicht mehr :)