Page 2 of 3

Posted: Sat Jun 05, 2004 2:03 pm
by Derlidio
Thank you guys for your quick answers :)

@GedB: I was aware of your Stack example already (by the way, another well commented sample code). I'll try the link you suggest, and I hope my really poor knowledge on ASM & C be enough to allow me to understand all concepts.

@aXend: I saw some posts about Interface Generator and I alaready download it :wink: but for now my major interest concerns on creating my own objects in the way GedB has shown. I'm working on an app that must run under Windows & Linux, and to do that I'm bypassing OS API. I've tested GedB concept under both systems (with PB 3.81) successfully, so I'm prepared to throw away part of the code I've made so far for that app and rewrite it using PB Interfaces.

Best wishes...

Posted: Sat Jun 05, 2004 3:59 pm
by aXend
This is fun, thanks GedB :D
I fooled around a bit with your example and produced the following code. To me it seems that OOP becomes possible with PureBasic.

Code: Select all

; Test with Interface and OOP in Purebasic
; An elaboration on the example of GedB
; I added put and get methods

#CRLF = Chr(13)+Chr(10)

; The interface to the object
Interface IPBFriendly
  get_Name.s()
  put_Name(a.s)
  get_Data.l()
  put_Data(a)
  SayHello() 
EndInterface 

Structure PersonalData      ; a structure with personal data
  Age.l
  SSN.l
EndStructure

; Definition of the object (is it a Class?)
Structure Class_PBFriendly
  *vTable.l                 ; vTable to the methods
  Name.s                    ; Name property
  *pData.PersonalData       ; pData property, pointer to personal data
EndStructure

; Method to get Name property
Procedure.s get_Name(*Self.Class_PBFriendly)
  ProcedureReturn *Self\Name
EndProcedure

; Method to set Name property
Procedure put_Name(*Self.Class_PBFriendly, Name.s)
  *Self\Name = Name
EndProcedure

; Method to get Personal Data
Procedure.l get_Data(*Self.Class_PBFriendly)
  ProcedureReturn *Self\pData
EndProcedure

; Method to set Personal Data
Procedure put_Data(*Self.Class_PBFriendly,pData.l)
  *Self\pData = pData
EndProcedure

; Method with Messagerequester
Procedure SayHello(*Self.Class_PBFriendly)
  MessageRequester("PBFriendly",*Self\Name+" says: Hello!",0)
EndProcedure

; Creating a vTable of methods, in-sync with the Interface
length.l = SizeOf(IPBFriendly)/4
Dim VTPBFriendlyFunctions(length-1)

VTPBFriendlyFunctions(0) = @get_Name()
VTPBFriendlyFunctions(1) = @put_Name()
VTPBFriendlyFunctions(2) = @get_Data()
VTPBFriendlyFunctions(3) = @put_Data()
VTPBFriendlyFunctions(4) = @SayHello() 

; This list will contain the instances of the objects
NewList Instances.Class_PBFriendly()

; Create an object
Procedure.l CreatePBFriendly(Name.s)
  AddElement(Instances())                         ; add a new element to the list
  Instances()\vTable = VTPBFriendlyFunctions()    ; assign the vTable to the object
  Instances()\Name = Name                         ; assign a property
  ProcedureReturn @Instances()                  ; return the address of the object
EndProcedure

; This procedure can be used by objects, it could also have been one of the methods
Procedure Introduction(*Person.IPBFriendly,pData.l)
  *Person\SayHello()                              ; call the SayHello method
  *Person\put_Data(pData)                         ; put personal data
  *pData.PersonalData = *Person\get_Data()        ; get the data
  msg.s = "My name is "+*Person\get_Name()+#CRLF
  msg = msg + "My age is "+Str(*pData\Age)+" and my SocialSecurityNumber (SSN) is "+Str(*pData\SSN)
  MessageRequester("PBFriendly",msg,0)            ; display the data
EndProcedure

oSimon.IPBFriendly = CreatePBFriendly("Simon")    ; creation of object oSimon
Introduction(oSimon,?Simon)
oPeter.IPBFriendly = CreatePBFriendly("Peter")    ; creation of object oPeter
Introduction(oPeter,?Peter)

oSimon\put_Name("Simon Simmons")
msg.s = "Simon says: I changed my name to "+oSimon\get_Name()
MessageRequester("PBFriendly",msg,0)

