0

I wish to share with you this problem I'm facing. Long story short, I have this little code (test purpose only):

 int main () { IXMLDOMDocument *pDoc(nullptr); CoCreateInstance(CLSID_DOMDocument, nullptr, CLSCTX_ALL, IID_IXMLDOMDocument, reinterpret_cast<LPVOID*>(&pDoc)); DWORD d = pDoc->AddRef(); std::cout << "pDoc: add ptr=" << pDoc << " d=" << d << std::endl; d = pDoc->Release(); std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl; IUnknown *pUnk(nullptr); pDoc->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnk)); d = pUnk->AddRef(); std::cout << "pUnk: add ptr=" << pUnk << " d=" << d << std::endl; d = pUnk->Release(); std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl; /*Release objects*/ d = pUnk->Release(); std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl; d = pDoc->Release(); std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl; return 0; } 

I'm expecting that the last 2 cout print 0 as the returned count, in their place I'm seeing:

pDoc: add ptr=004A4628 d=2 pDoc: rel ptr=004A4628 d=1 pUnk: add ptr=004A3A10 d=4 pUnk: rel ptr=004A3A10 d=3 pUnk: rel ptr=004A3A10 d=2 pDoc: rel ptr=004A4628 d=0 

Why QueryInterface returned me an IUnknown which internal count begins in 3? Why last Release method of the IUnknown object isn't returning 0 as excepted?

What I'm might be missing?

6
  • 2
    Why do you assume that you are the only one holding references and that the refcount should go to zero when you release all yours? Commented Feb 21, 2018 at 20:57
  • @JesperJuhl The OP is the one who created it, and the OP didn't pass it to any method which might want to add another reference. Commented Feb 21, 2018 at 21:04
  • Actually, I was playing with a COM+ smart pointer [link]msdn.microsoft.com/en-us/magazine/dn904668.aspx Commented Feb 21, 2018 at 23:45
  • Actually, I was playing with a COM+ smart pointer ( msdn.microsoft.com/en-us/magazine/dn904668.aspx ) But I found out that after calling QueryInterface asking for an IUnknown its Release method (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+ handles IUnknown::Release return value. Commented Feb 21, 2018 at 23:55
  • what you view - is com Aggregation Commented Feb 22, 2018 at 1:01

4 Answers 4

4

Why QueryInterface returned me an IUnknown which internal count begins in 3?

pDoc and pUnk are essentially two ways of accessing a single object. Since it's a single object, this is reflected in the reference count and explains why it doesn't start at 1.

But from that explanation, you might expect the reference count to start at 2 rather than 3. The fact it starts at 3 is likely caused by an internal helper object used by DOMDocument to handle the IUnknown interface, where that internal helper object maintains an additional reference.

Why last Release method of the IUnknown object isn't returning 0 as excepted?

For the same reason: pDoc and pUnk are essentially the same object. Since you still have an unreleased reference (accessible through pDoc) at that point, the object is still live.

Sign up to request clarification or add additional context in comments.

4 Comments

You are probably right about the helper, but I think using a helper for IUnknown might break the following COM rule: "For any one object, a specific query for the IUnknown interface on any of the object's interfaces must always return the same pointer value. This enables a client to determine whether two pointers point to the same component by calling QueryInterface with IID_IUnknown and comparing the results. It is specifically not the case that queries for interfaces other than IUnknown (even the same interface through the same pointer) must return the same pointer value."
@RemyLebeau No, it doesn't. That is exactly what I commented on your answer I already thought the rule would have to be: QueryInterface for IUnknown doesn't have to return the same pointer value as the one you're calling QueryInterface on, it has to return the same pointer value as what it returned for a prior request for the IUnknown interface.
"QueryInterface for IUnknown doesn't have to return the same pointer value as the one you're calling QueryInterface on" - that is not what I said. It should be returning a pointer to the same object that QueryInterface() is called on, which is not necessary the same pointer that QueryInterface() is called on.
@RemyLebeau The IUnknown interface is requested once, so trivially all of those 1 requests for the IUnknown interface give the same pointer value / a pointer to the same object, which is the same thing here. I don't see why this has to be so difficult. If a function has to return the same value each time it's called, and it's called only once, then there's no other result it has to compare equal to.
1

According to the MDSN documention of AddRef and Release, the return value is intended to only be used for test purposes. It may not be an accurate reflection of the actual number of references; and in particular testing it against 0 is not a guarantee that the object is finished.

Under what conditions will the IUnknown::AddRef method return 0?.

3 Comments

True. On the other hand, the AddRef() and Release() documentations both specifically state: "The method returns the new reference count.", which contradicts what Raymond says. Obviously, Raymond is never wrong, so the documentation must be :-)
@RemyLebeau the intent of the return value is documented, but the return value is ignored by all practical code. Any deviation from the documentation typically won't be discovered. Raymond and the documentation are both right, even though they disagree.
The documentation is wrong if we include Windows 95 because the 386 cannot atomically perform this operation. 98 added a workaround, see blogs.msdn.microsoft.com/oldnewthing/20040506-00/?p=39463
1

