Results 1 to 12 of 12

Thread: Trying to understand COM object memory structure and vtable calls

  1. #1

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 2013
    Posts
    302

    Trying to understand COM object memory structure and vtable calls

    Name:  f05QI04.JPG
Views: 152
Size:  17.2 KB

    Above is an example of a COM object vtable layaut.

    Since every interface is derived from IUnknown , If I wanted to make a vtable call to the ISum::Sum function , I would first have to determine its table offset counting from the IUknown::QueryInterface down to ISum::Sum which would be 7. Right ?

    Now, Suppose we decided that the above object would also implement the IDataObject Interface.
    Where in the table would the IDataObject Interface be located ? would be at the bottom of the vtable after ISum ?

    If so, and if we were to make a vtable call (via DispCallFunc API) to the first IDataObject function (IDataObject:: DAdvise) then based on the above logic,we have to pass Offset 8 ?

    However, IDataObject inherits from IUnknown so the vtable offset woudn't be 8 ... it would be 3 !!!

    Can anyone help me clear up the confusion I have.

    Regards.

  2. #2
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,102

    Re: Trying to understand COM object memory structure and vtable calls

    As far as the 1st question goes... Correct VTableAddress + 28 (7 * 4 in 32bit)

    The second question. That can be done a couple different ways, but it is not as you are describing. One way is that the QueryInterface method would know where the IDataObject interface's VTable is and return that interface. However, that would mean ISum object's IUnknown is customized.

    If it did implement IDataObject, then it would return an ObjPtr for that interface's IUnknown. And in that case, that object would also know where the ISum's IUnknown is located too.

    IUnknown interface itself has strict rules. Find an article regarding IUnknown also. QueryInterface is called whenever anyone wants to ask the object, "Do you support this or that interface?"

    Another way an object can inherit interfaces is via aggregation, yet another topic to look up. That is the norm I believe.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  3. #3

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 2013
    Posts
    302

    Re: Trying to understand COM object memory structure and vtable calls

    Thanks Lavolpe,

    I have taken a quick look at those topics (aggregation & Delegation) and they seem quite daunting but I'll keep on trying till I get more familiar with these concepts.

    If it did implement IDataObject, then it would return an ObjPtr for that interface's IUnknown
    Do you mean it would return the ObjPtr of the COM object ?

  4. #4
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,102

    Re: Trying to understand COM object memory structure and vtable calls

    Le's say I have a VB class that implements IDataObject among others. The class would have its own IUnknown, IDataObject has its own, etc.

    I have an ObjPtr to the class but want the ObjPtr to the IDataObject. I would call the class' IUnknown, passing it the GUID for IDataObject and ask the class if it implements/supports IDataObject. If the class does, that call would return the ObjPtr to the IDataObject. The way COM works, if I were later to ask that IDataObject if it supports the class, I would send it a request with the class' GUID and the IDataObject would return an ObjPtr to the class.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  5. #5
    Frenzied Member wqweto's Avatar
    Join Date
    May 2011
    Posts
    2,040

    Re: Trying to understand COM object memory structure and vtable calls

    @JAAFAR: On the picture on the coclass (the rectangle) you have 3 interfaces implemented (the circles). Think of these interfaces as if each conceptually has *separate* VTable like the list on the left.

    This means that first we have a VTable for the ISum interface which occupies 8 x sizeof(ptr) = 32 bytes with 8 function pointers like this
    Code:
    0000 pfnQI
    0004 pfnAddRef
    0008 pfnRelease
    000C pfnGetIDsOfNames
    0010 pfnGetTypeInfo
    0014 pfnGetTypeInfoCount
    0018 pfnInvoke
    001C pfnSum
    Then there is a separate VTable for the IDispatch, separate 28 bytes with only 7 function pointers like this
    Code:
    0000 pfnQI
    0004 pfnAddRef
    0008 pfnRelease
    000C pfnGetIDsOfNames
    0010 pfnGetTypeInfo
    0014 pfnGetTypeInfoCount
    0018 pfnInvoke
    And finally there is a separate VTable for IUnknown, separate 12 bytes with only 3 function pointers like this
    Code:
    0000 pfnQI
    0004 pfnAddRef
    0008 pfnRelease
    Now you want to add a new IDataObject interface. This will bring a 4-th VTable for the coclass with *separate* slots for its function pointers.

    First, notice that in the process memory you might have thousands of instances of the coclass but the VTables are present only once. Second, notice that pfnQI in each of these separate VTables points to the *same* function, so does pfnAddRef. Every pfn with matching name points to the same address. This is the reason why the first three VTables (for ISum, IDispatch and IUnknown) can be *merged* into a single VTable and it turns out that the union these VTables is exactly the VTable for ISum interface.

    This is strictly implementation details, an optimization which is conceptually not required. Conceptually each interface has a separate VTable but for the sake of reducing coclasses memory footpring compilers merge VTables of the base interfaces into *one* of the inheriting interfaces. If you think about it, it's *not* possible to merge the second inheriting interface -- e.g. the IDataObject interface -- as its custom methods will clash with the pfnSum slot of the ISum interface so IDataObject interface VTable has to remain separate and is located on a separate memory address.

    cheers,
    </wqw>

  6. #6

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 2013
    Posts
    302

    Re: Trying to understand COM object memory structure and vtable calls

    Quote Originally Posted by LaVolpe View Post
    Le's say I have a VB class that implements IDataObject among others. The class would have its own IUnknown, IDataObject has its own, etc.

    I have an ObjPtr to the class but want the ObjPtr to the IDataObject. I would call the class' IUnknown, passing it the GUID for IDataObject and ask the class if it implements/supports IDataObject. If the class does, that call would return the ObjPtr to the IDataObject. The way COM works, if I were later to ask that IDataObject if it supports the class, I would send it a request with the class' GUID and the IDataObject would return an ObjPtr to the class.
    Unless I misunderstood, that doesn't seem to apply when querying for the IDispatch interface and wanting to retrieve its ObjPtr .

    example:
    Code:
    aGUID(0) = &H20400: aGUID(2) = &HC0&: aGUID(3) = &H46000000
    
    CallFunction_COM objptr(TheObject), IUNK_QueryInterface, vbLong, CC_STDCALL, _
     VarPtr(aGUID(0)), VarPtr(IDispatch)
        
    Debug.Print objptr(TheObject) = objptr(IDispatch)  '<=== Returns TRUE
        
    CallFunction_COM VarPtr(IDispatch), IDSP_GetTypeInfo, vbLong, CC_STDCALL,  _
    0&, 0&, VarPtr(ITypeInfo)
    So since objptr(TheObject) = objptr(IDispatch), I could just use this simpler one-liner :
    Code:
    CallFunction_COM VarPtr(TheObject), IDSP_GetTypeInfo, vbLong, CC_STDCALL,  _
    0&, 0&, VarPtr(ITypeInfo)
    Last edited by JAAFAR; May 8th, 2020 at 04:45 PM.

  7. #7

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 2013
    Posts
    302

    Re: Trying to understand COM object memory structure and vtable calls

    Quote Originally Posted by wqweto View Post
    @JAAFAR:

    First, notice that in the process memory you might have thousands of instances of the coclass but the VTables are present only once. Second, notice that pfnQI in each of these separate VTables points to the *same* function, so does pfnAddRef. Every pfn with matching name points to the same address. This is the reason why the first three VTables (for ISum, IDispatch and IUnknown) can be *merged* into a single VTable and it turns out that the union these VTables is exactly the VTable for ISum interface.

    This is strictly implementation details, an optimization which is conceptually not required. Conceptually each interface has a separate VTable but for the sake of reducing coclasses memory footpring compilers merge VTables of the base interfaces into *one* of the inheriting interfaces. If you think about it, it's *not* possible to merge the second inheriting interface -- e.g. the IDataObject interface -- as its custom methods will clash with the pfnSum slot of the ISum interface so IDataObject interface VTable has to remain separate and is located on a separate memory address.
    Thanks wqweto for the detailed explanation.

    A couple of questions:

    1- Which is the correct statement based on the above vtable image respresentation in post#1?:
    ____a- ISum inherits from IDipatch or
    ____b- ISum inherits from IUnknow or
    ____c- ISum inherits from both IDspatch and IUnknown

    This is imporatant for when counting the offsets of each ISum function before passing the chosen offset to the DispCallFunc API.


    2-Now, If we were to add the IDataObject interface to the coclass, would the IDataObject then inherit from its own seperate vtable IUknown interface ?? (instead of inheriting from the merged vtable IUnknown like ISum and IDispatch do)

    3- Since many instances of a coclass use the same vtable , how does the code inside the vtable functions know which particular instance of the coclass requested the execution of the code ?

    Thanks.
    Last edited by JAAFAR; May 8th, 2020 at 07:36 PM.

  8. #8
    Frenzied Member wqweto's Avatar
    Join Date
    May 2011
    Posts
    2,040

    Re: Trying to understand COM object memory structure and vtable calls

    Quote Originally Posted by JAAFAR View Post
    1- Which is the correct statement based on the above vtable image respresentation in post#1?:
    ____a- ISum inherits from IDipatch or
    ____b- ISum inherits from IUnknow or
    ____c- ISum inherits from both IDspatch and IUnknown
    ISum directly inherits IDispatch the same way IDispatch directly inherits IUnknown. ISum indirectly inherits IUnknown.

    Quote Originally Posted by JAAFAR View Post
    This is imporatant for when counting the offsets of each ISum function before passing the chosen offset to the DispCallFunc API.
    Yes, count public methods after IDispatch slots.

    Quote Originally Posted by JAAFAR View Post
    2-Now, If we were to add the IDataObjectEx interface to the coclass, would the IDataObjectEx then inherit from its own seperate vtable IUknown interface ?? (instead of inheriting from the merged vtable IUnknown like ISum and IDispatch do)
    There is going to be e separate *full* VTable for IDataObjectEx which will contain slots for all the methods of IDispatch and IUknown too. The slots for IDispatch/IUnknown in this second VTable will point to the same addresses/functions as the corresponding slots in ISum's VTable so these will efectively execute the same code when invoked.

    Quote Originally Posted by JAAFAR View Post
    3- Since many instances of a coclass use the same vtable , how does the code inside the vtable functions know which particular instance of the coclass requested the execution of the code ?
    Simple, each of these functions operates solely on its first so called instance pointer (this in terms of C/C++ and Me in terms of VB6) so invoking instance1.Sum(. . .) vs instance2.Sum(. . .) calls the same function but with different first parameter and underneath the code is translated to (is lowered as) normal function call ISum::Sum(instance1, . . .) vs ISum::Sum(instance2, . . .) -- same function executed, different object to act upon in first parameter.

    Edit: In Rules for Implementing QueryInterface there is this identity rule which means that ISum::QI for IID_IUnknown and IDataObjectEx::QI for IID_IUnknown *must* return the same ObjPtr. This is *not* required for IID_IDispatch though and VB6 actually always does it for implemented interfaces to be scripting callable.
    Code:
    Dim oInstance As CoClass
    Dim pSum As ISum
    Dim pDataObject As IDataObjectEx
    Dim pUnk1 As IUnknown
    Dim pUnk2 As IUnknown
    Dim pDisp1 As IDispatch
    Dim pDisp2 As IDispatch
    
    Set pSum = oInstance
    Set pDataObject  = oInstance
    Set pUnk1 = pSum
    Set pUnk2 = pDataObject
    Debug.Assert ObjPtr(pUnk1) = ObjPtr(pUnk2)
    Set pDisp1 = pSum
    Set pDisp2 = pDataObject
    Debug.Assert ObjPtr(pDisp1) <> ObjPtr(pDisp2)
    Set pUnk1 = pDisp1
    Set pUnk2 = pDisp2
    Debug.Assert ObjPtr(pUnk1) = ObjPtr(pUnk2)
    (air code)

    cheers,
    </wqw>

  9. #9
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,102

    Re: Trying to understand COM object memory structure and vtable calls

    For clarity when mentioning IDataObject.

    IDataObject {0000010E-0000-0000-C000-000000000046} does not inherit IDispatch
    VB's DataObject {41A7D760-6018-11CF-9016-00AA0068841E} does inherit IDispatch

    VB' is likely using a real IDataObject behind the scenes (delegation?) and VB's DataObject looks to be a customized subset of the actual's methods plus supporting IDispatch.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  10. #10
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    19,102

    Re: Trying to understand COM object memory structure and vtable calls

    Quote Originally Posted by JAAFAR View Post
    Unless I misunderstood, that doesn't seem to apply when querying for the IDispatch interface and wanting to retrieve its ObjPtr .

    example:
    Code:
    aGUID(0) = &H20400: aGUID(2) = &HC0&: aGUID(3) = &H46000000
    
    CallFunction_COM objptr(TheObject), IUNK_QueryInterface, vbLong, CC_STDCALL, _
     VarPtr(aGUID(0)), VarPtr(IDispatch)
        
    Debug.Print objptr(TheObject) = objptr(IDispatch)  '<=== Returns TRUE
        
    CallFunction_COM VarPtr(IDispatch), IDSP_GetTypeInfo, vbLong, CC_STDCALL,  _
    0&, 0&, VarPtr(ITypeInfo)
    So since objptr(TheObject) = objptr(IDispatch), I could just use this simpler one-liner :
    Code:
    CallFunction_COM VarPtr(TheObject), IDSP_GetTypeInfo, vbLong, CC_STDCALL,  _
    0&, 0&, VarPtr(ITypeInfo)
    It is as I described. Need to better understand QueryInterface. This stuff is not easy first time we begin to play with it.

    The ObjPtr of a VB object will always be IDispatch. In short, when declaring a variable as Object or some VB object, you are setting it to its IDispatch. Any object points to an IDispatch. If the interface does not derive from IDispatch, you cannot set it to an Object variable: Type-Mismatch

    simple experiment to try to highlight what is going on, but may just confuse you more?
    Code:
        Dim oIUnknown As stdole.IUnknown ' IUnknown
        Dim oIUnknown2 As stdole.IUnknown ' IUnknown
        Dim oPic As StdPicture ' IDispatch
        Dim oIPicture As IPicture ' IDispatch
        Dim oIPicDisp As IPictureDisp ' IDispatch
    
        Set oPic = New StdPicture ' CoClass
        
        ' QI = QueryInterface
        Set oIPicDisp = oPic ' calls stdPicture QI passing IPictureDisp GUID
        Set oIPicture = oIPicDisp ' calls IPictureDisp QI passing IPicture GUID
        Debug.Print ObjPtr(oIPicDisp) = ObjPtr(oIPicture)   ' false, 2 different interfaces; mind blown?
        
        Set oIUnknown2 = oIPicDisp ' calls IPicDisp QI passing IUnknown GUID
        Debug.Print ObjPtr(oIPicDisp) = ObjPtr(oIUnknown2)  ' false, 2 different interfaces
        
        Set oIUnknown = oIPicture' calls IPicture QI passing IUnknown GUID
        Debug.Print ObjPtr(oIPicture) = ObjPtr(oIUnknown)   ' true, same interface
        
        Debug.Print ObjPtr(oIUnknown) = ObjPtr(oIUnknown2)  ' true
        
        ' VB's stdPicture's default interface is IPicture. When querying for stdPicture's IUknown,
        ' it returns the IUnknown on its default IPicture
        
        ' The IPictureDisp is the other part of the coclass, a dispatch-only interface &
        ' has its own VTable.
    Last edited by LaVolpe; May 9th, 2020 at 12:22 PM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  11. #11

    Thread Starter
    Hyperactive Member
    Join Date
    Nov 2013
    Posts
    302

    Re: Trying to understand COM object memory structure and vtable calls

    Thanks Lavolpe and wqweto for your valuable explanations.

    I will study this and get back later.

    Regards.
    Last edited by JAAFAR; May 11th, 2020 at 03:43 AM.

  12. #12
    Frenzied Member wqweto's Avatar
    Join Date
    May 2011
    Posts
    2,040

    Re: Trying to understand COM object memory structure and vtable calls

    Just remember that the *only* equality of ObjPtr baked into COM (i.e. ObjPtr(pThis) = ObjPtr(pThat)) is guaranteed for the IUnknown interface of a coclass. The coclass might have hundrends of interfaces implemented but when each is QI for it's IUnknown they have to return the same address, so that this address can be treated as "identity address" for the coclass. This allows COM to answer the question "Is this ISum interface on the same object as this IDataObjectEx interface?" very easily.

    This is what Is operator in VB6 does. You might have ObjPtr(pThis) <> ObjPtr(pThat) but pThis Is pThat stil being true -- so both interfaces have different addresses in memeory (an implementation detail) but they are actually pointing to the same object. The idea behind Is operator is to first QI both arguments for IUnknown and then compare these IUnknown interface addresses.

    Every other interface might return a separate address with separate VTable which means that generally you cannot rely on addresses matching even when upcasting. I.e. from ISum when upcast to IDispatch you might get different or the same address but COM does not guarantee neither.

    cheers,
    </wqw>

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width