End

DataSection
  Simon:                ; personal data for Simon
  Data.l 30,7345654
  Peter:                ; personal data for Peter
  Data.l 39,8675412
EndDataSection

Is this ask for too much?

Posted: Sat Jun 05, 2004 6:26 pm
by Derlidio
Yeepy...

Still playing around with GedB code and got to a concept question: Is it possible to store a reference to the object inside the object itself so it can call its own methods using its own interface? I've made a little piece of code to try it, and it seems to work "partially". Should it work or I'm just doing monkey moves?

Code: Select all

Interface vCounter_Interface
   Inc()
   Ret.l()
   Times(X.l)
EndInterface

Structure vCounter_Methods
   Inc.l
   Ret.l
   Times.l
EndStructure

Structure vCounter_Object
   *Methods.vCounter_Methods
   Self.vCounter_Interface
   Value.l
EndStructure

Procedure vCounter_Inc(*Object.vCounter_Object)
   *Object\Value = *Object\Value + 1
EndProcedure

Procedure.l vCounter_Ret(*Object.vCounter_Object)
   ProcedureReturn *Object\Value
EndProcedure

Procedure vCounter_Times(*Object.vCounter_Object, X.l)
   
   Limit.l = *Object\Self\Ret() + X.l ; This works!
   
   ; Shows a message assuring we successfully
   ; catch the limit from self...
   MessageRequester ("X", "Got Limit: " + Str(Limit.l))
   
   While *Object\Self\Ret() < Limit.l
     *Object\Self\Inc(); This doesn't work...
   Wend
   
EndProcedure

Global vCounter_Pointers.vCounter_Methods

vCounter_Pointers\Inc = @vCounter_Inc()
vCounter_Pointers\Ret = @vCounter_Ret()
vCounter_Pointers\Times = @vCounter_Times()

NewList Instances.vCounter_Object()

Procedure.l vCounter_Create()
   
   DefType.vCounter_Object *New
   
   *New = AddElement(Instances())
   *New\Methods = @vCounter_Pointers
   *New\Self = *New
   
   ProcedureReturn *New
   
EndProcedure

; Create the object:
Obj.vCounter_Interface = vCounter_Create()

; Test simple calls to Ret and Inc methods:
While Obj\Ret() < 10: Obj\Inc(): Wend

; The following shows "10":
MessageRequester ("X", Str(Obj\Ret()))

; Try a method that auto-call other methods from self.
; Code will crash in the middle, without warning, after
; showing the "Got Limit" message inside method code...
Obj\Times(10)

; The following should show "20":
MessageRequester ("X", Str(Obj\Ret()))

Posted: Sat Jun 05, 2004 6:52 pm
by aXend
Is it possible to store a reference to the object inside the object itself so it can call its own methods using its own interface?
I was busy with the same question and found the solution! :D
Yes, it is possible, but you have to change the calling. I used your example and changed it a bit. I don't know if the results are what you expected, but it doesn't crash anymore.

Code: Select all

Interface vCounter_Interface 
   Inc() 
   Ret.l() 
   Times(X.l) 
EndInterface 

Structure vCounter_Methods 
   Inc.l 
   Ret.l 
   Times.l 
EndStructure 

Structure vCounter_Object 
   *Methods.vCounter_Methods 
;   Self.vCounter_Interface    <======== removed this one
   Value.l 
EndStructure 

Procedure vCounter_Inc(*Object.vCounter_Object) 
   *Object\Value = *Object\Value + 1 
EndProcedure 

Procedure.l vCounter_Ret(*Object.vCounter_Object) 
   ProcedureReturn *Object\Value 
EndProcedure 

Procedure vCounter_Times(*Object.vCounter_Object, X.l) 
    
   Limit.l = vCounter_Ret(*Object) + X.l ; This works!     <======== changed this one
    
   ; Shows a message assuring we successfully 
   ; catch the limit from self... 
   MessageRequester ("X", "Got Limit: " + Str(Limit.l)) 
    
   While vCounter_Ret(*Object) < Limit.l     ;       <======== changed this one

     vCounter_Inc(*Object); This work now?...      <======== changed this one

   Wend 
    
EndProcedure 

Global vCounter_Pointers.vCounter_Methods 

vCounter_Pointers\Inc = @vCounter_Inc() 
vCounter_Pointers\Ret = @vCounter_Ret() 
vCounter_Pointers\Times = @vCounter_Times() 

