Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

ObjPtr() Example, VTables, and Object Memory

  • Filter
  • Time
  • Show
Clear All
new posts

    ObjPtr() Example, VTables, and Object Memory

    #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()
      'Call methods using interface/vtable
      'pointers.  1st I_X
      Print "pVTbl      = " pVTbl
      Print "VTbl       = " VTbl
      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!)"
           Print i, Varptr(@VTbl[i]), @VTbl[i],;
           Call DWord @VTbl[i] Using pFn(0)
        End If
      Next i
      'Then I_Y
      Print "pVTbl      = " pVTbl
      Print "VTbl       = " VTbl
      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!)"
           Print i, Varptr(@VTbl[i]), @VTbl[i],;
           Call DWord @VTbl[i] Using pFn(0)
        End If
      Next i
    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.

    #Compile Exe                        'Two levels of indirection using pointers
    #Dim All                            'are fundamental to COM's design.  When
    #Include ""             '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
           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
         Next i
         Erase I_X, I_Y
         Call GlobalFree(pVTbl)
      End If
    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.

    #Compile Exe
    #Dim All
    #Include ""
    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)
      If pCA Then
         Print "pCA        = " 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]
         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
           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
         Next i
         Call GlobalFree(pCA)
      End If
    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.

    #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]);
     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'.
     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()
    Last edited by Fred Harris; 10 Sep 2008, 01:30 PM. Reason: fix dumb error (left number in pointer brackets instead of i