Code:
#Compile Exe 'While the interface declarations just #Dim All 'below for I_X and I_Y can be deleted Declare Sub pFn(dwPtr As Dword) 'and this program will still work, I... Interface I_X : Inherit IUnknown '...thought for clarity sake it might be Method Fx1() 'worthwhile to include them for the purpose Method Fx2() 'of making the point that COM architecture End Interface 'is based on the idea of seperating the... Interface I_Y : Inherit IUnknown '...interface from the implementation of Method Fy1() 'the interface. Here, I_X and I_Y are Method Fy2() 'declared - but not implemented. They are End Interface 'implemented in class CA, and the ObjPtr() Class CA Interface I_X : Inherit IUnknown '...function can be used to obtain the Method Fx1() 'address of each interface's VTbl. Where Print "Called Fx1()" 'PowerBASIC seems to differ somewhat from End Method 'C++ is that in this situation in C++ the 'Sizeof(CA) would be 8 and those 8 bytes Method Fx2() 'would be two contiguous VTable pointers. Print "Called Fx2()" 'PowerBASIC will return two interface End Method 'pointers also but they do not appear to End Interface 'be contiguous. Below the I_X pVTbl is '1280208 and the I_Y interface pointer is Interface I_Y : Inherit IUnknown 'at 1280340 - not 1280212. Nontheless Method Fy1() 'they each can be used to obtain the Print "Called Fy1()" 'address of each interface's VTable, and End Method 'the function pointers held in the VTables 'can be used to call the interface Method Fy2() 'functions. This probably isn't really Print "Called Fy2()" 'recommended but this exercise at least End Method 'shows the memory layout. End Interface End Class Function PBMain() Local pVTbl As Dword Ptr Local VTbl As Dword Ptr Register i As Long Local ifX As I_X Local ifY As I_Y Let ifX = Class "CA" Let ifY = Class "CA" Call ifX.Fx1() : Call ifX.Fx2() Call ifY.Fy1() : Call ifY.Fy2() Print 'Call methods using interface/vtable 'pointers. 1st I_X pVTbl=ObjPtr(ifX) Print "pVTbl = " pVTbl VTbl=@pVTbl[0] Print "VTbl = " VTbl Print:Print Print " i Varptr(@VTbl[i]) @VTbl[i] Call Dword @VTbl[i]" Print "=========================================================================" For i=0 To 4 If i<=2 Then Print i, Varptr(@VTbl[i]), @VTbl[i]; " IUnknown Fns (better not call!)" Else Print i, Varptr(@VTbl[i]), @VTbl[i],; Call DWord @VTbl[i] Using pFn(0) End If Next i Print:Print 'Then I_Y pVTbl=ObjPtr(ifY) Print "pVTbl = " pVTbl VTbl=@pVTbl[0] Print "VTbl = " VTbl Print Print " i Varptr(@VTbl[i]) @VTbl[i] Call Dword @VTbl[i]" Print "=========================================================================" For i=0 To 4 If i<=2 Then Print i, Varptr(@VTbl[i]), @VTbl[i]; " IUnknown Fns (better not call!)" Else Print i, Varptr(@VTbl[i]), @VTbl[i],; Call DWord @VTbl[i] Using pFn(0) End If Next i Waitkey$ PBMain=0 End Function 'Called Fx1() 'Called Fx2() 'Called Fy1() 'Called Fy2() ' 'pVTbl = 1280208 'VTbl = 4200192 ' ' ' i Varptr(@VTbl[i]) @VTbl[i] Call Dword @VTbl[i] '========================================================================= ' 0 4200192 4208481 IUnknown Fns (better not call!) ' 1 4200196 4208461 IUnknown Fns (better not call!) ' 2 4200200 4208604 IUnknown Fns (better not call!) ' 3 4200204 4198799 Called Fx1() ' 4 4200208 4198867 Called Fx2() ' ' 'pVTbl = 1280340 'VTbl = 4200152 ' ' i Varptr(@VTbl[i]) @VTbl[i] Call Dword @VTbl[i] '========================================================================= ' 0 4200152 4208481 IUnknown Fns (better not call!) ' 1 4200156 4208461 IUnknown Fns (better not call!) ' 2 4200160 4208604 IUnknown Fns (better not call!) ' 3 4200164 4198935 Called Fy1() ' 4 4200168 4199003 Called Fy2()
Below program shows COM memory layout using all primitive data types. Can be compiled
with PB CC 4 or 5.
Code:
#Compile Exe 'Two levels of indirection using pointers #Dim All 'are fundamental to COM's design. When #Include "Win32Api.inc" 'a COM object is instantiated, memory will Declare Function fnPtr() As String 'be allocated for a pointer which points to 'each VTABLE/Intrface implemented in an Function QueryInterface() As String 'object. Here we have an I_X interface QueryInterface= _ 'containing Fx1() and Fx2(), and an I_Y "Called QueryInterface()" 'interface containing Fy1() and Fy2(). End Function 'Therefore we need to allocate two 32 bit 'pointers or 8 bytes. Down in PBMain() Function AddRef() As String 'note the 1st statement after the variable AddRef="Called AddRef()" 'declarations where pVTbl gets an 8 byte End Function 'memory block. Also note where simple 'variable declarations for dynamic memory Function Release() As String 'obtain Dword Ptr arrays to contain function Release="Called Release()" 'pointers for the five functions of the I_X End Function 'interface and the I_Y interface. The 'compiler is here allocating this memory, Function Fx1() As String 'and the base addresses are being assigned Fx1="Called Fx1()" 'respectively to the proper slot in one of End Function 'the two pVTbl[] slots. So, pVTbl[] holds '"pointers to pointers", and the VTABLE Function Fx2() As String 'itself - here represented by VTbl, holds Fx2="Called Fx2()" 'the actual interface function addresses. End Function 'Rather than doing seperate memory alloc- 'ations for the VTABLES themselves I let Function Fy1() As String 'the compiler set it up with array declara- Fy1="Called Fy1()" 'tions and set the base address in pVTbl[]. End Function 'In a real COM object QueryInterface(), 'AddRef(), and Release() have different Function Fy2() As String 'return values and signatures than shown Fy2="Called Fy2()" 'here, but the purpose of this code is just End Function 'to elucidate the memory layout of COM 'objects and show how their functions are Function PBMain() As Long 'called through multiple levels of pointer Register i As Long, j As Long 'indirection. Note that QueryInterface(), Local pVTbl,VTbl As Dword Ptr 'AddRef(), and Release() are implemented by Local strReturn As String 'every COM Interface/Object and the imple- Dim I_X(4) As Dword Ptr 'mentation of these functions is left to Dim I_Y(4) As Dword Ptr 'programmer who is creating the object 'server. Basically, one of the parameters pVTbl=GlobalAlloc(%GPTR,8) 'to QueryInterface is the IID of the inter- If pVTbl Then 'face desired, and a ptr to this interface Print "Varptr(@pVTbl[] @pVTbl[]" 'is returned through Print "=====================================" 'another parameter. With @pVTbl[0]=Varptr(I_X(0)) 'this interface pointer a @pVTbl[1]=Varptr(I_Y(0)) 'programmer can call the Print Varptr(@pVTbl[0]),, @pVTbl[0] 'interface's functions. Print Varptr(@pVTbl[1]),, @pVTbl[1] Print : Print I_X(0)=Codeptr(QueryInterface) : I_Y(0)=Codeptr(QueryInterface) I_X(1)=Codeptr(AddRef) : I_Y(1)=Codeptr(AddRef) I_X(2)=CodePtr(Release) : I_Y(2)=CodePtr(Release) I_X(3)=Codeptr(Fx1) : I_Y(3)=Codeptr(Fy1) I_X(4)=Codeptr(Fx2) : I_Y(4)=Codeptr(Fy2) Print "Varptr(@pVTbl[i] Varptr(@VTbl[j]) @VTbl[j] Call Dword @VTbl[j]" Print "========================================================================" For i=0 To 1 VTbl=@pVTbl[i] For j=0 To 4 Call Dword @VTbl[j] Using fnPtr To strReturn Print Tab(4) Varptr(@pVTbl[i]) Tab(22) Varptr(@VTbl[j]) Tab(36) @VTbl[j] Tab(50) strReturn Next j Print Next i Erase I_X, I_Y Call GlobalFree(pVTbl) End If Waitkey$ PBMain=0 End Function 'Varptr(@pVTbl[] @pVTbl[] '===================================== ' 1279944 1279880 ' 1279948 1279912 ' ' 'Varptr(@pVTbl[i] Varptr(@VTbl[j]) @VTbl[j] Call Dword @VTbl[j] '======================================================================== ' 1279944 1279880 4207317 Called QueryInterface() ' 1279944 1279884 4207391 Called AddRef() ' 1279944 1279888 4207465 Called Release() ' 1279944 1279892 4207539 Called Fx1() ' 1279944 1279896 4207613 Called Fx2() ' ' 1279948 1279912 4207317 Called QueryInterface() ' 1279948 1279916 4207391 Called AddRef() ' 1279948 1279920 4207465 Called Release() ' 1279948 1279924 4207687 Called Fy1() ' 1279948 1279928 4207761 Called Fy2() '
Next is another program that compiles with either CC 4 or 5 that uses Types.
Code:
#Compile Exe #Dim All #Include "Win32Api.inc" Declare Function fnPtr() As String Type IUnknwn 'Alias IUnknown QueryInterface As Dword Ptr AddRef As Dword Ptr Release As Dword Ptr End Type Type I_X 'Type Interface I_X IUnk As IUnknwn Fx1 As Dword Ptr Fx2 As Dword Ptr End Type Type I_Y 'Type Interface I_Y IUnk As IUnknwn Fy1 As Dword Ptr Fy2 As Dword Ptr End Type Type CA 'Class 'A' pIX As I_X Ptr pIY As I_Y Ptr End Type Function QueryInterface() As String QueryInterface="Called QueryInterface()" End Function Function AddRef() As String AddRef="Called AddRef()" End Function Function Release() As String Release="Called Release()" End Function Function Fx1() As String Fx1="Called Fx1()" End Function Function Fx2() As String Fx2="Called Fx2()" End Function Function Fy1() As String Fy1="Called Fy1()" End Function Function Fy2() As String Fy2="Called Fy2()" End Function Function PBMain() As Long Register i As Long, j As Long Local pVTbl,VTbl As Dword Ptr Local strReturn As String Local pCA As CA Ptr Local tagIX As I_X Local tagIY As I_Y Print "Sizeof(CA) = " Sizeof(CA) pCA=GlobalAlloc(%GPTR,Sizeof(CA)) If pCA Then Print "pCA = " pCA pVTbl=pCA Print "pVTbl = " pVTbl @pCA.pIX=Varptr(tagIX) : @pVTbl[0]=Varptr(tagIX) @pCA.pIY=Varptr(tagIY) : @pVTbl[1]=Varptr(tagIY) Print "@pVTbl[0] = " @pVTbl[0] Print "@pVTbl[1] = " @pVTbl[1] Print tagIX.IUnk.QueryInterface=Codeptr(QueryInterface) : tagIY.IUnk.QueryInterface=Codeptr(QueryInterface) tagIX.IUnk.AddRef=Codeptr(AddRef) : tagIY.IUnk.AddRef=Codeptr(AddRef) tagIX.IUnk.Release=CodePtr(Release) : tagIY.IUnk.Release=CodePtr(Release) tagIX.Fx1=Codeptr(Fx1) : tagIY.Fy1=Codeptr(Fy1) tagIX.Fx2=Codeptr(Fx2) : tagIY.Fy2=Codeptr(Fy2) Print "Varptr(@pVTbl[i] Varptr(@VTbl[j]) @VTbl[j] Call Dword @VTbl[j]" Print "========================================================================" For i=0 To 1 VTbl=@pVTbl[i] For j=0 To 4 Call Dword @VTbl[j] Using fnPtr To strReturn Print Tab(4) Varptr(@pVTbl[i]) Tab(22) Varptr(@VTbl[j]) Tab(36) @VTbl[j] Tab(50) strReturn Next j Print Next i Call GlobalFree(pCA) End If Waitkey$ PBMain=0 End Function 'Sizeof(CA) = 8 'pCA = 1279880 'pVTbl = 1279880 '@pVTbl[0] = 1244732 '@pVTbl[1] = 1244712 ' 'Varptr(@pVTbl[i] Varptr(@VTbl[j]) @VTbl[j] Call Dword @VTbl[j] '======================================================================== ' 1279880 1244732 4207317 Called QueryInterface() ' 1279880 1244736 4207391 Called AddRef() ' 1279880 1244740 4207465 Called Release() ' 1279880 1244744 4207539 Called Fx1() ' 1279880 1244748 4207613 Called Fx2() ' ' 1279884 1244712 4207317 Called QueryInterface() ' 1279884 1244716 4207391 Called AddRef() ' 1279884 1244720 4207465 Called Release() ' 1279884 1244724 4207687 Called Fy1() ' 1279884 1244728 4207761 Called Fy2()
And here is a C++ program that is exactly like these PowerBASIC programs in showing
how it is done there. I've got quite a few explanations & comments in this one.
Code:
#include <stdio.h> //CPP_ComMem.cpp - Program shows memory layout of COM Object. In a 'real' COM object #include <objbase.h> //QueryInterface(), AddRef(), and Release(), which are member functions of IUnknown, interface IUnknwn //Alias IUnknown //are prepended to every interface contained in a component. That { //is the effect of the ': IUnknown' just to the right of interface virtual void __stdcall QueryVTables()=0; //IX and interface IY terms here. So these interfaces actually virtual void __stdcall AddReference()=0; //contain five functions each. What is actually going on here with virtual void __stdcall ReleaseRef()=0; //these three interfaces and class CA is the construction of several }; //binary layouts in memory consisting of arrays of function pointers. interface IX : IUnknwn //Class CA below implements the three IUnknwn functions and the four IX { //and IY interface functions by simply providing output statements that virtual void __stdcall Fx1()=0; //they were called. However, down in main() where an instance of CA is virtual void __stdcall Fx2()=0; //created, you'll be able to see in detail the layout of these binary }; //structures. interface IY : IUnknwn //If by some chance you have or can acquire a C++ compiler you'll { //surely get different numbers than mine below, but for illustrating virtual void __stdcall Fy1()=0; //the layout of a COM object in memory these numbers will do. Note that virtual void __stdcall Fy2()=0; //the statement 'pCA=new CA' will create an object in memory. Because }; //of class CA inheriting virtually and multiply from two base classes, class CA : public IX, public IY //i.e., IX and IY, the size of CA will be 8 bytes. You can think of { //these 8 bytes as a Dword Ptr array containing two elements. The first public: //element starting at 4144816 will hold a pointer to another block of virtual void __stdcall QueryVTables(){puts("Called QueryVTables()");} //memory (another 'array') virtual void __stdcall AddReference(){puts("Called AddReference()");} //which will hold the actual virtual void __stdcall ReleaseRef(){puts("Called ReleaseRef()");} //pointers to the IX virtual void __stdcall Fx1(){printf("Called Fx1()\n");} //implementations //interface functions. These virtual void __stdcall Fx2(){printf("Called Fx2()\n");} //of inherited //begin at 4224292. This virtual void __stdcall Fy1(){printf("Called Fy1()\n");} //pure virtual //range of 20 bytes (5X4=20) virtual void __stdcall Fy2(){printf("Called Fy2()\n");} //functions. //is the IX VTable. You can }; //see that in column two ... int main(void) { void (__stdcall* pFn)(int); //...below. The second set of four bytes in the sizeof(CA)=8 is the pointer to the unsigned int* pVTbl=0; //IY VTable and this pointer is stored at bytes 4144820 - 4144823. The number that unsigned int* VTbl=0; //is stored there is 4224328 and this begins the virtual function table for the five unsigned int i=0,j=0; //IY interface functions. So in the code just left you can see two important CA* pCA=0; //variables. pVTbl[] contains the two pointers to the interface VTables. VTbl at... pCA=new CA; //left is assigned the base address of the IX VTable/interface just above the j for printf("sizeof(CA)\t\t= %u\t : An IX VTBL Ptr And A IY VTBL Ptr\n",sizeof(CA)); printf("pCA\t\t\t= %u\t : Ptr To IX VTBL\n",(unsigned int)pCA); pVTbl=(unsigned int*)pCA; printf("&pVTbl[0]=%u\t< at this address is ptr to IX VTable\n",&pVTbl[0]); printf("&pVTbl[1]=%u\t< at this address is ptr to IY VTable\n",&pVTbl[1]); printf("\n"); printf("&pVTbl[i]\t&VTbl[j]\tpFn=VTbl[j]\tpFn()\n"); printf("=========================================================================\n"); for(i=0;i<2;i++) //loop when i=0, and in the 2nd iteration of the i for loop VTbl is assigned { //the base address of the IY VTable. The j loop in either case simply loops VTbl=(unsigned int*)pVTbl[i]; //through the five function addresses held in each VTable, assigns the for(j=0;j<5;j++) //address to a function pointer variable ( pFn() ), then calls each function { //through its address. Note that in this whole sweet setup there are no Api printf //memory allocation calls to obtain any of this memory. Its all setup by ( //the C++ compiler through the virtual inheritance mechanism. One point I "%u\t\t%u\t\t%u\t\t", //might make about the pFn() function call. In calling the interface functions (unsigned int)&pVTbl[i], //like this we are being completely underhanded and are going behind the (unsigned int)&VTbl[j], //compiler's back, so to speak. When a member function of a class is called (unsigned int)VTbl[j] //whether it be a virtual function or not a hidden 'this' pointer is transfered ); //to the procedure as the first parameter. In a __stdcall procedure, this pFn=(void(__stdcall*)(int))VTbl[j]; //'this' pointer will be pushed on the stack before the call and pFn(0); //popped at cleanup. In this particular case the various interface functions } //take no parameters, but I included an unused int parameter in the function printf("\n"); //pointer declaration of pFn so as to cause a parameter to be popped off the } //stack and maintain a balanced stack. Otherwise, various poor outcomes may delete pCA; //occur. This is what is known as a 'hack'. getchar(); return 0; } /* sizeof(CA) = 8 : An IX VTBL Ptr And A IY VTBL Ptr pCA = 4144816 : Ptr To IX VTBL &pVTbl[0]=4144816 < at this address is ptr to IX VTable &pVTbl[1]=4144820 < at this address is ptr to IY VTable &pVTbl[i] &VTbl[j] pFn=VTbl[j] pFn() ========================================================================= 4144816 4224292 4214984 Called QueryVTables() 4144816 4224296 4214960 Called AddReference() 4144816 4224300 4215008 Called ReleaseReference() 4144816 4224304 4215032 Called Fx1() 4144816 4224308 4215056 Called Fx2() 4144820 4224328 4215268 Called QueryVTables() 4144820 4224332 4215256 Called AddReference() 4144820 4224336 4215280 Called ReleaseReference() 4144820 4224340 4215292 Called Fy1() 4144820 4224344 4215304 Called Fy2() */