Page 1 of 2

Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 2:56 pm
by srod
Hi,

similar to mk-soft's great 'Module Base Class' macro-based utility, I have another offering which does a similar job, but in a slightly different way.

mk-soft's utility offers up interface-based 'classes', each of which really need to be embedded within their own Purebasic modules, whilst I required a similar utility which can readily create simple 'classes' both inside and outside of PB modules. Also, my little utility does not store individual class info in a Map() or some such. All done with macros.

My aim was to make creating interface based classes a little more concise and simpler than working directly with Interfaces, class structures and Vtables and the like. Makes for slightly more readable code inmo.

LilClass offers the following :
  • Very simple syntax
  • Simple inheritance
  • Method over-riding
  • Class constructor and destructor
  • No need to deal with vTables.
Similar to mk-soft's offering, these classes are interface based (not prototyped functions) and can thus be used for COM if desired, although our base class would need to extend the iUknown interface for this and our base class vTable modified accordingly, but that is a simple 10-second modification to our little utility! (Creating a COM component is NOT a 10 second job mind, with or without a utility like this!) :)

Regards.

8th Feb 2020.
Added an OverrideClassMethodForIndividualObject() macro which would only be useful for certain esoteric applications (such as my own!)

This can be used on an individually instantiated object to override the given method without altering the underlying class or other objects of the same class. That is, all other class objects will still use the original class method (unless otherwise over-ridden themselves!)
Once this has been used on an individual object, any changes to the underlying class methods will not be reflected in the given object and so this should only be used after all class methods have been defined.

By way of an example, imagine two objects of the same class. Both will, at the outset at least, expose the same class methods. If we use this new macro on one of the objects to alter one of the class methods, say a \GetWidth() method for example, then the two objects will still expose the same class methods, but one of these objects will use the new \GetWidth() method implementation whilst the other will defer to the original \GetWidth(). All new objects of this same class will continue to use the original \GetWidth().
This is of course in addition to the ability to switch all class methods for an individual object when creating the object (by substituting an alternative vTable pointer).

I admit this is a somewhat 'specialised' facility and most people would not have need for it - only complete nutters like myself and fangbeast!


