Are aggregated objects forced to be an IUnknown reference?

2

I am trying to implement shared logic using COM aggregation with ATL. I've defined a base class, called CameraBase, that is only available through aggregation. Therefor I've added the aggregateable annotation to it's coclass-declaration.

[
    uuid(...),
    aggregatable
]
coclass CameraBase
{
    [default] interface ICamera;
};

I've also added the DECLARE_ONLY_AGGREGATEABLE macro to the class definition.

class ATL_NO_VTABLE CCameraBase :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CCameraBase, &CLSID_CameraBase>,
    public ISupportErrorInfo,
    public IProvideClassInfoImpl<...>,
    public IDispatchImpl<...>
{
public:
    CCameraBase()
    {
    }

    DECLARE_REGISTRY_RESOURCEID(IDR_CAMERABASE)

    DECLARE_ONLY_AGGREGATABLE(CCameraBase)

    BEGIN_COM_MAP(CCameraBase)
        ...
    END_COM_MAP()

    DECLARE_PROTECT_FINAL_CONSTRUCT()

    ...
}

Now I have different classes who are using the logic of CameraBase somewhere. Therefor I've extented the com map of the parent class (e.g. SampleCamera):

BEGIN_COM_MAP(CSampleCamera)
    COM_INTERFACE_ENTRY_AGGREGATE(IID_ICamera, m_base)
    ...
END_COM_MAP

DECLARE_GET_CONTROLLING_UNKNOWN()

Since I want to be able to call the members on CameraBase (through the ICamera interface) from the parent class, I do not want to use COM_INTERFACE_ENTRY_AUTOAGGREGATE, which stores the inner object's pointer as a reference of IUnknown. Therefor I am creating it on my own from the FinalConstruct-method:

HRESULT FinalConstruct()
{
    HRESULT hr;

    if (FAILED(hr = m_camera.CoCreateInstance(CLSID_CameraBase, this->GetControllingUnknown(), CLSCTX_INPROC_SERVER)))
        return hr;
}

Where m_camera is defined as CComPtr<ICamera>. However, this does result in a error CLASS_E_NOAGGREGATION (HRESULT 0x80040110). My current workaround is to store two references, IUnknown and ICamera, and query for the later one.

if (FAILED(hr = m_base.CoCreateInstance(CLSID_CameraBase, this->GetControllingUnknown(), CLSCTX_INPROC_SERVER)) ||
    FAILED(hr = m_base->QueryInterface(&m_camera)))
    return hr;

This works, but it feels kinda strange, since the class (CameraBase) that get's instanciated is the same in both cases. Am I missing something? Am I using the right way to aggregate the inner object? Why does the returned pointer of CoCreateInstance need to be of type IUnknown, if an outer unknown is passed?

Thanks in advance! :)

c++
visual-studio
com
atl
midl
asked on Stack Overflow Jan 12, 2017 by Carsten

1 Answer

3

An aggregatable COM object provides two distinct implementations of IUnknown - non-delegating and delegating.

The non-delegating implementation is the "normal" one - its QueryInterface hands out interfaces implemented by the aggregatable object, and its AddRef and Release control the lifetime of that object.

The delegating implementation, as the name suggests, delegates all three method calls to the controlling IUnknown of the outer object. All the other interfaces implemented by the object have their three IUnknown methods backed by this delegating implementation. This is how the aggregation can maintain an illusion for the client that it's dealing with a single COM object - it allows the client to query from an interface implemented by the outer to one implemented by the inner, and (more interestingly) vice versa. Recall that it's a requirement for IUnknown that QueryInterface implementation be symmetrical and transitive.

When CoCreateInstance is called with non-NULL controlling unknown parameter, it must request IUnknown from the inner object - that is the outer's one and only chance to obtain a non-delegating implementation. You cannot use any other interface pointer from the inner in the outer's interface map - again, all other interfaces are backed by a delegating unknown, so forwarding QueryInterface call to them would end up calling QueryInterface on the outer, and end up right back in the interface map, leading to an infinite recursion.

answered on Stack Overflow Jan 12, 2017 by Igor Tandetnik

User contributions licensed under CC BY-SA 3.0