When an object is queried for its IUnknown interface specifically, COM expects the same object to be returned every time, to ensure identity tests work (you can test if two interfaces point to the same object in memory by querying both interfaces for IUnknown and then compare the queried pointers). This is pointed out in the QueryInterface() documentation:

For any one object, a specific query for the IUnknown interface on any of the object's interfaces must always return the same pointer value. This enables a client to determine whether two pointers point to the same component by calling QueryInterface with IID_IUnknown and comparing the results. It is specifically not the case that queries for interfaces other than IUnknown (even the same interface through the same pointer) must return the same pointer value.

So, when requesting the DOMDocument object's IUnknown interface via QueryInterface(), one would expect the reference count to be incremented by 1, not by 2. In which case, you should have gotten the following numbers in your output:

int main () { IXMLDOMDocument *pDoc(nullptr); CoCreateInstance(CLSID_DOMDocument, nullptr, CLSCTX_ALL, IID_IXMLDOMDocument, reinterpret_cast<LPVOID*>(&pDoc)); // DOMDoc refcnt=1 DWORD d = pDoc->AddRef(); // DOMDoc refcnt=2 std::cout << "pDoc: add ptr=" << pDoc << " d=" << d << std::endl; d = pDoc->Release(); // DOMDoc refcnt=1 std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl; IUnknown *pUnk(nullptr); pDoc->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnk)); // DOMDoc refcnt=2 d = pUnk->AddRef(); // DOMDoc refcnt=3 std::cout << "pUnk: add ptr=" << pUnk << " d=" << d << std::endl; d = pUnk->Release(); // DOMDoc refcnt=2 std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl; /*Release objects*/ d = pUnk->Release(); // DOMDoc refcnt=1 std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl; d = pDoc->Release(); // DOMDoc refcnt=0 std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl; return 0; } 

However, in reality, when you query the DOMDocument object for its IUnknown interface, there is clearly an extra internal reference being made to the object, and that extra reference is not released until all references to the queried IUnknown interface have been released. That would account for the numbers you are seeing:

int main () { IXMLDOMDocument *pDoc(nullptr); CoCreateInstance(CLSID_DOMDocument, nullptr, CLSCTX_ALL, IID_IXMLDOMDocument, reinterpret_cast<LPVOID*>(&pDoc)); // DOMDoc refcnt=1 DWORD d = pDoc->AddRef(); // DOMDoc refcnt=2 std::cout << "pDoc: add ptr=" << pDoc << " d=" << d << std::endl; d = pDoc->Release(); // DOMDoc refcnt=1 std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl; IUnknown *pUnk(nullptr); pDoc->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnk)); // DOMDoc refcnt=3, not 2! d = pUnk->AddRef(); // DOMDoc refcnt=4 std::cout << "pUnk: add ptr=" << pUnk << " d=" << d << std::endl; d = pUnk->Release(); // DOMDoc refcnt=3 std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl; /*Release objects*/ d = pUnk->Release(); // DOMDoc refcnt=1, not 2! std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl; d = pDoc->Release(); // DOMDoc refcnt=0 std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl; return 0; } 

The DOMDocument object is likely returning a pointer to an internal helper object when queried for IUnknown, and that helper object is returning the owning DOMDocument's reference count from AddRef() and Release() instead of returning its own reference count.

2 Comments

@hvd: Yes, querying an object for IUnknown must return the same IUnknown pointer every time, and that is usually the IUnknown of the same object that is being queried, not an IUnknown of some internal helper that the object owns. Though that would be technically legal, it is not common. Most implementations just return the object's this pointer casted to IUnknown*
DOMDocument object is aggregable object, and it delegate call QueryInterface to it inner object via m_pUnkOuter if be exactly. from another side the DOMDocument object is not created once, when inner object created. instead created (allocated) every time new, when we query for IXMLDOMDocument inner object. this is called teardown in com if i not mistake
1

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 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.