• Welcome to Jose's Read Only Forum 2023.
 

Object Oriented Programming

Started by Kent Sarikaya, July 21, 2007, 09:41:10 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Kent Sarikaya

Hi guys,

I know powerbasic doesn't support OOP. But from readings on other forums(aurora, freebasic and others), and in articles on the web. I know that object oriented programming in the end comes down into code that is procedural and that procedural languages could mimic OOP features.

Has anyone done an example in powerbasic. For example I guess the methods in a class are just procedures with a pointer look up table in the structure of the members. I read that but I don't understand how using the pointer from that table in the structure that you can actually call the procedure and pass it the parameters using the dot notation as you can with c++.

So if anyone has a nice simple example maybe showing the c++ version and the powerbasic oop emulation, I sure would love to study it.

Thanks.
Kent

Charles Pegge

#1
Hi Kent,

I hope this will answer at least half of your question. There is no attempt to diguise the clunkiness of this technique, but I am sure you can make it more palatable with macros etc. This is OOP in the raw.

I defer to José's expertise on this subject because he has translated so much C++ stuff and of course COM is a particular form of OOP.



' some ver basic OOP for Powerbasic ver 8
' Charles E V Pegge
' 21 July 2007


#COMPILE EXE
#DIM ALL

' define a class

TYPE MyClass
vtable AS LONG PTR  ' pointer to the virtual table of methods for this class
a AS LONG           ' static members of objects in this class
b AS LONG           '
c AS LONG           '
END TYPE


' Create some virtual methods including a creator if reuired.

