OOP to complex to use in PB? Try this!
Posted: Sat Jul 14, 2007 12:23 am
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.
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
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.
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:
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
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:
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
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)
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
or I personally like to create a macro that does this with a further simplified call
then using the macro like this
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
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.
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
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
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
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
Code: Select all
Methods.l[SizeOf(Dog) / 4]
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
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
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
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)
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
Code: Select all
*MyDog.Dog = CreatePuppy("Mutt")
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