what you view - is Com Aggregation and here pUnk is inner object and pDoc is aggregable object. also interesting that when you query IXMLDOMDocument interface on inner object - he every time allocate new aggregable object, which implement this interface
let at begin create utility function for print reference count on object and also compare 2 object pointers from com view (binary values of this pointers can be different, but IUnknown for both objects the same)
ULONG GetRefCount(IUnknown *pUnk, BOOLEAN bPrint = TRUE) { pUnk->AddRef(); ULONG d = pUnk->Release(); if (bPrint) DbgPrint("%p>%u\n", pUnk, d); return d; } BOOLEAN IsSameObjects(IUnknown *p, IUnknown *q) { BOOLEAN f = FALSE; IUnknown *Unk1, *Unk2; if (0 <= p->QueryInterface(IID_PPV_ARGS(&Unk1))) { if (0 <= q->QueryInterface(IID_PPV_ARGS(&Unk2))) { f = Unk1 == Unk2; Unk2->Release(); } Unk1->Release(); } DbgPrint("%p[%u] %s %p[%u]\n", p, GetRefCount(p, FALSE), f ? "==" : "!=", q, GetRefCount(q, FALSE)); return f; }
now let do first test:
void test1 () { IXMLDOMDocument *pDoc, *pDoc2; if (0 <= CoCreateInstance(__uuidof(DOMDocument), 0, CLSCTX_ALL, IID_PPV_ARGS(&pDoc))) { GetRefCount(pDoc); IUnknown *pUnk; if (0 <= pDoc->QueryInterface(IID_PPV_ARGS(&pUnk))) { IsSameObjects(pDoc, pUnk); if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc2))) { IsSameObjects(pDoc, pDoc2); GetRefCount(pUnk); pDoc2->Release(); GetRefCount(pUnk); } pUnk->Release(); } GetRefCount(pDoc); DbgPrint("Final Release=%u\n", pDoc->Release()); } }
and it output:
000001DD8DCE71A0>1 000001DD8DCE71A0[1] == 000001DD8DCE5950[3] 000001DD8DCE71A0[1] == 000001DD8DCE7270[1] 000001DD8DCE5950>4 000001DD8DCE5950>3 000001DD8DCE71A0>1 Final Release=0
here visible that pUnk and pDoc (pDoc2) point to different memory locations, but this is the same com object
based on this let do more symmetric test:
void test2 () { IUnknown *pUnk; if (0 <= CoCreateInstance(__uuidof(DOMDocument), 0, CLSCTX_ALL, IID_PPV_ARGS(&pUnk))) { GetRefCount(pUnk); IXMLDOMDocument *pDoc, *pDoc2; if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc))) { IsSameObjects(pUnk, pDoc); if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc2))) { IsSameObjects(pDoc2, pDoc); GetRefCount(pUnk); pDoc2->Release(); } if (0 <= pDoc->QueryInterface(IID_PPV_ARGS(&pDoc2))) { IsSameObjects(pDoc2, pDoc); GetRefCount(pUnk); pDoc2->Release(); } pDoc->Release(); } GetRefCount(pUnk); DbgPrint("Final Release=%u\n", pUnk->Release()); } }
and output:
000001DD8DCE5950>1 000001DD8DCE5950[3] == 000001DD8DCE7270[1] 000001DD8DCE71A0[1] == 000001DD8DCE7270[1] 000001DD8DCE5950>4 000001DD8DCE7270[2] == 000001DD8DCE7270[2] 000001DD8DCE5950>3 000001DD8DCE5950>1 Final Release=0
here better visible that first created inner object. every time when we query IXMLDOMDocument on this object - new aggregable object created, and pointer to it returned.
how this is implemented in code ? simply demo
struct __declspec(novtable) __declspec(uuid("78979DF1-A166-4797-AF2B-21BBE60D0B2E")) IDemo : public IUnknown { virtual void Demo() = 0; }; class CDemo : public IDemo { IUnknown* _pUnkOuter; LONG _dwRef; ~CDemo() { DbgPrint("%s<%p>\n", __FUNCTION__, this); _pUnkOuter->Release(); } public: CDemo(IUnknown* pUnkOuter) : _pUnkOuter(pUnkOuter) { DbgPrint("%s<%p>\n", __FUNCTION__, this); _dwRef = 1; pUnkOuter->AddRef(); } virtual void Demo() { DbgPrint("%s<%p>\n", __FUNCTION__, this); } virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void**ppvObject) { if (riid == __uuidof(IDemo)) { AddRef(); *ppvObject = static_cast<IUnknown*>(this); return S_OK; } return _pUnkOuter->QueryInterface(riid, ppvObject); } virtual ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&_dwRef); } virtual ULONG STDMETHODCALLTYPE Release() { ULONG dwRef = InterlockedDecrement(&_dwRef); if (!dwRef) delete this; return dwRef; } }; class CObject : public IUnknown { LONG _dwRef; ~CObject() { DbgPrint("%s<%p>\n", __FUNCTION__, this); } public: CObject() { DbgPrint("%s<%p>\n", __FUNCTION__, this); _dwRef = 1; } virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void**ppvObject) { *ppvObject = 0; if (riid == __uuidof(IUnknown)) { AddRef(); *ppvObject = static_cast<IUnknown*>(this); return S_OK; } else if (riid == __uuidof(IDemo)) { if (CDemo* pDoc = new CDemo(this)) { *ppvObject = static_cast<IUnknown*>(pDoc); return S_OK; } return E_OUTOFMEMORY; } return E_NOINTERFACE; } virtual ULONG STDMETHODCALLTYPE AddRef() { return InterlockedIncrement(&_dwRef); } virtual ULONG STDMETHODCALLTYPE Release() { ULONG dwRef = InterlockedDecrement(&_dwRef); if (!dwRef) delete this; return dwRef; } }; void test3() { if (CObject* pUnk = new CObject) { GetRefCount(pUnk); IDemo *pDoc, *pDoc2; if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc))) { IsSameObjects(pUnk, pDoc); if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc2))) { IsSameObjects(pDoc2, pDoc); GetRefCount(pUnk); pDoc2->Release(); } if (0 <= pDoc->QueryInterface(IID_PPV_ARGS(&pDoc2))) { IsSameObjects(pDoc2, pDoc); GetRefCount(pUnk); pDoc2->Release(); } pDoc->Release(); } GetRefCount(pUnk); DbgPrint("Final Release=%u\n", pUnk->Release()); } }
and output:
CObject::CObject<000001DD8C340970> 000001DD8C340970>1 CDemo::CDemo<000001DD8C33B950> 000001DD8C340970[2] == 000001DD8C33B950[1] CDemo::CDemo<000001DD8C338930> 000001DD8C338930[1] == 000001DD8C33B950[1] 000001DD8C340970>3 CDemo::~CDemo<000001DD8C338930> 000001DD8C33B950[2] == 000001DD8C33B950[2] 000001DD8C340970>2 CDemo::~CDemo<000001DD8C33B950> 000001DD8C340970>1 CObject::~CObject<000001DD8C340970> Final Release=0
QueryInterfaceasking for anIUnknownitsReleasemethod (called during the smart pointer destructor) wasn't resulting in 0 as I was expecting, so I translated all the smart pointer stuff to raw pointer, just to find out the same problem, however, it wasn't a problem, just the way COM+ handlesIUnknown::Releasereturn value.