FUNCTION MyMethod0(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
'creator method
' sets initial property values
this.a=10
this.b=x
this.c=y
FUNCTION=0
END FUNCTION


FUNCTION MyMethod1(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
this.a=x+y
FUNCTION=this.a
END FUNCTION

FUNCTION MyMethod2(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
this.a=x-y
FUNCTION=this.a
END FUNCTION

FUNCTION MyMethod3(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
this.a=x*y
FUNCTION=this.a
END FUNCTION

FUNCTION MyMethod4(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
this.a=x/y
FUNCTION=this.a
END FUNCTION
   


FUNCTION PBMAIN () AS LONG

' Set up the methods virtual table for MyClass

DIM MyMethods(10) AS LONG PTR
MyMethods(0)=CODEPTR(MyMethod0)
MyMethods(1)=CODEPTR(MyMethod1)
MyMethods(2)=CODEPTR(MyMethod2)
MyMethods(3)=CODEPTR(MyMethod3)
MyMethods(4)=CODEPTR(MyMethod4)

'create an object

DIM MyObject AS MyClass
DIM pt AS LONG PTR
MyObject.vtable=VARPTR(MyMethods(0))
' call the creator method to set initial values
pt=MyObject.@vtable[0]: CALL DWORD pt USING MyMethod0(MyObject,2,3)
   
'call a method

DIM ans AS LONG
pt=MyObject.@vtable[3]: CALL DWORD pt USING MyMethod3(MyObject,2,3) TO ans



'display results which should be the same

MSGBOX STR$(ans)+"     "+STR$(MyObject.a)

END FUNCTION


José Roca

#2
 
A COM class that inherits directly from IUnknown isn't much different, excepting
that COM uses double indirection. A COM Automation class is more involved. The attached file includes one that wraps the Open/Save File Dialog API functions and an small example that uses it.


Edwin Knoppert

The combrowser for the lcc compiler creates defines like:

#define ICalendar_get_Day(ip, p1) (ip)->lpVtbl->get_Day(ip, p1)

I am not sure if PB can use this for a macro such as:

macro ICalendar_get_Day(ip, p1) = ip.pVtbl.get_Day(ip, p1)

But i don't think so.

Charles Pegge

#4
In my example, the double indirection has been disguised :)
Indirection is such a brain-teaser.

These lines are equivalent:



pt=MyObject.@vtable[3]: CALL DWORD pt USING MyMethod3(MyObject,2,3) TO ans

pt=VARPTR(MyObject):f=@@pt[3]:CALL DWORD f USING MyMethod3(MyObject,2,3) TO ans


Edwin Knoppert

>pt=VARPTR(MyObject):f=@@pt[3]:CALL DWORD f USING MyMethod3(MyObject,2,3) TO ans

I guess this one can be put in a macro(?)

Usually the syntax: If 'the macro here' = 1 then .. fails.
Sorry but i think PB does not show much effort to implement classes and at least update the annoying com syntax..

Oh well, why should i be concerned with this language at all..
Pffft..

José Roca

 
Quote
Sorry but i think PB does not show much effort to implement classes and at least update the annoying com syntax..

You already know that PB doesn't talk about which features will be incorporated in the new versions of the compilers. So you don't know...

Charles Pegge

#7
 
I think this is the best we can do with PB macros:


MACRO multiply(MyObject,a,b,ans)
pt=MyObject.@vtable[3]: CALL DWORD pt USING MyMethod3(MyObject,a,b) TO ans
END MACRO

....

multiply(MyObject,2,3,ans)



The problem is such macros have global scope which limits their usefulness
in oop programming.

Kent Sarikaya

Guys thank you so much for the examples and discussion. I am in the mood to study tonight, so I am very happy. Thanks for all the material. I hope it all makes sense in my mind so I get a clear picture of how all of this works behind the scenes.

Thanks so much!!

Kent Sarikaya

#9
Quote from: Charles Pegge on July 21, 2007, 11:02:26 PM
Hi Kent,

I hope this will answer at least half of your question. There is no attempt to diguise the clunkiness of this technique, but I am sure you can make it more palatable with macros etc. This is OOP in the raw.

I defer to José's expertise on this subject because he has translated so much C++ stuff and of course COM is a particular form of OOP.



' some ver basic OOP for Powerbasic ver 8
' Charles E V Pegge
' 21 July 2007


#COMPILE EXE
#DIM ALL

' define a class

TYPE MyClass
vtable AS LONG PTR  ' pointer to the virtual table of methods for this class
a AS LONG           ' static members of objects in this class
b AS LONG           '
c AS LONG           '
END TYPE


' Create some virtual methods including a creator if reuired.

FUNCTION MyMethod0(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
'creator method
' sets initial property values
this.a=10
this.b=x
this.c=y
FUNCTION=0
END FUNCTION


FUNCTION MyMethod1(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
this.a=x+y
FUNCTION=this.a
END FUNCTION

FUNCTION MyMethod2(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
this.a=x-y
FUNCTION=this.a
END FUNCTION

FUNCTION MyMethod3(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
this.a=x*y
FUNCTION=this.a
END FUNCTION

FUNCTION MyMethod4(BYREF this AS MyClass, BYVAL x AS LONG , BYVAL y AS LONG) AS LONG
this.a=x/y
FUNCTION=this.a
END FUNCTION
   


FUNCTION PBMAIN () AS LONG

' Set up the methods virtual table for MyClass

DIM MyMethods(10) AS LONG PTR
MyMethods(0)=CODEPTR(MyMethod0)
MyMethods(1)=CODEPTR(MyMethod1)
MyMethods(2)=CODEPTR(MyMethod2)
MyMethods(3)=CODEPTR(MyMethod3)
MyMethods(4)=CODEPTR(MyMethod4)

'create an object

DIM MyObject AS MyClass
DIM pt AS LONG PTR
MyObject.vtable=VARPTR(MyMethods(0))
' call the creator method to set initial values
pt=MyObject.@vtable[0]: CALL DWORD pt USING MyMethod0(MyObject,2,3)
   
'call a method

DIM ans AS LONG
pt=MyObject.@vtable[3]: CALL DWORD pt USING MyMethod3(MyObject,2,3) TO ans



'display results which should be the same

MSGBOX STR$(ans)+"     "+STR$(MyObject.a)

END FUNCTION



Charles your example is awesome. So far, it is the one I started to study first. I can follow everything and it really does help in putting all of this OOP stuff into perspective.
I am just confused with this part at the moment, as I am trying to see how the compiler would do all of this on its own in terms of the logic.

I will label this PART A:
DIM MyMethods(10) AS LONG PTR
MyMethods(0)=CODEPTR(MyMethod0)
MyMethods(1)=CODEPTR(MyMethod1)
MyMethods(2)=CODEPTR(MyMethod2)
MyMethods(3)=CODEPTR(MyMethod3)
MyMethods(4)=CODEPTR(MyMethod4)

This part above does not tie the information to the myclass structure.
It seems to me like somehow the above could be in:
This will be PART B
TYPE MyClass
vtable AS LONG PTR  ' pointer to the virtual table of methods for this class
a AS LONG           ' static members of objects in this class
b AS LONG           '
c AS LONG           '
END TYPE

In the vtable member of PART B, Is there a more direct way that you can think of to write it as an array member with long ptrs or as codeptrs?

I guess my confusion is in how would the compiler know to make PART A on its own and to tie it into PART B as in your great example? It just seems to keep track of everything it would be more direct, this all from guessing and a sense of intuition, not from anything I read so I could be just way off in my feelings about it.

But your example has taken my out of the darkness I was in, in even beginning to even see a glimpse of the picture. Now I see the picture and appreciate it, just asking about details now :)

Thanks again!!

Now onto study the other examples.

Kent Sarikaya

#10
Now I see that the latter posts sort of cleared what my confusion was. The indirection mind melting code actually is what I was looking for but couldn't phrase it.
When you see it, it makes more sense to me. So if we went that route, then there would be no need for PART A, is this correct?
Also I know this is code was for a variable after it is declared, but couldn't the code be in PART B of the example above?
pt=VARPTR(MyObject):f=@@pt[3]:CALL DWORD f USING MyMethod3(MyObject,2,3) TO ans
That is the methods would be pointed to within the structure and when a variable is instanced, it would know to assign that method as classes do in oop languages?
How is that part handled behind the scenes, I guess is another way to ask it?

This is really awesome if you ask me. After I just purchased powerBasic, I was sort of dismayed by its age and way of doing things. But knowing that things like this can be done. I can see putting the time to really work with it.

Now to study Jose's COM code sample!

Kent Sarikaya

Jose, I can ask too many questions about your example, but I won't do that. I will read up on COM and how powerbasic interfaces to it before I get back to you.
The little I have read about COM, it sounds good to me. Perhaps the way all objects should be written and then used, but this is just from brief readings about it and not in depth study.
I will read into it more tonight, just wanted to comment back so you know why I am not asking about your example more. Thanks so much for that example, lots to look at and to help figure out things as I read up on it!

Kent Sarikaya

I just read through MSDN about COM and came back to look at Jose's example. If this statement is correct I would assume I am heading the right direction in understanding, otherwise I am still lost in the woods :)

In looking at TB_OpenSaveDlg.inc, it seems that this code would be sort of the readable step that a compiler would do behind the scenes when it ran across a Class in c++ and started to do its work, is this correct?

José Roca

#13
 
Well, my example just shows a way to do it, but the implementation is free. COM specifies that you have to build a virtual table, that is an array of pointers to the methods and properties of the object, and that you have to return the address of a pointer, i.e. a pointer to a pointer, to this table. For a dispatch interface, it also specifies that the first seven methods must be the three IUnknown methods (QueryInterface, AddRef and Release) and the four IDIspatch methods (GetTypeInfoCount, GetTypeInfo, GetIDsOfNames and Invoke), and also specifies what these methods must do, but not how to do it: this is your business.

A dual interface allows both to call the methods and properties (procedures and functions) directly, in the same way as shown in Charles example or in my libraries of wrapper functions, or through the Invoke method (Automation). When you use OBJECT CALL oOsfn.ShowSave TO vRes, it is not the TB_OpenSaveFileName_ShowSave function which is called, but TB_OpenSaveFileName_Invoke, and this function will call TB_OpenSaveFileName_ShowSave. To call the function directly with PB, you will have to use CALL DWORD.

Automation only languages such Visual Basic don't implement Invoke in the same way as I have done, but built a sort of internal type library and delegate to the standard implementation of Invoke built in the Microsoft COM library. I know how to do it, but it is too cumbersome to be done by hand, it is a task for the compiler.

In short, COM specifies a set of rules, but every compiler implements them in his own way. As PB currently doesn't have support for classes, we have to write all the code needed to implement the virtual table, the seven standard methods of a dispatch interface, code to allocate and free the memory used by the object, etc. Otherwise, we could write something like:

CLASS MyClass : IDispatch

   METHOD Somemethod (list of parameters) AS LONG
      --- some code
   END METHOD

   --- more methods and properties

END CLASS

And the compiler will do the rest.

A COM class can inherit directly from IUnknown and not implement the IDispatch interface. This produces much more compact and efficient code and is the way in which all the standard COM interfaces, and also components like DirectX, have been implemented. The methods of these low-level interfaces can only be called directly and can be used by languages able to call a function through a pointer, such PB or C. Visual Basic and scripting languages can't use them.

Some future version of PB will have to implement classes and native support for direct interface calls. It seems unavoidable because it is the way to access many Microsoft technologies and also the way to interface with the .NET framework. It is possible to host the .NET runtime, that is nothing else but a COM server, in a PB application and call the methods of the .NET classes. I have done it. It will be also possible to do the same with .NET assemblies, without having to build COM callable wrappers.

Charles Pegge

Dynamic OOP in PowerBasic

This shows how to create classes and objects in dynamic strings. And by using macros, the syntax is also made much simpler.

For the sake of brevity, this code assumes Classes and objects each have a standard generic structure, and only the methods will vary, but more advanced orders of flexibility are poosible using similar techniques.

The advantage of holding objects in strings is that they can be generated in arrays, cloned, stuffed into other structures and manipulated like any other kind of string. They are completely relocatable as long as their class string exists and remains in the same place as when the objects were created from it. The vtable pointer in each object must always point to the start of their class string.





' some OOP for Powerbasic ver 8
' using dynamic objects made with strings
' making use of macros to simplify the syntax.
' Charles E V Pegge
' 22 July 2007


#COMPILE EXE
#DIM ALL


' DEFINE THE STRUCTURE OF THE GENERIC CLASS
TYPE GenericClass
constructor AS LONG PTR
destructor  AS LONG PTR
' make 4 methods available
m1          AS LONG PTR
m2          AS LONG PTR
m3          AS LONG PTR
m4          AS LONG PTR
ObjectSize  AS LONG
' make 4 static members for general use eg tallying objects created
p1          AS LONG
p2          AS LONG
p3          AS LONG
p4          AS LONG
' etc ....
END TYPE

' DEFINE THE STRUCTURE OF THE GENERIC OBJECTS
TYPE GenericObject
vtable AS LONG PTR ' points to table of methods (in class structure)
' statc members
sa AS LONG
sb AS LONG
sc AS LONG
sd AS LONG
se AS LONG
sf AS LONG
' .... etc
END TYPE


' OOP GENERIC MACROS

MACRO CreateClass(MyClass,cre,des,f1,f2,f3,f4)
DIM MyClass AS STRING
MyClass=NUL$(SIZEOF(GenericClass))
cp=STRPTR(MyClass)
@cp.constructor=CODEPTR(cre)
@cp.destructor =CODEPTR(des)
@cp.m1=CODEPTR(f1)
@cp.m2=CODEPTR(f2)
@cp.m3=CODEPTR(f3)
@cp.m4=CODEPTR(f4)
@cp.ObjectSize=SIZEOF(GenericObject)
END MACRO

MACRO CreateObject(MyClass,MyObject,a,b,c,d,ans)
cp=STRPTR(MyClass)
DIM MyObject AS STRING
MyObject=NUL$(@cp.ObjectSize) ' fix size of object
op=STRPTR(MyObject)
@op.vtable=cp ' pointer to the classes method table
CallObject(MyObject,%CREATEme,a,b,c,d,ans)
END MACRO

MACRO CallObject(MyObject,MyMethod,a,b,c,d,ans)
op=STRPTR(MyObject): pt=op: vt=@@pt[MyMethod]
CALL DWORD vt USING MyGenericMethod(op,a,b,c,d) TO ans
END MACRO

MACRO DestroyObject(MyObject)
CallObject(MyObject,%DESTROYme,0,0,0,0,ans)
MyObject=""
END MACRO

     

MACRO GenericParameters=BYVAL pthis AS GenericObject PTR, BYVAL a AS LONG, BYVAL b AS LONG, BYVAL c AS LONG, BYVAL d AS LONG



DECLARE FUNCTION MyGenericMethod (GenericParameters) AS LONG


' SOME METHODS

%CREATEme   = 0
%DESTROYme  = 1
%ADDme      = 2
%SUBTRACTme = 3
%MULTIPLYme = 4
%DIVIDEme   = 5


FUNCTION Creates (GenericParameters) AS LONG
' initialise static members
@pthis.sa=a
@pthis.sb=b
@pthis.sc=c
@pthis.sd=d
FUNCTION =0
END FUNCTION

FUNCTION Destroys (GenericParameters) AS LONG
' anything that needs to be done before nulling the object
FUNCTION =0
END FUNCTION

FUNCTION adds (GenericParameters) AS LONG
@pthis.sa=a+b
FUNCTION =@pthis.sa
END FUNCTION

FUNCTION subtracts(GenericParameters) AS LONG
@pthis.sa=a-b
FUNCTION =@pthis.sa
END FUNCTION

FUNCTION multiplies(GenericParameters) AS LONG
@pthis.sa=a*b
FUNCTION =@pthis.sa
END FUNCTION

FUNCTION Divides(GenericParameters) AS LONG
@pthis.sa=a/b
FUNCTION =@pthis.sa
END FUNCTION



FUNCTION PBMAIN()


DIM cp AS GenericClass PTR
DIM op AS GenericObject PTR
DIM pt AS LONG PTR ' pointer to virtual table
DIM vt AS LONG PTR ' pointer to virtual table
DIM ans AS LONG

CreateClass(MyClass,Creates,Destroys,Adds,Subtracts,Multiplies,Divides)
CreateObject(MyClass,MyObject,0,0,0,0,ans)
CallObject(MyObject,%MULTIPLYme,2,3,0,0,ans)
MSGBOX STR$(ans)+"    "+STR$(@op.sa)
DestroyObject(MyObject)

END FUNCTION