OOP to complex to use in PB? Try this!

Share your advanced PureBasic knowledge/code with the community.
Dreglor
Enthusiast
Enthusiast
Posts: 759
Joined: Sat Aug 02, 2003 11:22 pm
Location: OR, USA

OOP to complex to use in PB? Try this!

Post by Dreglor »

I made this include to make OOP Easier. This file provides a standardized template to make your objects from it has limits but it gets the job done.

Code: Select all

;////////////////////////////////////////////////////////////////
;//
;// Title: Object Oriented Programming
;// FileName: OOPBase.pbi
;// Purpose: OOP Base
;// Version: 1.0.0.0
;// Date: 7/13/07
;// Notes: Any segment of code requiring OOP
;//
;////////////////////////////////////////////////////////////////

;- Prototypes

Prototype.l Object_Structor(*This.l, Parameter.l = #Null)
Prototype.l Object_NullPointer(*This.l)

;- Structures

Structure ObjectBase_
  ;Base Properties
  MethodPointer.l ;Vector Table Pointer
  
  ;Methods
  Constructor.Object_Structor ;On Creation this is called (Shouldn't be called from host unless auto constuct is false)
  Destructor.Object_Structor ;On Release this is called (Shouldn't be called by the host, this is called by release to properly destroy the object)
  Release.Object_Structor ;Release is what should be called from host
EndStructure

;- Interfaces

Interface ObjectBase
  Constructor(Parameter.l) ;On Creation
  Destructor(Parameter.l = #Null) ;On Release
  Release(Parameter.l = #Null) ;Front End should Call this instead of destructor
EndInterface

;- Macros

Macro SizeOfObject(Object)
  SizeOf(Object#_)
EndMacro

Macro OffsetOfObject(Object, Field)
  OffsetOf(Object#_\Field)
EndMacro

;- Procedures

Procedure DestroyObject(*This.ObjectBase_, DestructParameter.l = #Null) ;One Call to Free them All
  Result = *This\Destructor(*This, DestructParameter) ;should return the pointer to the object for this to free NUll if freed it self
  
  *This\MethodPointer = #Null
  
  *This\Constructor = #Null
  *This\Destructor = #Null
  *This\Release = #Null
  
  If Result <> #Null
    FreeMemory(*This)
  EndIf
  
  ProcedureReturn Result
EndProcedure

Procedure CreateObject(Constructor.Object_NullPointer, Destructor.Object_NullPointer, ConstructParameter.l = #Null, AutoConstruct.c = #True) ;Creates a new Object (Returns Object Interface)
  If Constructor = #Null Or Destructor = #Null
    ProcedureReturn #Null
  EndIf
  
  *New.ObjectBase_ = AllocateMemory(SizeOf(ObjectBase_))
  
  *New\MethodPointer = @*New\Constructor
  *New\Constructor = Constructor
  *New\Destructor = Destructor
  *New\Release = @DestroyObject()
  
  If AutoConstruct = #True
    Result = *New\Constructor(*New, ConstructParameter)
  Else
    Result = *New
  EndIf
  
  ProcedureReturn Result
EndProcedure
How do you use the code you say?
well then here is an example;
lets say you wanted to create a object of a dog

starting out with some simple Properties for the dog such as:
Name
Age
Weight
and some Functions:
Bark
Feed
Play
together they are a object.
now an object is a combination of an interface and a structure (and in fact an interface is very similar to a structure) with some fancy type overloading.
to define a object me must write both it's structured version and it's interface version of the object.
for are dog the structured version of the object looks like this. Note: when I name or refer the structured version of the object I put a underscore after the name and on a interface I do not this is purely my style of coding you can mark them with another method

Code: Select all

Structure Dog_
  ;Methods
  Bark.Object_NullPointer
  Feed.Object_NullPointer ;Feed(*This, Amount)
  Play.Object_NullPointer ;Play(*This, Time)
  
  
  ;Properties
  Name.s
  Weight.f
  Age.f
EndStructure
simple? unfortunately this is far from what a OOP object is.
you still need to blend the interface and structure together but, before i explain how that happens I'm going to show you how the interface of "dog" should be. now sense an interface is just functions it should be rather simple.

Code: Select all

Interface Dog
  Bark()
  Feed(Amount.f)
  Play(HowLong.f)
EndInterface
we all should easily grasp that this is a pretty standard interface, right?
what makes interfaces special is that they have a hidden first parameter. "*this" when a function is called from a interface the first parameter is always the pointer to it's self, handy isn't? Now to "blend" the interface version of dog and structured version of dog we must extend both of them with the included OOPBase structure and interface: ObjectBase_ (structure) and ObjectBase (Interface). These contain fields that house the constructor and destructor and the vector table pointer which makes these definitions into objects. now the object definitions now should look like this, comments aside:

Code: Select all

Structure Dog_ Extends ObjectBase_
  ;ObjectBase_ Resides Here 
  ;{
  ;Base Properties
  ;MethodPointer.l ;Vector Table Pointer
  
  ;Methods
  ;Constructor.Object_Structor ;On Creation this is called (Shouldn't be called from host unless auto constuct is false)
  ;Destructor.Object_Structor ;On Release this is called (Shouldn't be called by the host, this is called by release to properly destroy the object)
  ;Release.Object_Structor ;Release is what should be called from host
  ;}
  ;Methods
  Bark.Object_NullPointer
  Feed.Dog_FloatPointerFloat ;Feed(*This, Amount)
  Play.Dog_FloatPointerFloat ;Play(*This, Time)
  
  
  ;Properties
  Name.s
  Weight.f
  Age.f
EndStructure

;- Interface

Interface Dog Extends ObjectBase
  ;ObjectBase Resides Here
  ;{
  ;Constructor(Parameter.l) ;On Creation
  ;Destructor(Parameter.l = #Null) ;On Release
  ;Release(Parameter.l = #Null) ;Front End should Call this instead of destructor
  ;}
  Bark()
  Feed(Amount.f)
  Play(HowLong.f)
EndInterface
one thing i left unexplained is that in the structured version of the object the methods are defined with prototypes, those prototypes must be defined with the first parameter as "*this.l" to ensure that when you are calling functions from inside the object that it's still being treated as a object instead of a fancy function. There is another way to define the structured version of the methods, instead of naming each one and defining prototypes you can simply use

Code: Select all

Methods.l[SizeOf(Dog) / 4]
but that just inhibits the use of inter-method calls

with are object define now how do we create it?
with it constructors of course

Constructors are relatively easy all you need to do is remember to reallocate the memory, set up your methods, and return the pointer to it's self. to spare you a long explanation of how and why we set are methods up (assuming you know how pointers and prototypes work) are dog constructor will look like this:

Code: Select all

Procedure Dog_Constructor(*This.Dog_, Name.s) 
  ;We must make room for the rest of the object
  *This = ReAllocateMemory(*This, SizeOf(Dog_))
  
  ;lets set up the Functions
  *This\Bark = @Dog_Bark()
  *This\Feed = @Dog_Feed()
  *This\Play = @Dog_Play()
  
  ;Lets Set the starting Properties
  *This\Name = Name
  *This\Weight = 5
  *This\Age = 1
  
  ;If Successful we must return the Object Address to the calling procedure (Create Object)
  ProcedureReturn *This
EndProcedure
because when the constructor is called from a parent base object we must reallocate the memory to fit are object into the same memory this is the same reason we must return the pointer because it is being called from a parent function and if it wasn't the pointer would be used to assign to the
object variable. as a side note object can only be constructed and destructed with one long variable so it's one variable or a structured pointer must be passed this is one of the limits of the this system.

destruction work very similar to construction

Code: Select all

Procedure Dog_Destructor(*This.Dog_, Void = #Null)
  ;this functions is called from Release it's function is to properly destroy any object specific items
  *This\Bark = #Null
  *This\Feed = #Null
  *This\Play = #Null
  
  ;Lets Set the starting Properties
  *This\Name = #Null$
  *This\Weight = #Null
  *This\Age = #Null
  
   ;If Successful we must return the Object Address to the calling procedure (Release)
  ;you can destroy the object by release the memory and return #null but it's ussally safer to let release do it
  ProcedureReturn *This
EndProcedure
now with are constructor defined there only one thing to define the methods

these are very straight forward, th only special thing is that the first parameter is *this.dog_ (structured version of the object)

Code: Select all

Procedure Dog_Play(*This.Dog_, HowLong.f)
  *This\Weight = *This\Weight - (HowLong / 4)
  ;Example of inter-method call
  *This\Bark(*This) ;Must have *This
  ProcedureReturn -(HowLong / 4)
EndProcedure

Procedure Dog_Feed(*This.Dog_, Amount.f)
  *This\Weight = *This\Weight + (Amount / 4)
  ProcedureReturn (Amount / 4)
EndProcedure

Procedure Dog_Bark(*This.Dog_)
  MessageRequester(*This\Name, "Woof!")
EndProcedure
Now we are done defining the object althought this is alot of work for a small object larger objects and the flexibly of the currently defined object is rather easy and once you get the hang of defining object this isn't so hard

so when we want to use the object was call

Code: Select all

*MyDog.Dog = CreateObject(@Dog_Constructor(), @Dog_Destructor(), "Mutt") ;because it's a string we must use the address to pass (PB does this internally but because we are passing through longs we must do this manually)
or I personally like to create a macro that does this with a further simplified call

Code: Select all

Macro CreatePuppy(Name)
  CreateObject(@Dog_Constructor(), @Dog_Destructor(), @Name) ;because it's a string we must use the address to pass (PB does this internally but because we are passing through longs we must do thsi manually)
EndMacro
then using the macro like this

Code: Select all

*MyDog.Dog = CreatePuppy("Mutt")
now you can use it's functions
but you cannot use it's properties unless you make another variable to hold the structured version of the object or create methods to get and set

as a general rule for myself is that I typically use the structured version only with in internal functions to the object and the interface for the external (unless I am defining objects with in object then i defines union with both of them defined in the same location)

now here is that full source of what I was explaining if your more of the Understand-by-source type of person

Code: Select all

;////////////////////////////////////////////////////////////////
;//
;// Title: Dog Object Example
;// FileName: Dog.pbi
;// Purpose: Show how to use OOP
;// Version: 1.0.0.0
;// Date: 7/13/07
;// Notes: Woof
;//
;////////////////////////////////////////////////////////////////

;- Includes

XIncludeFile "OOPBase.pbi"

;- Constants
;for the sake of the example the puppy will always start with these values
#Dog_Begin_Wieght = 5.0
#Dog_Begin_Age = 1.0

;- Prototypes
; my format for prototype names for objects are Object_ReturnsPointerArgumentTypes
Prototype.f Dog_FloatPointerFloat(*This.l, Float.f)

;- Structures

Structure Dog_ Extends ObjectBase_
  ;ObjectBase_ Resides Here 
  ;{
  ;Base Properties
  ;MethodPointer.l ;Vector Table Pointer
  
  ;Methods
  ;Constructor.Object_Structor ;On Creation this is called (Shouldn't be called from host unless auto constuct is false)
  ;Destructor.Object_Structor ;On Release this is called (Shouldn't be called by the host, this is called by release to properly destroy the object)
  ;Release.Object_Structor ;Release is what should be called from host
  ;}
  ;Methods
  Bark.Object_NullPointer
  Feed.Dog_FloatPointerFloat ;Feed(*This, Amount)
  Play.Dog_FloatPointerFloat ;Play(*This, Time)
  
  ;Properties
  Name.s
  Weight.f
  Age.f
EndStructure

;- Interface

Interface Dog Extends ObjectBase
  ;ObjectBase Resides Here
  ;{
  ;Constructor(Parameter.l) ;On Creation
  ;Destructor(Parameter.l = #Null) ;On Release
  ;Release(Parameter.l = #Null) ;Front End should Call this instead of destructor
  ;}
  Bark()
  Feed(Amount.f)
  Play(HowLong.f)
EndInterface

;- Macro

Macro CreatePuppy(Name)
  CreateObject(@Dog_Constructor(), @Dog_Destructor(), @Name) ;because it's a string we must use the address to pass (PB does this internally but because we are passing through longs we must do thsi manually)
EndMacro

;- Procedures

Procedure Dog_Play(*This_.Dog_, HowLong.f)
  *This\Weight = *This\Weight - (HowLong \ 4)
  ;Example of inter-method call
  *This\Bark(*This) ;Must have *This
  ProcedureReturn -(HowLong \ 4)
EndProcedure

Procedure Dog_Feed(*This.Dog_, Amount.f)
  *This\Weight = *This\Weight + (Amount \ 4)
  ProcedureReturn (Amount \ 4)
EndProcedure

Procedure Dog_Bark(*This.Dog_)
  MessageRequester(*This\Name, "Woof!")
EndProcedure

Procedure Dog_Constructor(*This.Dog_, Name.s) ;Called From the CreatePuppy macro and ultimatly the create object procedure
  ;This Function is called to set the object up with all of it's functions and variables other wise the object is unusable
  ;We must make room for the rest of the object
  *This = ReAllocateMemory(*This, SizeOf(Dog_))
  
  ;lets set up the Functions
  *This\Bark = @Dog_Bark()
  *This\Feed = @Dog_Feed()
  *This\Play = @Dog_Play()
  
  ;Lets Set the starting Properties
  *This\Name = Name
  *This\Weight = #Dog_Begin_Wieght
  *This\Age = #Dog_Begin_Age
  
  ;If Successful we must return the Object Address to the calling procedure (Create Object)
  ProcedureReturn *This
EndProcedure

Procedure Dog_Destructor(*This.Dog_, Void = #Null)
  ;this functions is called from Release it's function is to properly destroy any object specific items
  *This\Bark = #Null
  *This\Feed = #Null
  *This\Play = #Null
  
  ;Lets Set the starting Properties
  *This\Name = #Null$
  *This\Weight = #Null
  *This\Age = #Null
  
   ;If Successful we must return the Object Address to the calling procedure (Release)
  ;you can destroy the object by release the memory and return #null but it's ussally safer to let release do it
  ProcedureReturn *This
EndProcedure
one note about it's limitations is that you cannot extend or inherit objects per se because how the system works the only way is to incorporate the objects by hand but, it's a start and it works for me rather well.
~Dreglor
Kale
PureBasic Expert
PureBasic Expert
Posts: 3000
Joined: Fri Apr 25, 2003 6:03 pm
Location: Lincoln, UK
Contact:

Post by Kale »

Complicated stuff. :wink:

Here's another thread with some simpler ideas:
http://www.purebasic.fr/english/viewtopic.php?t=19416

Nice code though.
--Kale

Image
Dreglor
Enthusiast
Enthusiast
Posts: 759
Joined: Sat Aug 02, 2003 11:22 pm
Location: OR, USA

Post by Dreglor »

complex it may be but it is easier to read and more flexible
~Dreglor
Dare
Addict
Addict
Posts: 1965
Joined: Mon May 29, 2006 1:01 am
Location: Outback

Post by Dare »

Interesting read.

Thanks Dreglor, much appreciated.

(Anything that helps this noggin of mine get a bit more clued up on PB and OOP is a bonus)
Dare2 cut down to size
Post Reply