January 1st, 2024

How do I prevent my ATL class from participating in COM aggregation? DECLARE_NOT_AGGREGATABLE didn’t work

Consider the following ATL class declaration:

class Widget :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<Widget>,
    public IAgileObject
{
public :
    DECLARE_NOT_AGGREGATABLE(Widget)

    BEGIN_COM_MAP(Widget)
        COM_INTERFACE_ENTRY(IAgileObject)
    END_COM_MAP()

    ⟦ ... ⟧
};

This class says that it is not aggregatable.

Hold my beer.

HRESULT CreateAggregatedWidget(
    IUnknown* punkOuter, REFIID riid, void** ppv)
{
    return
        CComCreator<CComAggObject<Widget>>::
            CreateInstance(punkOuter, riid, pppv);
}

There, I aggregated your allegedly non-aggregatable object.

The DECLARE_NOT_AGGREGATABLE macro is an instruction to the class factory to disallow aggregation, but if you create the object by means other than the class factory, then the macro has no effect. Creating the object without using the class factory is common for objects created internally.

So how do you mark your class so that any attempt to aggregate the object encounters a compiler error?

I found a sneaky trick.

The CComAggObject template sets up aggregation by creating the inner object and overriding its IUnknown methods to forward to the controlling unknown, kept in the member variable m_pOuterUnknown. The connection is made here:

template <class Base>
class CComContainedObject :
    public Base
{
public:
    typedef Base _BaseBlass;

    CComContainedObject(void* pv)
    {
        this->m_pOuterUnknown = (IUnknown*)pv;
    }

If we can make this line of code produce an error, then we can prevent people from instantiating CComContainedObject<Widget> and thereby prevent them from putting it in a CComAggObject.

I had multiple ideas for how to accomplish this, but the simplest was this:

class Widget :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<Widget>,
    public IAgileObject
{
public :
    DECLARE_NOT_AGGREGATABLE(Widget)

    static constexpr void* m_pOuterUnknown = nullptr;

    BEGIN_COM_MAP(Widget)
        COM_INTERFACE_ENTRY(IAgileObject)
    END_COM_MAP()

    ⟦ ... ⟧
};

This shadows the m_pOuterUnknown member from CComObjectRootEx, so when CComContainedObject tries to do a this->m_pOuterUnknown, it gets the Widget one. And since the Widget one is marked constexpr, it cannot be modified.

error C3892: 'm_pOuterUnknown': you cannot assign to a variable that is const

If you are compiling with a version of C++ that doesn’t support constexpr (which is not entirely out of the question given that you’re using ATL, and ATL was written in 1996, over a decade before constexpr was a twinkle in Gabriel and Bjarne’s eyes), you can use this alternate shadowing definition:

    static void m_pOuterUnknown() {}

This makes m_pOuterUnknown a function, and you cannot assign to a function.

error C2659: '=': function as left operand
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.