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