• Welcome to Jose's Read Only Forum 2023.
 

Trying To Figure Out Basic COM Memory Layout

Started by Frederick J. Harris, September 05, 2008, 10:27:45 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

     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...

Frederick J. Harris

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!!!

Frederick J. Harris

#2
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;                         
}                                 
 

 

Frederick J. Harris

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.