I am trying to understand COM memory layout and using C++ classes just confuses me. In general I understand what is going on with the double indirection of function pointers but as so often is the case in programming one must understand the ugly details – not just the generalities. So I thought I would create a very simple example and attempt to call a few functions contained in a couple interfaces without using typical C++ object call syntax but just using C style function pointers and addresses I could dig/pry/coax out of a C++ class. I’ve been largely successful in this endeavor I think but I am having some difficulties that I can’t explain. I would certainly appreciate someone’s looking at this who is knowledgable in these matters (and who has perhaps waded through this stuff him/herself!). For others just learning COM there may be some merit in trying to follow what I am doing here.
Here is what I’ve done. I created two interfaces IX and IY that each inherit from Iunknown and each contain just two functions, e.g., Fx1(), Fx2(), Fy1(), and Fy2(). They are pure virtual functions within these two interfaces and are implemented in class CA where they are publically inherited. Class CA contains nothing else to complicate matters. The implementation of these functions in class CA just outputs a message that they were called. Here is the very simple construct…
interface IX : IUnknown //will cause inheritance of
{ //QueryInterface(),AddRef(),
virtual void __stdcall Fx1()=0; //and Release().
virtual void __stdcall Fx2()=0;
};
Interface IY : IUnknown
{
virtual void __stdcall Fy1()=0;
virtual void __stdcall Fy2()=0;
};
class CA : public IX, public IY //publically inherit interfaces IX and IY
{
public:
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv)
{
puts("\nCalled QueryInterface()");
return S_OK;
}
virtual ULONG __stdcall AddRef()
{
puts("Called AddRef()");
return 0;
}
virtual ULONG __stdcall Release()
{
puts("Called Release()");
return 0;
}
virtual void __stdcall Fx1(){printf("Called Fx1()\n");}
virtual void __stdcall Fx2(){printf("Called Fx2()\n");}
virtual void __stdcall Fy1(){printf("Called Fy1()\n");}
virtual void __stdcall Fy2(){printf("Called Fy2()\n");}
};
As you can see QueryInterface(), AddRef(), and Release() were also implemented to just make the class instantiable. From my reading of an article by Jeff Glatt “COM In Plain C” which I believe was pretty widely read on the net…
http://www.codeproject.com/KB/COM/com_in_c1.aspx
Jeff makes the statement that “a C++ class is really nothing more than a struct whose first member is always a pointer to an array – an array that contains pointers to all the functions inside that class”. This idea reaffirmed a post I discovered here at daniwem.com from March 10, 2008 by Asadullah at…
http://www.daniweb.com/forums/showthread.php?p=556791&highlight=VTABLE#post556791
Where Asadullah Ansari methodically disects classes and vtables to call virtual functions just with C addresses and some rather nasty casting. Without Asadullah’s invaluable post I’m not sure I would have made the progress with this that I have because the casting becomes really, really ugly, to put it mildly.
So that’s what I’m attempting to do here. Its my hope that this exercise will further cement my understanding of COM memory layout. Here goes. In my main() function the first thing I do is declare a pointer to class CA and use it to create an new instance of CA as follows…
pCA=new CA;
printf("sizeof(CA) = %u\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("*(int*)(&pCA) = %u\t : Same Thing With Just More Involved Notation.\n", *(int*)(&pCA));
printf("*(int*)*(int*)(&pCA) = %u\t : Should Point To IX::QueryInterface()???\n", *(int*)*(int*)(&pCA));
The output from those statements are as follows…
sizeof(CA) = 8 : An IX VTBL Ptr And A IY VTBL Ptr
pCA = 4144888 : Ptr To IX VTBL
*(int*)(&pCA) = 4144888 : Same Thing With Just More Convoluted Notation.
*(int*)*(int*)(&pCA) = 4224168 : Should Point To IX::QueryInterface()???
So far so good, I think! My understanding of class construction is that there should be a pointer to a vtable for each interface that is being inherited. Here, since we are inheriting both IX and IY that would make two pointers or 8 bytes. In the program I also declared an int* named dwPtr that I set equal to pCA to allow me to use base pointer offset notation to point to each of the two vtables as needed, i.e.,
int* dwPtr=0;
dwPtr=(int*)pCA;
So, to point sequentially to each of the vtables this would work…
For(i=0; i<2; i++)
printf(“%u\t%u\n”, i, &dwPtr[I]);
// Output:
//
// i &dwPtr[I]
// ==========================================
// 1 4144888 points to IX VTable
// 2 4144892 points to IY Vtable
Since we’ve located the respective Vtables in memory it should be thoretically possible to call the IX and IY interface functions through their function addresses within each Vtable. To that end I created a simple typedef to make the function pointer calls in the Vtable easier…
typedef void (*FN)(void);
One last issue before I present main() and the whole program together (its only 78 lines counting spaces) is that since interfaces IX and IY both themselves inherit the memory layout of Iunknown, the first three functions in each Vtable will be QueryInterface(), AddRef(), and Release(). Here is the whole program, and the output directly follows…
(sorry about the formatting. I struggled to make it fit better here but it just runs too wide. You'll have to 'Toggle Plain Text' or better yet post in a code editor)
#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().
virtual void __stdcall Fx2()=0;
};
interface IY : IUnknown
{
virtual void __stdcall Fy1()=0;
virtual void __stdcall Fy2()=0;
};
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("\nCalled 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.
puts("Called AddRef()");
return 0;
}
virtual ULONG __stdcall Release()
{
puts("Called Release()");
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)
{
unsigned int i;
int* dwPtr=0;
CA* pCA=0;
FN pFn=0;
pCA=new CA;
printf("sizeof(CA) = %u\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("*(int*)(&pCA) = %u\t : Same Thing With Just More Involved Notation.\n",*(int*)(&pCA));
printf("*(int*)*(int*)(&pCA) = %u\t : Should Point To IX::QueryInterface()???\n",*(int*)*(int*)(&pCA));
dwPtr=(int*)pCA;
printf("dwPtr = %u\n",(unsigned int)dwPtr);
for(i=0;i<2;i++)
{
pFn=(FN)*((int*)*(&dwPtr[i])+0);//QueryInt @offset 0 in ith vtbl
pFn();
pFn=(FN)*((int*)*(&dwPtr[i])+1);//AddRef() @offset 1 in ith vtbl
pFn();
pFn=(FN)*((int*)*(&dwPtr[i])+2);//Release() @offset 2 in ith vtbl
pFn();
pFn=(FN)*((int*)*(&dwPtr[i])+3);//Fx1,Fy1 @offset 3 in ith vtbl
pFn();
pFn=(FN)*((int*)*(&dwPtr[i])+4);//Fx2,Fy2 @offset 4 in ith vtbl
pFn();
}
delete pCA;
getchar();
return 0;
}
And here is the output…
/*
sizeof(CA) = 8 : An IX VTBL Ptr And A IY VTBL Ptr
pCA = 4144888 : Ptr To IX VTBL
*(int*)(&pCA) = 4144888 : Same Thing With Just More Involved Notation.
*(int*)*(int*)(&pCA) = 4224168 : Should Point To IX::QueryInterface()???
dwPtr = 4144888
Called QueryInterface()
Called AddRef()
Called Release()
Called Fx1()
Called Fx2()
Called QueryInterface()
Called AddRef()
Called Release()
Called Fy1()
Called Fy2()
*/
Now for the questions and problems. I have three different C++ compiler / development environments with which I can test this program. Using Microsoft’s Visual C++ 6 the program seems to work flawlessly and produces the above output. Using the new Code::Blocks IDE version 8.02 I just installed a few weeks ago Which uses the GNU gcc compiler suite the program produces the above output exactly the same as the MS VC++ package, but when I hit the [ENTER] key to get through the getchar() at program termination I get a GPF. I also have the Dev C++ 4.9.9.2 Bloodshed Suite which uses another separate installation of the GNU gcc compiler suite and this is GPF’ing after the “Called Fy1()” line. So something is wrong but I’m not sure what. My guess would be somewhere in the bizarre casting that has to take place to be able to iterate through pointers that are no longer pointing to CA objects but to int* in the vtables.
I’m aware that my typedef void (*FN)(void) doesn’t accurately reflect the QueryInterface(), AddRef(), and Release() function returns and signatures, but adding and using modified typedefs for these doesn’t change the results at all, so I don’t believe that is the problem.
My concern is that I’m failing to understand the memory layout, but if my understanding was poor and I’m laboring under major misconceptions I don’t think I would have gotten as far as I did. I really need to understand this because I’d like to be able to create COM components in other languages besides C++ and for that I need to completely understand this.
This business of the 1st members of a class being pointers to one or multiple VTABLES – is that a standardized C++ construction or just one implemented by the MS and GNU compilers I’ve used? It kind of seems pretty standard to me.
If anyone has any comments on what I’ve done or corrections I’d be glad. Again, I realize it would be easier to just do this – pCA->Fx1() – but that’s not the point. I’d particularly like to know why the crashes at program termination with some of the compilers.
If Asadullah Ansari is listening I’d especially like to thank him for his posts of March 10, 2008 where his coverage of some of these issues allowed me to make what progress I have managed with it here. Certainly, every post isn’t equally useful to everyone.