NewList Instances.vCounter_Object() 

Procedure.l vCounter_Create() 
    
   DefType.vCounter_Object *New 
    
   *New = AddElement(Instances()) 
   *New\Methods = @vCounter_Pointers 
;    *New\Self = *New                       <======== removed this one
    
   ProcedureReturn *New 
    
EndProcedure 

; Create the object: 
Obj.vCounter_Interface = vCounter_Create() 

; Test simple calls to Ret and Inc methods: 
While Obj\Ret() < 10: Obj\Inc(): Wend 

; The following shows "10": 
MessageRequester ("X", Str(Obj\Ret())) 

; Try a method that auto-call other methods from self. 
; Code will crash in the middle, without warning, after 
; showing the "Got Limit" message inside method code... 
Obj\Times(10) 

; The following should show "20": 
MessageRequester ("X", Str(Obj\Ret())) 
I hope this helps :?:

Posted: Sat Jun 05, 2004 6:59 pm
by aXend
In addition to my previous example I have changed the procedure Introduction to a method of the class. This is the result.

Code: Select all

[.....]
; Add method that uses other methods
Procedure Introduction(*Self.Class_PBFriendly,pData.l)
  SayHello(*Self)                               ; call the SayHello method
  put_Data(*Self,pData)                         ; put personal data
  msg.s = "My name is "+*Self\Name+#CRLF
  msg = msg + "My age is "+Str(*Self\pData\Age)+" and my SocialSecurityNumber (SSN) is "+Str(*Self\pData\SSN)
  MessageRequester("PBFriendly",msg,0)          ; display the data
EndProcedure

; added method to the vTable
[.....]
VTPBFriendlyFunctions(5) = @Introduction() 

; changed calling of method
oSimon.IPBFriendly = CreatePBFriendly("Simon")    ; creation of object oSimon
oSimon\Introduction(?Simon)
oPeter.IPBFriendly = CreatePBFriendly("Peter")    ; creation of object oPeter
oPeter\Introduction(?Peter)

[.....]

Posted: Sat Jun 05, 2004 7:27 pm
by Derlidio
Yeepy...

I understand your point. Indeed, GedB has made the same type of calling (see PBStackObj_Push) at his Stack example posted here: viewtopic.php?t=10319&postdays=0&postorder=asc&start=20

I just wonder why the replicated reference to the vCounter that I used in my code doesn't work as expected :cry: ... GedB?

Posted: Sat Jun 05, 2004 8:16 pm
by aXend
I just wonder why the replicated reference to the vCounter that I used in my code doesn't work as expected
I hope you didn't miss my answer before my last post. Here I gave the solution for your problem. :)

Your replicated reference doesn't work (I think) because *Self is of another type (vCounter_Object) than the object that uses the interface (vCounter_Interface). Therefore *Self can't reference to the methods, but it can call the procedures, with *Self as parameter.

Posted: Sat Jun 05, 2004 8:56 pm
by Derlidio
Yeepy... Here we go again!

This thing is becoming quite exciting :D

I've found a solution for my problem. Its not that hard after all :oops: Indeed, it is quite simple. What I was trying to accomplish was a way to call object's methods inside a third method function (from the same object) without having to know the real procedure name. Got it? At a first look it may seem useless, but its all what I need to get my app concept on track. I'm trying to create a "generic" kind of object holding some predefined (well known) methods, but these methods may point to different functions deppending on the parameters passed by the programmer at the creation moment. How could I know what procedure to call among those coded for each object sub-type? I could store a SubType variable within the object data structure and use a Select when needed, but it is not "clean" enough...

So, I realize this:

Code: Select all

Procedure vCounter_Times(*Object.vCounter_Object, X.l)
   
   Limit.l = CallFunctionFast(*Object\Methods\Ret,*Object) + X.l ; This works!
   
   ; Shows a message assuring we successfully
   ; catch the limit from self...
   MessageRequester ("X", "Got Limit: " + Str(Limit.l))
   
   While CallFunctionFast(*Object\Methods\Ret,*Object) < Limit.l
     CallFunctionFast(*Object\Methods\Inc,*Object); This works too!
   Wend
   
EndProcedure
This will work fine if you replace the function at the last code you post (that regarding to my question), and no "real-name" calling is need at all.

As Johnny says, lets keep walking! :wink:

