Adjustor thunks


Yesterday we learned about the layout of COM objects and I hinted at "adjustor thunks".

If you find yourself debugging in disassembly, you'll sometimes find strange little functions called "adjustor thunks". Let's take another look at the object we laid out last time:

class CSample : public IPersist, public IServiceProvider
{
public:
  // *** IUnknown ***
  STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
  STDMETHODIMP_(ULONG) AddRef();
  STDMETHODIMP_(ULONG) Release();

  // *** IPersist ***
  STDMETHODIMP GetClassID(CLSID* pClassID);

  // *** IQueryService ***
  STDMETHODIMP QueryService(REFGUID guidService,
                  REFIID riid, void** ppv);
private:
  LONG m_cRef;
  ...
};
p    lpVtbl    QueryInterface (1)
q    lpVtbl    QueryInterface (2) AddRef (1)
m_cRef AddRef (2) Release (1)
... Release (2) GetClassID (1)
QueryService (2)

In the diagram, p is the pointer returned when the IPersist interface is needed, and q is the pointer for the IQueryService interface.

Now, there is only one QueryInterface method, but there are two entries, one for each vtable. Remember that each function in a vtable receives the corresponding interface pointer as its "this" parameter. That's just fine for QueryInterface (1); its interface pointer is the same as the object's interface pointer. But that's bad news for QueryInterface (2), since its interface pointer is q, not p.

This is where the adjustor thunks come in.

The entry for QueryInterface (2) is a stub function that changes q to p, and then lets QueryInterface (1) do the rest of the work. This stub function is the adjustor thunk.

[thunk]:CSample::QueryInterface`adjustor{4}':
  sub     DWORD PTR [esp+4], 4 ; this -= sizeof(lpVtbl)
  jmp     CSample::QueryInterface

The adjustor thunk takes the "this" pointer and subtracts 4, converting q into p, then it jumps to the QueryInterface (1) function to do the real work.

Whenever you have multiple inheritance and a virtual function is implemented on multiple base classes, you will get an adjustor thunk for the second and subsequent base class methods in order to convert the "this" pointer into a common format.

Comments (15)
  1. Turn away from the darkside young skywalker. Walk into the light ;)

  2. geraldH says:

    Note: Diagram only visible in Internet Explorer. Mozilla and Opera won’t display any arrows nor the right structure.

    Please, Raymond… next time, use a format everybody can see?

  3. jeffdav says:

    Hm. I always wondered how that worked.

  4. Jim Causey says:

    Raymond’s diagrams work just fine in Mozilla Firebird; maybe you need to upgrade your browser?

  5. rfredell says:

    Works fine in Mozilla 1.6 too.

  6. brion says:

    No arrows in Mozilla 1.6 here (tested Win98 and Mac OS X). Positioning of the cells looks okay, but it’s kind of unclear without the arrows.

    For one thing I suspect that Mozilla doesn’t believe it’s kosher to slip XML into a document which isn’t parsable as well-formed XML and is labeled clearly as HTML 4.0 Transitional to boot. ;)

  7. Raymond Chen says:

    Sorry, I thought it was well-formed XML. Aside from a missing </TD> in the first table, everything seems to parse okay. What did I mess up?

    <v:shapetype id="arrow" coordsize="1,1"

    strokecolor="black" strokeweight="1pt">

    <v:stroke endarrow="classic" />

    <v:path v="m0,0 l 100,0 e" />

    </v:shapetype>

  8. brion says:

    The page as a whole declares itself to be HTML 4.0 Transitional, but doesn’t validate as either HTML 4.0 or XML. (Try validator.w3.org… it’s very nitpicky!)

    However, as far as I can tell nothing supports VML except IE/Win anyway.

  9. Raymond Chen says:

    Alas, the claim to be HTML 4.0 Transitional is coming from the blog software, not from me.

  10. No arrows here in Internet Explorer,

    Version: 6.0.2800.1106.xpsp2.030422-1633

    […]

    ???????:; SP1; 3823; Q330994; Q824145; Q832894;

    (Also I really love how Internet Explorer lets me read that information and type it back in myself at the keyboard. If I had to use the mouse to copy and paste, it would be too easy. Internet Explorer is as helpful as Visual Studio .NET 2003 is in this regard.)

  11. Fuggles says:

    If one is interested in the underlying COM mechanisms it is useful to read the ‘COM Programmer’s Cookbook’ as samples are largely provided in C. This removes the fog of the underlying compiler tricks.

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncomg/html/msdn_com_co.asp

    The layout of CSample as a C struct would be:

    typedef struct {

    IPersist IPersistIFace;

    IQueryService IQueryServiceIFace

    LONG m_cRef;

    } CSample;

    The author provides an ‘adjustor thunk macro’. So to implement the thunk function for IQueryService::QueryInterface:

    STDMETHODIMP IQS_QueryInterface(IQueryService * This, REFIID riid, void ** ppv)

    {

    CSample * ThisObj = IMPL (CSample, IQueryServiceIFace, This);

    return CSBase_QueryInterface(ThisObj, riid, ppv);

    }

    The definition for IMPL is essentially:

    #define IMPL(class, member, pointer)

    ((class *) (((long) pointer) – offsetof (class, member)))

    So:

    CSample * ThisObj = This – 4;

    The author also provides a FindImpl function which relieves us from having to include these thunk functions by ‘searching’ for the base class.

  12. Raymond Chen says:

    Aw phooey. I edited this entry to add the link, and in the process I destroyed the arrows. Let me fix them.

  13. A co-worker came by to ask what he thought was a coding "style" question that turned into a correctness

Comments are closed.