previous page
next page

Extensibility

COM_INTERFACE_ENTRY_FUNC and COM_INTERFACE_ENTRY_FUNC_BLIND

ATL provides two macros for putting raw entries into the interface map:

#define COM_INTERFACE_ENTRY_FUNC(iid, dw, func) \ 
  { &iid, dw, func },                         
                                                  
#define COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func) \
  { NULL, dw, func },                             

These macros are the universal back door to ATL's implementation of QueryInterface. If you come up with another way of exposing COM interfaces, you can use these macros to achieve them, as long as it lives up to the laws of COM identity.

Direct Access to the this Pointer

One identity trick you can perform using COM_INTERFACE_ENTRY_FUNC was kicked around the ATL Mailing List for quite a while but was ultimately perfected by Don Box. (A slightly modified version of his solution is provided next.) In Chapter 4, "Objects in ATL," I presented the CreateInstance static member functions of CComObject, CComAgg-Object, and CComPolyObject when using private initialization. The CreateInstance method performed the same job as a Creator would but returned a pointer to the this pointer of the object instead of to only one of the interfaces. This was useful for calling member functions or setting member data that was not exposed via interfaces. We used this technique because it was unsafe to perform a cast. However, why not make QueryInterface perform the cast safely? In other words, why not add an entry to the interface map that returns the object's this pointer? Imagine a global function with the following implementation:

inline
HRESULT WINAPI _This(void* pv, REFIID iid,
  void** ppvObject, DWORD) {
  ATLASSERT(iid == IID_NULL);
  *ppvObject = pv;
  return S_OK;
}

This function takes the first parameter, pv, which points to the object's this pointer and hands it out directly in ppvObject. Notice also that this function does not AddRef the resultant interface pointer. Because it's returning an object pointer, not an interface pointer, it's not subject to the laws of COM. Remember, the this pointer is useful only within the server. To make sure that any out-of-apartment calls to QueryInterface fail, be sure to pick an interface ID without a proxy-stub, such as IID_NULL.

For example, imagine implementations of the following interfaces, creating a simple object model:

interface IBalloonMan : IUnknown {
    HRESULT CreateBalloon(long rgbColor, IBalloon** ppBalloon);
    HRESULT SwitchColor(IBalloon* pBalloon, long rgbColor);
};

interface IBalloon : IUnknown {
    [propget] HRESULT Color([out, retval] long *pVal);
};

Notice that the balloon's color can't be changed via IBalloon, but the implementation of IBalloonMan can give you a balloon of the color you want. If the implementations of IBalloonMan and IBalloon share the same server, the implementation of IBalloon can expose its this pointer via the _This function like this:

class ATL_NO_VTABLE CBalloon :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CBalloon>,
  public IBalloon {
public:
DECLARE_REGISTRY_RESOURCEID(IDR_BALLOON)
DECLARE_NOT_AGGREGATABLE(CBalloon)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CBalloon)
  COM_INTERFACE_ENTRY(IBalloon)
  COM_INTERFACE_ENTRY_FUNC(IID_NULL, 0, _This)
END_COM_MAP()
    // IBalloon
public:
  STDMETHOD(get_Color)(/*[out, retval]*/ long *pVal);

private:
  COLORREF m_rgbColor;
  friend class CBalloonMan;
};

Because CBalloonMan is a friend of CBalloon, CBalloonMan can set the private color data member of CBalloon, assuming that it can obtain the object's this pointer. CBalloon's special entry for IID_NULL lets CBalloonMan do just that:

STDMETHODIMP CBalloonMan::CreateBalloon(long rgbColor,
  IBalloon** ppBalloon) {
  // Create balloon
  *ppBalloon = 0;
  HRESULT     hr = CBalloon::CreateInstance(0, ppBalloon);

  if( SUCCEEDED(hr) ) {
    // Use backdoor to acquire CBalloon's this pointer
    CBalloon* pBalloonThis = 0;
    hr = (*ppBalloon)->QueryInterface(IID_NULL,
       (void**)&pBalloonThis);
    if( SUCCEEDED(hr) ) {
       // Use CBalloon's this pointer for private initialization
       pBalloonThis->m_rgbColor = rgbColor;
    }
  }
  return hr;
}

The benefit to this technique over the private initialization technique that the CreateInstance member function of CComObject et al exposes is that the this pointer can be obtained after creation:

STDMETHODIMP CBalloonMan::SwitchColor(IBalloon* pBalloon,
  long rgbColor) {
  // Use backdoor to acquire CBalloon's this pointer
  CBalloon*   pBalloonThis = 0;
  HRESULT hr = pBalloon->QueryInterface(IID_NULL, (void**)&pBalloonThis);
  if (SUCCEEDED(hr)) {
    hr = pBalloonThis->m_rgbColor = rgbColor;
  }
  return hr;
}

Clearly, this technique is a back-door hack with limited usefulness; it should not be used to subvert the binary boundary between client and object. However, it does have its own special charm. For objects that share the same apartment in the same server, it's a valid way to discover just who is who. If you find yourself using this technique, you might find the following macro to be a useful shortcut:

#define COM_INTERFACE_ENTRY_THIS() \
        COM_INTERFACE_ENTRY_FUNC(IID_NULL, 0, _This)

Per-Object Interfaces

Sometimes it's useful to handle interfaces on a per-object basis instead of a per-class basis. Another friend of mine, Martin Gudgin, provided the following example. If you want to implement something known as a "smart proxy," you have to keep track of the list of interfaces each object supports, and you might not know what those are until runtime. Each smart proxy object has its own list of interfaces, which can easily be managed by a member function. Unfortunately, interface maps can't hold member functions (believe me, I tried). However, you can use a combination of COM_INTERFACE_ENTRY_FUNC_BLIND and a static member function to perform the forwarding to a member function:

class ATL_NO_VTABLE CSmartProxy :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CSmartProxy, &CLSID_SmartProxy>,
  public IUnknown
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_SMARTPROXY)
DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CSmartProxy)
  COM_INTERFACE_ENTRY(IUnknown)
  COM_INTERFACE_ENTRY_FUNC_BLIND(0, _QI)
END_COM_MAP()

public:
  static HRESULT WINAPI _QI(void* pv, REFIID iid,
    void** ppvObject, DWORD) {
    // Forward to QI member function
    return ((CSmartProxy*)pv)->QI(iid, ppvObject);
  }

  // Per-object implementation of QI
  HRESULT QI(REFIID riid, void** ppv);
};

Of course, you might wonder why you'd go to all this trouble to get back to the per-object implementation of QueryInterface that you've come to know and love, but that's ATL.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)