Posted: Sat Jun 05, 2004 9:32 pm
by aXend
I see what you mean, but I don't understand what you're trying to do. To me OOP stands for defining classes with methods and properties. Both can be public or private.
By creating the object, you hide the implementation from a programmer, but inside the implementation everything is predefined. You have to add the procedure addresses to the vTable anyway. You can't generalize that. So it seems useless to me. But may be I'm missing something? :?
If you could show me an example where this is useful?

Posted: Sun Jun 06, 2004 12:01 am
by GedB
Derlidio,

There is no need to keep a reference to self within the object itself. Self is always passed in as the first paramater to your methods.

You are on the right track, but there is no need to use CallFunctionFast.

Just access the object through the interface.

Code: Select all

Procedure vCounter_Times(*Object.vCounter_Interface, x.l)
  
  Limit.l = *Object\Ret() + x.l ; This works!
  
  ; Shows a message assuring we successfully
  ; catch the limit from self...
  MessageRequester ("X", "Got Limit: " + Str(Limit))
  
  While *Object\Ret() < Limit
    *Object\Inc() ;This works too!
  Wend
  
EndProcedure
As a simple rule, only use the object directly if you need to access private members. Otherwise always use the interface. This will make future rewrites much easier.

You can switch between the two by assigning your pointer to a differently typed variable. For example:

Code: Select all

Procedure vCounter_Times(*Object.vCounter_Interface, x.l)
  
  Limit.l = *Object\Ret() + x.l ; This works!
  
  ; Shows a message assuring we successfully
  ; catch the limit from self...
  MessageRequester ("X", "Got Limit: " + Str(Limit))
  
  While *Object\Ret() < Limit
    *Object\Inc() ;This works too!
  Wend
  
  *PrivateObject.vCounter_Object = *Object
  MessageRequester("value", Str(*PrivateObject\Value))
  
EndProcedure
The important thing to remember is that it is just a pointer. If you cast the pointer to vCounter_Object then it will be treated as a normal structure and values will be returned.. If you cast it to vCounter_Interface if will be treated as a vTable and methods will be called.

Posted: Sun Jun 06, 2004 12:25 am
by Derlidio
Yeepy...

Now what can I say? Thanks man... :D I was missing that point, but now everything became clear! Now I know that what I was looking for is possible, and simple. I'm quite confident now to start recoding my app.

And thanks to aXend too for his help and patience. I'll try to figure out a sample code to explain my point in a clear way. I just don't know if it can be pointed in a short piece of code. What I'm trying to do will be huge, but when it is finished maybe it became a good sample (but it will demand some time).

Best wishes... (and lets keep walking :wink: )

Posted: Sun Jun 06, 2004 6:25 pm
by aXend
@GedB: I tested your solution, but with me it doesn't work! That seems logical, as I stated earlier. I think that calling a method from another method has to be done with the *object as parameter.

@Derlidio: Thanks.. :) I'll wait for your app.

Posted: Sun Jun 06, 2004 10:10 pm
by GedB
aXend,

An interface should be an interface, regardless.

Could you send me some code?

Posted: Mon Jun 07, 2004 7:46 am
by aXend
@GedB:
You're right, I overlooked the change in the arguments of the method. :oops: All other methods use *Self.Object and this one uses *Self.Interface. To me that is confusing, but you're right, it works and it is interchangable.

I have some questions for you.
1. Why do you use a list to create the instances? This works too, without any list involved.

Code: Select all

Procedure CreatePBFriendly()
  Shared object.Class_PBFriendly
  object\vTable = VTPBFriendlyFunctions()
  object\Name = ""
  ProcedureReturn @object
EndProcedure
2. Why do you define a structure for the vTable? As you can see in my example I defined an array to hold the pointers to the methods. This has another advantage: you don't have to define a global variable for the vTable.

3. To create an object you have to know the name of the procedure that creates the object. I would like to generalize that. But that would require COM-like things like UUID's and an IUnknown Interface. Did you already find a solution for this?

As I said before: I'm very interested in OOP functionality in PB. So if you have found answers for it... :)

Posted: Mon Jun 07, 2004 8:33 am
by Danilo
Have you guys checked my old examples for this: PB_OOP.zip ??

It shows how to create a simple class and the second class
inherits from the first class (examples OOP.pb and OOPsmall.pb).
Its not much, but its all you need to know.