7th Feb 2020.
Bugs fixed.
Added an alternative way of creating a class object. For example, suppose you have defined a 'Box' class then you can simply use NewBox() to instantiate a class object of this type. This does have a couple of limitations mind (due to limitations with PB's macros) in that it can fail if you try to use it without defining any class methods for example. If you lay your class definitions out logically (see the demos) then the compiler shouldn't complain. If it does then add the class name to EndClassProperties(). That is use EndClassProperties(Box) instead. If this doesn't stop PB complaining then simply use NewClassObject(Box) together with InitClassObject() instead of NewBox().


LilClass.pbi

Code: Select all

CompilerIf Defined(INCLUDE_LilClass, #PB_Constant)=0
#INCLUDE_LilClass=1

;/////////////////////////////////////////////////////////////////////////////////
;***LilClass***
;
;Created by srod - 2020.
;Platforms : ALL.
;/////////////////////////////////////////////////////////////////////////////////

#LILCLASS_NUMMETHODSPERBASECLASS = 2

;/////////////////////////////////////////////////////////////////////////////////
;-BASE CLASS.

  Interface LilClass_BaseClass ;Extends iUnknown
    InitObject()
    DestroyObject()
  EndInterface
  Structure ClassTemplate_LilClass_BaseClass
    *LilClass_vTable
    Array _LilClass_vTable.i(0)
    LilClass_blnIndividualVtablePtr.i
  EndStructure

;Dummy constructor/destructor.
  Procedure LilClass_BaseMethod_InitDestroy(*this)
  EndProcedure
  
Global Dim LilClass_VT_LilClass_BaseClass.i(#LILCLASS_NUMMETHODSPERBASECLASS-1)
LilClass_VT_LilClass_BaseClass(0) = @LilClass_BaseMethod_InitDestroy() ;If extending iUnknown then change index to 3.
LilClass_VT_LilClass_BaseClass(1) = @LilClass_BaseMethod_InitDestroy() ;If extending iUnknown then change index to 4.
;/////////////////////////////////////////////////////////////////////////////////

;/////////////////////////////////////////////////////////////////////////////////
;-CLASS DEFINITION MACROS.

;The following allows us to create an appropriate NEW() procedure for the underlying class.
Macro _LilClass_Internal_CreateNew(ClassName)
  CompilerIf Not(Defined(_LilClass_LABEL_New#ClassName, #PB_Constant))
    #_LilClass_LABEL_New#ClassName = 1
    Procedure.i New#ClassName(vTable=0)
      Protected *this.ClassTemplate_#ClassName, this.LilClass_BaseClass
      *this = AllocateStructure(ClassTemplate_#ClassName)
      If *this
        *this\LilClass_vTable = @LilClass_VT_#ClassName()
        If vTable
          *this\LilClass_vTable = vTable
        EndIf
        this = *this
        this\InitObject()
      EndIf
      ProcedureReturn *this
    EndProcedure
  CompilerEndIf
EndMacro

Macro Class(ClassName, ExtendedClassName=LilClass_BaseClass)
  Declare.i New#ClassName(vTable=0)
  ;Create a global array to hold the virtual table of method pointers.
    Global Dim LilClass_VT_#ClassName.i(#LILCLASS_NUMMETHODSPERBASECLASS-1)  ;Make the original class methods (those already defined at least) available to the extended class.
  Interface ClassName Extends ExtendedClassName
EndMacro

Macro EndClass(ClassName=)
  EndInterface 
EndMacro

Macro ClassProperties(ClassName, ExtendedClassName=LilClass_BaseClass)
  CopyArray(LilClass_VT_#ExtendedClassName(), LilClass_VT_#ClassName())
  ReDim LilClass_VT_#ClassName(SizeOf(ClassName)/SizeOf(Integer)-1)
  Structure ClassTemplate_#ClassName Extends ClassTemplate_#ExtendedClassName
EndMacro

Macro EndClassProperties(ClassName=LilClass_BaseClass)
  EndStructure
  ;Create an object-creation function.
    _LilClass_Internal_CreateNew(ClassName)
EndMacro
;/////////////////////////////////////////////////////////////////////////////////

;/////////////////////////////////////////////////////////////////////////////////
;-CLASS METHOD MACROS.

;To use the following, place after a class method's EndProcedure and the procedure must be named as in the following example :
;Procedure ClassMethod_ClassName_MethodName() where 'MethodName' is the name of the method as listed in the class definition.
Macro AsClassMethod(ClassName, MethodName)
  :  LilClass_VT_#ClassName(OffsetOf(ClassName\MethodName())/SizeOf(INTEGER)) = @ClassMethod_#ClassName#_#MethodName()
  ;Create an object-creation function.
    _LilClass_Internal_CreateNew(ClassName)
EndMacro

;With the following alternative to the above macro, we can name the procedure anything we wish.
;Also, do not place a call to the following macro after EndProcedure. Place it on its own line instead.
Macro AddClassMethod(ClassName, MethodName, address)
  LilClass_VT_#ClassName(OffsetOf(ClassName\MethodName())/SizeOf(INTEGER)) = address
  ;Create an object-creation function.
    _LilClass_Internal_CreateNew(ClassName)
EndMacro

;The following copies base class method pointers to an extended class. Use if the extended class template was defined before the base class methods.
;Cannot use CopyArray() as this will resize the destination array.
Macro InheritBaseClassMethods(ClassName, ExtendedClassName)
  Define.i LilClass_ivxcxcY
  For LilClass_ivxcxcY = 0 To ArraySize(LilClass_VT_#ExtendedClassName())
    LilClass_VT_#ClassName(LilClass_ivxcxcY) = LilClass_VT_#ExtendedClassName(LilClass_ivxcxcY)
  Next
  ;Create an object-creation function.
    _lilClass_Internal_CreateNew(ClassName)
EndMacro

;The following allows an instantiated object to override the given method without altering the underlying class. 
;That is, all other class objects will still use the original class method.
;Once this has been used, any changes to the underlying class methods will not be reflected in the given object and so this should only be used
;after all class methods have been defined.
Macro OverrideClassMethodForIndividualObject(VarName, ClassName, MethodName, address)
  Define.ClassTemplate_#ClassName *LilClass_PtrClassTemplate
  *LilClass_PtrClassTemplate = VarName
  If *LilClass_PtrClassTemplate\LilClass_blnIndividualVtablePtr = 0
    ReDim *LilClass_PtrClassTemplate\_LilClass_vTable(SizeOf(ClassName)/SizeOf(Integer)-1)
    CopyMemory(*LilClass_PtrClassTemplate\LilClass_vTable, @*LilClass_PtrClassTemplate\_LilClass_vTable(), SizeOf(ClassName))
    *LilClass_PtrClassTemplate\LilClass_vTable = @*LilClass_PtrClassTemplate\_LilClass_vTable()
    *LilClass_PtrClassTemplate\LilClass_blnIndividualVtablePtr = 1
  EndIf
  *LilClass_PtrClassTemplate\_LilClass_vTable(OffsetOf(ClassName\MethodName())/SizeOf(INTEGER)) = address
EndMacro
;/////////////////////////////////////////////////////////////////////////////////

;/////////////////////////////////////////////////////////////////////////////////
;-CLASS CREATION/INITIALISATION MACROS.

Macro NewClassObject(ClassName)
  AllocateStructure(ClassTemplate_#ClassName)
EndMacro

;The following initialises the given class variable by setting the virtual table to map to the default class table.
;It also calls the class constructor.
Macro InitClassObject(VarName, ClassName)
  Define.INTEGER *ptr#ClassName
  Define.LilClass_BaseClass vvvLilClass_#ClassName
  *ptr#ClassName = VarName
  *ptr#ClassName\i = @LilClass_VT_#ClassName()
  ;Call any optional class constructor.
    vvvLilClass_#ClassName = VarName
    vvvLilClass_#ClassName\InitObject()
EndMacro

;The following initialises the given class variable by setting the virtual table to map to the given address.
;This allows an individual class object to share the class template, but to utilise different class methods.
;It also calls the class constructor.
Macro InitClassObjectEx(VarName, vTableAddress)
  Define.INTEGER *ptrVVxv32_
  Define.LilClass_BaseClass vvvLilClassVVxv32_
    *ptrVVxv32_ = VarName
    *ptrVVxv32_\i = vTableAddress
    ;Call any optional class constructor.
      vvvLilClassVVxv32_ = VarName
      vvvLilClassVVxv32_\InitObject()
EndMacro
;/////////////////////////////////////////////////////////////////////////////////

;/////////////////////////////////////////////////////////////////////////////////
;-CLASS DESTRUCTION MACRO.

Macro DestroyClassObject(VarName)
  ;Call any optional class destructor.
    Define.LilClass_BaseClass vvvLilClassVVxv32
    vvvLilClassVVxv32 = VarName
    vvvLilClassVVxv32\DestroyObject()
  FreeStructure(VarName)
  VarName = 0
EndMacro
;/////////////////////////////////////////////////////////////////////////////////

CompilerEndIf
Examples to follow.

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 2:59 pm
by srod
Simple demo - single class.

Code: Select all

;/////////////////////////////////////////////////////////////////////////////////
;***LilClass***
;
;Demo program.
;
;A simple demo which creates a simple class. Includes simple constructor and destructor methods (which are optional).
;/////////////////////////////////////////////////////////////////////////////////

XIncludeFile "LilClass.pbi"

;*********************************************************************************
;Class definition.  We define a simple 'Box' class with a single method and two class properties.
;=================
  Class(Box)
    GetArea.i()
  EndClass()
  
  ClassProperties(Box)
    width.i
    height.i
  EndClassProperties()

;Class methods. Can list them in any order.
;==============
;NOTE that because we use the AsClassMethod() macro to declare our procedures as class methods, we have to name each procedure very precisely.
;This can be avoided by using the AddClassMethod() macro instead. AsClassMethod() is more convenient however. 

;Constructor (optional). We simply over-ride the \InitObject() method inherited from the base class.
Procedure ClassMethod_Box_InitObject(*this.ClassTemplate_Box)
  Debug "Optional constructor called!"
EndProcedure AsClassMethod(Box, InitObject)

;Destructor (optional).  We simply over-ride the \DestroyObject() method inherited from the base class.
Procedure ClassMethod_Box_DestroyObject(*this.ClassTemplate_Box)
  Debug "Optional destructor called!"
EndProcedure AsClassMethod(Box, DestroyObject)

;\GetArea() method.
Procedure.i ClassMethod_Box_GetArea(*this.ClassTemplate_Box)
  ProcedureReturn *this\width * *this\height
EndProcedure AsClassMethod(Box, GetArea)
;*********************************************************************************

;*********************************************************************************
;Test the class out.
;===================

myBox.Box = NewBox() ;Takes an optional parameter of an optional vTable pointer. 
;This allows an individual class object to share the class template, but to utilise different class methods.

If myBox
  ;Set the height and width of the box directly. We haven't bothered declaring methods for this!
    *myBox.ClassTemplate_Box = myBox
    *myBox\height = 5
    *myBox\width = 10
  ;Call the \GetArea() method.
    Debug "Area = " + Str(myBox\GetArea())
  ;Destroy our object.
    DestroyClassObject(myBox)
EndIf
;*********************************************************************************

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 3:03 pm
by srod
Simple inheritance demo.

Code: Select all

;/////////////////////////////////////////////////////////////////////////////////
;***LilClass***
;
;Demo program.
;
;A demo showing simple inheritance between two classes. Includes method over-riding.
;/////////////////////////////////////////////////////////////////////////////////

XIncludeFile "LilClass.pbi"

;*********************************************************************************
;Class definitions.  
;=================
  Class(Car)
    GetRegYear.i()
    SetRegYear(year)
  EndClass()

  Class(Porsche, Car)  ;Inherits from the 'Car' class.
    GetIsAClassic.i()
    SetIsAClassic(value)
  EndClass() 
;*********************************************************************************

;*********************************************************************************
;-Car class implementation.

  ClassProperties(Car)
    color.i
    regYear.i
  EndClassProperties()

;Destructor (optional).  We simply over-ride the inherited \DestroyObject() method.
Procedure ClassMethod_Car_DestroyObject(*this.ClassTemplate_Car)
  Debug "Car optional destructor called!"
EndProcedure AsClassMethod(Car, DestroyObject)

;GetRegYear() method.
Procedure.i ClassMethod_Car_GetRegYear(*this.ClassTemplate_Car)
  ProcedureReturn *this\regYear
EndProcedure AsClassMethod(Car, GetRegYear)

;SetRegYear() method.
Procedure ClassMethod_Car_SetRegYear(*this.ClassTemplate_Car, regYear)
  *this\regYear = regYear
EndProcedure AsClassMethod(Car, SetRegYear)
;*********************************************************************************

;*********************************************************************************
;-Porsche class implementation.

  ;The following class properties 'inherit' from the 'Car' class properties, that is to say that it extends the list of properties.
  ;In this way, a 'Porsche' object is also a 'Car' object which is why/how a Porsche object can call the 'Car' class methods.
    ClassProperties(Porsche, Car) 
      numDoors.i
      isAClassic.i
    EndClassProperties()

;Destructor (optional).  We simply over-ride the inherited \DestroyObject() method.
;This over-rides our own base class (car) destructor.
Procedure ClassMethod_Porsche_DestroyObject(*this.ClassTemplate_Porsche)
  Debug "Porsche optional destructor called!"
  ;Let us call the base class destructor manually. We can do this because the *this parameter is, on top of being a 'Porsche' class template instance,
  ;also a 'Car' class template instance variable.
    ClassMethod_Car_DestroyObject(*this)
EndProcedure AsClassMethod(Porsche, DestroyObject)

;GetIsAClassic() method.
Procedure.i ClassMethod_Porsche_GetIsAClassic(*this.ClassTemplate_Porsche)
  ProcedureReturn *this\isAClassic
EndProcedure AsClassMethod(Porsche, GetIsAClassic)

;SetIsAClassic() method.
Procedure ClassMethod_Porsche_SetIsAClassic(*this.ClassTemplate_Porsche, value)
  *this\isAClassic = value
EndProcedure AsClassMethod(Porsche, SetIsAClassic)
;*********************************************************************************

;*********************************************************************************
;Test the classes out.
;===================

myPorsche.Porsche = NewPorsche() ;Instead of NewClassObject(Porsche).
If myPorsche
  ;Use the base class methods to set and get the year of registration.
    myPorsche\SetRegYear(2006)
    Debug "Reg year (from base class) = " + Str(myPorsche\GetRegYear())
  ;Use the extended class methods to set and get the 'IsAClassic' property.
    myPorsche\SetIsAClassic(#True)
    Debug "'IsAClassic' (from extended class) = " + Str(myPorsche\GetIsAClassic())
  ;Access the 'color' field from the base class. We access this directly.
    *myPorsche.ClassTemplate_Porsche = myPorsche
    *myPorsche\color = $FF
    Debug "Color (public field from base class) = " + Str(*myPorsche\color)
  ;Destroy our object.
    DestroyClassObject(myPorsche)
EndIf
;*********************************************************************************

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 3:06 pm
by srod
Demo - embed one class object inside another.

Code: Select all

;/////////////////////////////////////////////////////////////////////////////////
;***LilClass***
;
;Demo program.
;
;A demo showing one class object embedded within another.
;/////////////////////////////////////////////////////////////////////////////////

XIncludeFile "LilClass.pbi"


;*********************************************************************************
;Class definitions.  
;=================
  Class(Color)
    GetColor.i()
    SetColor(color)
  EndClass()

  ;The following class will embed a 'Color' class object within each instance.
    Class(Box)
      GetColor.i()
      GetWidth.i()
      SetWidth(width)
    EndClass()
;*********************************************************************************

;*********************************************************************************
;-Color class implementation.
  ClassProperties(Color)
    color.i
  EndClassProperties()

;Constructor (optional). We simply over-ride the \InitObject() method inherited from the base class.
;Here we simply set a default color.
Procedure ClassMethod_Color_InitObject(*this.ClassTemplate_Color)
  *this\color = $FF
EndProcedure AsClassMethod(Color, InitObject)

;GetColor() method.
Procedure.i ClassMethod_Color_GetColor(*this.ClassTemplate_Color)
  ProcedureReturn *this\color
EndProcedure AsClassMethod(Color, GetColor)

;SetColor() method.
Procedure ClassMethod_Color_SetColor(*this.ClassTemplate_Color, color)
  *this\color = color
EndProcedure AsClassMethod(Color, SetColor)
;*********************************************************************************

;*********************************************************************************
;-Box class implementation.
  ClassProperties(Box)
    width.i
    ;Embed a 'Color' object.
      boxColor.Color
  EndClassProperties()

;Constructor (optional). We simply over-ride the \InitObject() method inherited from the base class.
;Here we instantiate the boxColor field.
Procedure ClassMethod_Box_InitObject(*this.ClassTemplate_Box)
  *this\boxColor = NewColor() ;Instead of NewClassObject(Color).
EndProcedure AsClassMethod(Box, InitObject)

;Destructor (optional).  We simply over-ride the \DestroyObject() method inherited from the base class.
;Here we destroy the boxColor field.
Procedure ClassMethod_Box_DestroyObject(*this.ClassTemplate_Box)
  If *this\boxColor
    ;Destroy our embedded color object.
      DestroyClassObject(*this\boxColor)
  EndIf
EndProcedure AsClassMethod(Box, DestroyObject)

;GetColor() method.
Procedure.i ClassMethod_Box_GetColor(*this.ClassTemplate_Box)
  Protected result
  ;We have to query the embedded 'Color' object.
  If *this\boxColor
    result = *this\boxColor\GetColor()
  EndIf
  ProcedureReturn result
EndProcedure AsClassMethod(Box, GetColor)

;GetWidth() method.
Procedure.i ClassMethod_Box_GetWidth(*this.ClassTemplate_Box)
  ProcedureReturn *this\width
EndProcedure AsClassMethod(Box, GetWidth)

;SetWidth() method.
Procedure ClassMethod_Box_SetWidth(*this.ClassTemplate_Box, width)
  *this\width = width
EndProcedure AsClassMethod(Box, SetWidth)
;*********************************************************************************

;*********************************************************************************
;Test the classes out.
;===================

myBox.Box = NewBox() ;Instead of NewClassObject(Box).
If myBox
  ;We simply output the color stored by the embedded color object.
    Debug "Color of embedded 'Color' object = " + Str(myBox\GetColor())
  ;Destroy our object.
    DestroyClassObject(myBox)
EndIf
;*********************************************************************************

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 3:29 pm
by infratec
Hi,

can you provide a COM example? (Please)
I think that would be my only reason for using this stuff.

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 3:43 pm
by srod
Well, you wouldn't really use this (or similar utilities) for accessing a COM server because the required interfaces would already have been defined by whoever created the component in question. Some component methods would require you creating a separate interface class instance however (for certain parameters) which is where this could help.

No you could use something like this if creating a COM server yourself in which case you would be dealing with class-factories and the like which is not a trivial matter. You would need to create a bunch of interface classes inheriting from iUnknown which this utility could make a little simpler.

In short, a quick example of using this utility to create a COM class is not viable. Beside's, mk-soft has created a great looking framework for creating COM classes which uses his Module-Base-Class utility at its heart. I haven't tested it myself because I have no need to create a COM server. I think it creates iDispatch automation servers, but I am not 100% sure about that.

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 3:52 pm
by Rinzwind
but that is a simple 10-second modification!
Bytes in butt

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 3:55 pm
by srod
Rinzwind wrote:
but that is a simple 10-second modification!
Bytes in butt
:?:

I never said that creating a COM component is a 10-second task if that is what you are getting at? I only said that making the modifications to this utility to make it 'COM compatible' is a 10-second job.

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 5:38 pm
by mk-soft
@srod,
nice project :wink:

Because IUnknown

With Purebasic you can realize simple COM interfaces. IUnknown and IDispatch.

Some COM objects want to have a callback as object. For the callback object there is then an interface description of the required methods which always starts IUnknown.
This is easy to realize with the module BaseClass, because the interface IUnknown already exists and the standard query 'QuerInterface - IUnknown' is already programmed.

With the interface 'IDispatch' it looks quite different.
There is much more to program.

With the module BaseClass Dispatch I realized the minimum requirement for the interface 'IDispatch'.
Some 'callers' try to use the interface 'IDispatchEx'. If the interface does not exist, the callers automatically call the interface 'IDispatch'.

Show my signature

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 6:08 pm
by srod
Yes I have occasionally needed to create IUnknown and IDispatch interface implementations for various projects and, as you say, IDispatch can be a pain in the a*se! Creating ActiveX event sinks comes to mind! :)

If I ever need to create an IDispatch automation server then I would undoubtedly use your Module-Base-Class Dispatch framework since I think you have implemented a basic class factory etc. which would save a bit of work.

No I created this little utility as I always make use of basic class interfaces (where possible) in my projects and seeing how you had streamlined your interface use with your Module-Base-Class utility, I decided to do likewise and tidy things up. I would have used your Module-Base-Class if it wasn't so tied to PB Modules which I am not a great fan of. There is one massive include I have which will not fit conveniently into a module which required that I create my LilClass utility instead of using Module-Base-Class. Have already put it to good use and it is making things a lot tidier! :wink:

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 7:07 pm
by mk-soft
Creating ActiveX or OCX with Purebasic is a disaster and much too complex. For simple COM object DLL with Dispatch no problem.

Even with .Net to create an OCX is with effort to do.
Had to convert a DLL into an OCX. This was done with VB6 only a few lines.

If you are interested in how to use the interface IUnknown correctly, you can have a look at my Module ActiveScript and also how to create an Object NamedItem.

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 7:07 pm
by mk-soft
To your LilClass.pbi

If you always follow the rule to call ClassProperty - EndClassProperty, you can also move the DIM for the vTable there. So the vTable has a suitable large one.

Code: Select all

;-CLASS DEFINITION MACROS.

Macro Class(ClassName, ExtendedClassName=LilClass_BaseClass)
  Interface ClassName Extends ExtendedClassName
EndMacro

Macro EndClass()
  EndInterface  
EndMacro

Macro ClassProperties(ClassName, ExtendedClassName=LilClass_BaseClass)
  ;Create a global array to hold the virtual table of method pointers.
    Global Dim LilClass_VT_#ClassName.i(SizeOf(ClassName)/SizeOf(Integer))
  
  ;Make the original class methods (those already defined at least) available to the extended class.
    CopyArray(LilClass_VT_#ExtendedClassName(), LilClass_VT_#ClassName())
  Structure ClassTemplate_#ClassName Extends ClassTemplate_#ExtendedClassName
EndMacro

Macro EndClassProperties()
  EndStructure
EndMacro

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 7:27 pm
by srod
Nice idea, but the CopyArray() alters the size of the destination array anyhow and so we need to use ReDim instead.

The following works :

Code: Select all

#LILCLASS_MAXNUMMETHODSPERCLASS = 1  ;This should be enough. Increase if some class or other may define more than this number of methods.

;/////////////////////////////////////////////////////////////////////////////////
;-BASE CLASS.

  Interface LilClass_BaseClass ;Extends iUnknown
    InitObject(*this)
    DestroyObject(*this)
  EndInterface
  Structure ClassTemplate_LilClass_BaseClass
    *vTable
  EndStructure
  
;Dummy constructor/destructor.
  Procedure LilClass_BaseMethod_InitDestroy(*this)
  EndProcedure
  
Global Dim LilClass_VT_LilClass_BaseClass.i(#LILCLASS_MAXNUMMETHODSPERCLASS)
LilClass_VT_LilClass_BaseClass(0) = @LilClass_BaseMethod_InitDestroy() ;If extending iUnknown then change index to 3.
LilClass_VT_LilClass_BaseClass(1) = @LilClass_BaseMethod_InitDestroy() ;If extending iUnknown then change index to 4.
;/////////////////////////////////////////////////////////////////////////////////

;/////////////////////////////////////////////////////////////////////////////////
;-CLASS DEFINITION MACROS.

Macro Class(ClassName, ExtendedClassName=LilClass_BaseClass)
  ;Create a global array to hold the virtual table of method pointers.
    Global Dim LilClass_VT_#ClassName.i(#LILCLASS_MAXNUMMETHODSPERCLASS)  ;Make the original class methods (those already defined at least) available to the extended class.
  Interface ClassName Extends ExtendedClassName
EndMacro

Macro EndClass()
  EndInterface  
EndMacro

Macro ClassProperties(ClassName, ExtendedClassName=LilClass_BaseClass)
    CopyArray(LilClass_VT_#ExtendedClassName(), LilClass_VT_#ClassName())
    ReDim LilClass_VT_#ClassName(SizeOf(ClassName)/SizeOf(Integer)-1)
  Structure ClassTemplate_#ClassName Extends ClassTemplate_#ExtendedClassName
EndMacro

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 8:19 pm
by mk-soft
Right, you're right. :(

I also do a ReDim at my module :wink:

Re: Yet another basic 'OOP Class' utility!

Posted: Thu Feb 06, 2020 8:45 pm
by srod
mk-soft wrote:Creating ActiveX or OCX with Purebasic is a disaster and much too complex. For simple COM object DLL with Dispatch no problem.

Even with .Net to create an OCX is with effort to do.
Had to convert a DLL into an OCX. This was done with VB6 only a few lines.

If you are interested in how to use the interface IUnknown correctly, you can have a look at my Module ActiveScript and also how to create an Object NamedItem.
Yes I have never attempted to create an ActiveX object with PB (or any other language for that matter). I have created event sinks for ActiveX though which are just relatively simple iDispatch objects.

No worries with IUnknown. Have created lots of those. :wink: