This has been my summer to try and tackle learning COM. I've been immersed in C++ code all summer mostly because all the books, tutorials and such are in C++. About a week ago I set out to see if I could dig out of a C++ COM class the addresses of the Vtable pointers and the Vtables themselves. I figured if I could do that it would be pretty neat to iterate through a component's Vtables printing out addresses and calling the functions through function pointers. I wasn't really using an actual component but just C++ classes and interfaces designed so as to have the same memory footprint as a real COM object. I think I've been largely successful but depending on which compiler / development environment I use I keep getting what are for me unexplainable crashes. I know this isn't a C++ forum but I was hoping someone here would be interested enough to take a look at what I'm doing to see if there is anything obviously wrong with my code. The programs are very, very, short and simple. Here is the first, followed immediately by the output. This is using the Dev-C++ 4.9.9.2 environment and gcc...
#include <stdio.h> //CPP_ComMem.cpp - Program shows memory layout of COM Object.
#include <objbase.h> //As opposed to here, in a 'real' COM object QueryInterface(),
typedef void (*FN)(void); //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) //...below. The second set of four bytes in the sizeof(CA)=8 is the pointer to the
{ //IY VTable and this pointer is stored at bytes 4144820 - 4144823. The number that
unsigned int* pVTbl=0; //is stored there is 4224328 and this begins the virtual function table for the five
unsigned int* VTbl=0; //IY interface functions. So in the code just left you can see two important
unsigned int i=0,j=0; //variables. pVTbl[] contains the two pointers to the interface VTables. VTbl at
CA* pCA=0; //left is assigned the base address of the IX VTable/interface just above the j for
FN pFn=0; //loop when i=0, and in the 2nd iteration of the i for loop VTbl is assigned the...
pCA=new CA;
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++) //...base address of the IY VTable. The j loop in either case simply loops
{ //through the five function addresses held in each VTable, assigns the
VTbl=(unsigned int*)pVTbl[i]; //address to a function pointer variable ( pFn() ), then calls each function
for(j=0;j<5;j++) //through its address. Note that in this whole sweet setup there are no Api
{ //memory allocation calls to obtain any of this memory. Its all setup by
printf //the C++ compiler through the virtual inheritance mechanism.
(
"%u\t\t%u\t\t%u\t\t",
(unsigned int)&pVTbl[i],
(unsigned int)&VTbl[j],
(unsigned int)VTbl[j]
);
pFn=(FN)VTbl[j];
pFn();
}
printf("\n");
}
delete pCA;
getchar();
return 0;
}
Results from Dev-C++ 4.9.9.2 Environment (gcc compiler) Works perfectly,
no crashes, output as expected, etc.
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 4215008 Called QueryVTables()
4144816 4224296 4214984 Called AddReference()
4144816 4224300 4214960 Called ReleaseRef()
4144816 4224304 4215032 Called Fx1()
4144816 4224308 4215056 Called Fx2()
4144820 4224328 4215280 Called QueryVTables()
4144820 4224332 4215268 Called AddReference()
4144820 4224336 4215256 Called ReleaseRef()
4144820 4224340 4215292 Called Fy1()
4144820 4224344 4215304 Called Fy2()
When I attempt to compile this program with the Microsoft Visual C++ 6 compiler I get beautiful perfect output as above but I get a crash at the end. Here is the output from there...
Results from Microsoft Visual C++ 6. Output is perfect except that the program is crashing after printing this nice output. Its crashing before the getchar() at the end.
sizeof(CA) = 8 : An IX VTBL Ptr And A IY VTBL Ptr
pCA = 3146576 : Ptr To IX VTBL
&pVTbl[0]=3146576 < at this address is ptr to IX VTable
&pVTbl[1]=3146580 < at this address is ptr to IY VTable
&pVTbl[i] &VTbl[j] pFn=VTbl[j] pFn()
=========================================================================
3146576 4219068 4198672 Called QueryVTables()
3146576 4219072 4198688 Called AddReference()
3146576 4219076 4198704 Called ReleaseRef()
3146576 4219080 4198720 Called Fx1()
3146576 4219084 4198736 Called Fx2()
3146580 4219048 4198784 Called QueryVTables()
3146580 4219052 4198800 Called AddReference()
3146580 4219056 4198816 Called ReleaseRef()
3146580 4219060 4198752 Called Fy1()
3146580 4219064 4198768 Called Fy2()
Press any key to continue
Finally, here are the results using again the gcc compiler but this time with the new Code::Blocks development environment just let out by the nice Open Source folks...
http://www.codeblocks.org/
As an aside – this really is neat. I can use the same IDE / Compiler in both Windows and Linux. The program doesn't crash at all and even outputs pretty decent results except for some mangled Vtable pointer addresses in column 1 that it really can't be using internally or else it would crash hard...
sizeof(CA) = 8 : An IX VTBL Ptr And A IY VTBL Ptr
pCA = 4144888 : Ptr To IX VTBL
&pVTbl[0]=4144888 < at this address is ptr to IX VTable
&pVTbl[1]=4144892 < at this address is ptr to IY VTable
&pVTbl[i] &VTbl[j] pFn=VTbl[j] pFn()
=========================================================================
4144888 4224200 4214608 Called QueryVTables()
4144888 4224204 4214576 Called AddReference()
4144888 4224208 4214544 Called ReleaseRef()
4144888 4224212 4214640 Called Fx1()
4144888 4224216 4214672 Called Fx2()
4214800 4224236 4214800 Called QueryVTables()
4224240 4224240 4214784 Called AddReference()
4224240 4224244 4214768 Called ReleaseRef()
4224240 4224248 4214816 Called Fy1()
4223390 4224252 4214832 Called Fy2()
Process returned 0 (0x0) execution time : 14.060 s
Press any key to continue.
I fear I'm doing something wrong but not sure what. If anyone looks closely at the code above such a person may see this and wonder what in the world is going on...
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.
Its not the real bonafide Iunknown, but rather one made up by me. The reason I did that is because all I'm trying to do is understand what is going on in memory, and what goes on in memory isn't specific to COM but rather a result of the behavior of C++ when a class inherits virtual functions from multiple base classes. In fact, the reason C++ is so heavily – almost universally – used in creating COM components is that C++ automatically creates the memory layout required by the COM specification. So my intent in learning this was to reduce something complicated to its most fundamental form so as to study it. Hence, my digging into classes to find out where stuff is at!
At first I wasn't using that funny looking Iunknown, but rather the real one in objbase.h. Unfortunately, using that one conflicted with my desire to reduce everything to the simplest possible terms. I couldn't use one typedef to call all the functions because there are three different return value / signature catagories; QueryInterface() is one, AddRef()/Release() is another, and the third is Fx1() – Fx2() / Fy1() – Fy2(). It wouldn't be possible to blow the whole thing through a tight double I, j for loop as in the above example, because you would be calling functions with the wrong return/signature. Or would it??? Recall I said the above program creates good output but generates a crash in closing in VC 6? Well, here is one that with the very same compiler creates perfect output and runs flawlessly in every way any number of times you want to try it and never crashes. It uses the real Iunknown and calls every function in both Vtables using incorrect function signatures!
#include <stdio.h> //for printf(), puts(), etc.
#include <objbase.h> //for typedef struct interface
typedef void (*FN)(void); //to ease use of function pointers
interface IX : IUnknown //will cause inheritance of
{ //QueryInterface(),AddRef(),
virtual void __stdcall Fx1()=0; //and Release(). Note that when
virtual void __stdcall Fx2()=0; //class CA (just below) is
}; //instantiated, these two functions
interface IY : IUnknown //of IX and two functions of IY
{ //will be implemented, and a memory
virtual void __stdcall Fy1()=0; //allocation will be made for a VTABLE
virtual void __stdcall Fy2()=0; //which will contain function pointers
}; //to these interface functions. This
//is transparently done by the compiler
class CA : public IX, public IY //will inherit pure abstract base
{ //classes IX and IY.
public:
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv)
{
puts("Called QueryInterface()"); //QueryInterface(), AddRef()
return S_OK; //and Release() from IUnknown
} //will be implemented here as
//well as Fx1(), Fx2(), Fy1(),
virtual ULONG __stdcall AddRef() //and Fy2() so that the class
{ //can be instantiated. All pure
puts("Called AddRef()"); //virtual functions must be
return 0; //implemented for a class that
} //inherits them to be
//instantiable. For those who
virtual ULONG __stdcall Release() //wish to be able to pronounce
{ //'instantiable', practicing in
puts("Called Release()"); //front of a mirror helps.
return 0;
}
virtual void __stdcall Fx1(){printf("Called Fx1()\n");} //implementations
virtual void __stdcall Fx2(){printf("Called Fx2()\n");} //of inherited
virtual void __stdcall Fy1(){printf("Called Fy1()\n");} //pure virtual
virtual void __stdcall Fy2(){printf("Called Fy2()\n");} //functions.
};
int main(void)
{
DWORD* pVTbl=0; //will hold pointer to vtable (there are two - IX, IY vtables)
DWORD* VTbl=0; //address of vtable, i.e., pVTbl
CA* pCA=0; //pointer to CA instance
FN pFn=0; //function pointer for calling interface functions through address
DWORD i,j; //for loop iterators
pCA=new CA; //create new instance of CA
printf("sizeof(CA) = %u\t\t : An IX VTBL Ptr And A IY VTBL Ptr\n",sizeof(CA));
printf("pCA = %u\t : Ptr To IX VTBL\n",(unsigned int)pCA);
printf("*(DWORD*)(&pCA) = %u\t : Same Thing With Just More Convoluted Notation.\n",*(int*)(&pCA));
printf("*(DWORD*)*(DWORD*)(&pCA) = %u\t : Pointer to Pointer For IX::QueryInterface()\n",*(int*)*(int*)(&pCA));
pVTbl=(DWORD*)pCA;
printf("pVTbl = %u\n\n",(unsigned int)pVTbl);
printf("&pVTbl[i]\t&VTbl[j]\tpFn=VTbl[j]\tpFn()\n");
printf("=========================================================================\n");
for(i=0;i<2;i++) //Two VTABLES! Iterate Through Them. The 1st VTABLE Pointer Occupies Bytes
{ //0 - 3 Offset From pCA, i.e., here 3146624 - 314627. The 2nd VTABLE Ptr
VTbl=(DWORD*)pVTbl[i]; //is at Offset Bytes 4 - 7, i.e.,3146628 - 3146631. The 'j' loop iterates
for(j=0;j<5;j++) //through each VTABLE calling the five functions contained in each interface.
{ //The VTABLEs contain pointers to the respective interface functions.
printf
(
"%u\t\t%u\t\t%u\t\t", //The expression &pVTbl[i] will resolve to an address which contains a
(unsigned int)&pVTbl[i], //pointer to one of the two possible VTABLEs created when class CA was
(unsigned int)&VTbl[j], //instantiated. In this program run address 3146624 contains a pointer
(unsigned int)VTbl[j] //to the IX VTable, and when i=1 address 3146628 contains a pointer to
); //the IY VTABLE. Note that the variable VTbl is also an intger/DWORD
pFn=(FN)VTbl[j]; //pointer, so, when we store the base address of either of the VTABLEs
pFn(); //in VTbl, incrementing it through use of base pointer notation it will
} //make available the next function pointer stored in the VTABLE. The
printf("\n"); //pointer array VTbl[] contains function pointers to the respective
} //interface functions. With C or C++, a typedef'ed function pointer
delete pCA; //variable - here FN pFn - is usually created to take a function pointer
getchar(); //address to create a usable function pointer call.
return 0;
}
Perfect Output From Above!!!
sizeof(CA) = 8 : An IX VTBL Ptr And A IY VTBL Ptr
pCA = 3146576 : Ptr To IX VTBL
*(DWORD*)(&pCA) = 3146576 : Same Thing With Just More Convoluted Notation.
*(DWORD*)*(DWORD*)(&pCA) = 4219068 : Pointer to Pointer For IX::QueryInterface()
pVTbl = 3146576
&pVTbl[i] &VTbl[j] pFn=VTbl[j] pFn()
=========================================================================
3146576 4219068 4198656 Called QueryInterface()
3146576 4219072 4198688 Called AddRef()
3146576 4219076 4198720 Called Release()
3146576 4219080 4198752 Called Fx1()
3146576 4219084 4198768 Called Fx2()
3146580 4219048 4198816 Called QueryInterface()
3146580 4219052 4198832 Called AddRef()
3146580 4219056 4198848 Called Release()
3146580 4219060 4198784 Called Fy1()
3146580 4219064 4198800 Called Fy2()
Press any key to continue
So go figure. If ever there was a program that ought to crash its that one! Now see why I did what I did with Iunknown??? What actually happened is I tried the program immediately above In Dev-C++ and Code::Blocks and was getting crashes there, even though it was working perfect with Microsoft's compiler. I eventually ( a day or two) figgured out the problem was with the typedefs, so rather than have a much more complicated and longer program by fixing it I went to the 'artificial' Iunknown. Here is the program as it would have to be to use Iunknown correctly...
#include <stdio.h>
#include <objbase.h>
typedef HRESULT (*FN)(const IID&,void**);
typedef ULONG (*FN1)(void);
typedef void (*FN2)(void);
interface IX : IUnknown
{
virtual void __stdcall Fx1()=0;
virtual void __stdcall Fx2()=0;
};
interface IY : IUnknown
{
virtual void __stdcall Fy1()=0;
virtual void __stdcall Fy2()=0;
};
class CA : public IX, public IY
{
public:
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv)
{
puts("Called QueryInterface()");
return S_OK;
}
virtual ULONG __stdcall AddRef()
{
puts("Called AddRef()");
return 0L;
}
virtual ULONG __stdcall Release()
{
puts("Called Release()");
return 0L;
}
virtual void __stdcall Fx1(){printf("Called Fx1()\n");} //implementations
virtual void __stdcall Fx2(){printf("Called Fx2()\n");} //of inherited
virtual void __stdcall Fy1(){printf("Called Fy1()\n");} //pure virtual
virtual void __stdcall Fy2(){printf("Called Fy2()\n");} //functions.
};
int main(void)
{
const IID IID_IX={0x32bb8320,0xb41b,0x11cf,{0xa6,0xbb,0x0,0x80,0xc7,0xb2,0xd6,0x82}};
DWORD* pVTbl=0;
DWORD* VTbl=0;
IX* pIX=NULL;
HRESULT hr;
long iRet;
CA* pCA=0;
FN pFn=0;
FN1 pFn1=0;
FN2 pFn2=0;
pCA=new CA; //create new instance of CA
printf("sizeof(CA) = %u\t\t : An IX VTBL Ptr And A IY VTBL Ptr\n",sizeof(CA));
printf("pCA = %u\t : Ptr To IX VTBL\n",(unsigned int)pCA);
printf("*(DWORD*)(&pCA) = %u\t : More Convoluted Notation.\n",*(int*)(&pCA));
printf("*(DWORD*)*(DWORD*)(&pCA) = %u\t : Ptr to Ptr For IX::QueryInterface()\n",*(int*)*(int*)(&pCA));
pVTbl=(DWORD*)pCA;
printf("pVTbl = %u\n\n",(unsigned int)pVTbl);
printf("&pVTbl[i]\t&VTbl[j]\tpFn=VTbl[j]\tpFn()\n");
printf("=========================================================================\n");
VTbl=(DWORD*)pVTbl[0];
//QueryInterface()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[0],(unsigned int)&VTbl[0],(unsigned int)VTbl[0]);
pFn=(FN)VTbl[0];
hr=pFn(IID_IX,(void**)&pIX);
//AddRef()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[0],(unsigned int)&VTbl[1],(unsigned int)VTbl[1]);
pFn1=(FN1)VTbl[1];
iRet=pFn1();
//Release()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[0],(unsigned int)&VTbl[2],(unsigned int)VTbl[2]);
pFn1=(FN1)VTbl[2];
iRet=pFn1();
//Fx1()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[0],(unsigned int)&VTbl[3],(unsigned int)VTbl[3]);
pFn2=(FN2)VTbl[3];
pFn2();
//Fx2()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[0],(unsigned int)&VTbl[4],(unsigned int)VTbl[4]);
pFn2=(FN2)VTbl[4];
pFn2();
printf("\n");
VTbl=(DWORD*)pVTbl[1];
//QueryInterface()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[1],(unsigned int)&VTbl[0],(unsigned int)VTbl[0]);
hr=pFn(IID_IX,(void**)&pIX);
//AddRef()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[1],(unsigned int)&VTbl[1],(unsigned int)VTbl[1]);
pFn1=(FN1)VTbl[1];
iRet=pFn1();
//Release()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[1],(unsigned int)&VTbl[2],(unsigned int)VTbl[2]);
pFn1=(FN1)VTbl[2];
iRet=pFn1();
//Fy1()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[1],(unsigned int)&VTbl[3],(unsigned int)VTbl[3]);
pFn2=(FN2)VTbl[3];
pFn2();
//Fy2()
printf("%u\t\t%u\t\t%u\t",(unsigned int)&pVTbl[1],(unsigned int)&VTbl[4],(unsigned int)VTbl[4]);
pFn2=(FN2)VTbl[4];
pFn2();
printf("\n\n");
pCA->QueryInterface(IID_IX,(void**)&pIX);
pCA->AddRef();
pCA->Release();
pCA->Fx1();
pCA->Fx2();
pCA->Fy1();
pCA->Fy2();
delete pCA;
return 0;
}
/*
sizeof(CA) = 8 : An IX VTBL Ptr And A IY VTBL Ptr
pCA = 4144872 : Ptr To IX VTBL
*(DWORD*)(&pCA) = 4144872 : More Convoluted Notation.
*(DWORD*)*(DWORD*)(&pCA) = 4224296 : Ptr to Ptr For IX::QueryInterface()
pVTbl = 4144872
&pVTbl[i] &VTbl[j] pFn=VTbl[j] pFn()
=========================================================================
4144872 4224296 4214896 Called QueryInterface()
4144872 4224300 4215056 Called AddRef()
4144872 4224304 4215088 Called Release()
4144872 4224308 4214928 Called Fx1()
4144872 4224312 4214960 Called Fx2()
4144876 4224332 4215120 Called QueryInterface()
4144876 4224336 4215168 Called AddRef()
4144876 4224340 4215184 Called Release()
4144876 4224344 4215136 Called Fy1()
4144876 4224348 4215152 Called Fy2()
Called QueryInterface()
Called AddRef()
Called Release()
Called Fx1()
Called Fx2()
Called Fy1()
Called Fy2()
Process returned 0 (0x0) execution time : 0.000 s
Press any key to continue.
*/
And that was unacceptable to me for what I was trying to do, i.e., just concisely study memory layout. But I guess I'm not such a good C++ coder. Can anyone spot something I'm doing wrong? That 1st program I posted only contains 51 lines of code not counting curly braces and spaces. Why is that crashing??? Perhaps I'm worried I'm not understanding the memory layout. Its got me vexed. Just so as not to have no PowerBASIC code in this post, here is my rendition of what I think is going on with memory in terms of PB 4/5. 1st version with TYPEs to mimic C++ classes. Compiles with either PB 4 or 5...
#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
Output:
'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()
With primitive data types and arrays only...
#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
Output:
'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()
'
I was kind of hoping someone with more C++ skills than myself would be interested enough in what I'm trying to do to take a stab at this all...
Drove home from work and in the middle of eating supper had a brainstorm! I bet those crashes are being caused by my failing to specify __stdcall in the typedef for the function pointer being used. Its using cdecl I bet! Will try it as soon as possible!!!
Nope! Still no luck. What I didn't mention before is that I'm sure its calling the functions through the addresses that is causing the problems. If I remove the function call - pFn(); and add a printf("\n"); to replace the ones in the uncalled functions I get a good run with no GPFs. Also, if I remove all the __stdcall specifications I also get good runs. But I'm determined to get it with __stdcall.
I tried eliminating the typedef and just declaring a regular __stdcall function pointer like so...
void (__stdcall* pFn)(void);
and using that in the VTABLE array like so...
pFn=(void(__stdcall*)(void))VTbl[j];
pFn();
But its still erroring out. Here's my latest but it GPFs at end. I'll sleep on it. Tomorrow's another day. :)
#include <stdio.h>
#include <objbase.h>
interface IUnknwn //Alias IUnknown
{
virtual void __stdcall QueryVTables()=0;
virtual void __stdcall AddReference()=0;
virtual void __stdcall ReleaseRef()=0;
};
interface IX : IUnknwn
{
virtual void __stdcall Fx1()=0;
virtual void __stdcall Fx2()=0;
};
interface IY : IUnknwn
{
virtual void __stdcall Fy1()=0;
virtual void __stdcall Fy2()=0;
};
class CA : public IX, public IY
{
public:
virtual void __stdcall QueryVTables(){puts("Called QueryVTables()");}
virtual void __stdcall AddReference(){puts("Called AddReference()");}
virtual void __stdcall ReleaseRef(){puts("Called ReleaseRef()");}
virtual void __stdcall Fx1(){printf("Called Fx1()\n");} //implementations
virtual void __stdcall Fx2(){printf("Called Fx2()\n");} //of inherited
virtual void __stdcall Fy1(){printf("Called Fy1()\n");} //pure virtual
virtual void __stdcall Fy2(){printf("Called Fy2()\n");} //functions.
};
int main(void)
{
void (__stdcall* pFn)(void);
unsigned int* pVTbl=0;
unsigned int* VTbl=0;
unsigned int i=0,j=0;
CA* pCA=0;
pCA=new CA;
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++)
{
VTbl=(unsigned int*)pVTbl[i];
for(j=0;j<5;j++)
{
printf("%u\t\t%u\t\t%u\t\t",&pVTbl[i],&VTbl[j],VTbl[j]);
pFn=(void(__stdcall*)(void))VTbl[j];
pFn();
}
printf("\n");
}
delete pCA;
return 0;
}
Just got the dumb thing working. The program would be compiled to be expecting the hidden 'this' pointer as a parameter, even though the interface functions have void parameter lists. So to fix it all one needs to do is supply a function pointer like so...
pFn=(void(__stdcall*)(int))VTbl[j];
instead of the ones above...
pFn=(void(__stdcall*)(void))VTbl[j];
And when calling the functions using the function pointer, pass some number. The pointer to the class pCA is a likely canidate!
pFn((int)pCA);
Then everything works out.