Why is every successful QueryInterface() call followed by Release() call?
Asked Answered
P

3

11

Why is a QueryInterface() call always followed by a Release() call? For example, I have seen a sample code from MSDN as below:

HRESULT hr = S_OK;
CDecoder *pObj = new CDecoder(&hr);

if (SUCCEEDED(hr))
{
    *ppv = NULL;
    hr = pObj->QueryInterface(riid, ppv);
}
pObj->Release();
return hr;

can someone explain the intent behind Release() call here?

Phelan answered 18/2, 2011 at 23:36 Comment(1)
That code is very unusual. It seems like like a C++ obfuscation of COM details.Hemimorphite
G
16

It's not always followed directly like this, though that is pretty common.

COM objects are reference counted. When you initially create the object, you get a pointer to an IUnknown. Then you obtain some other interface with QueryInterface. Since you (usually) don't care about the IUnknown interface anymore, you release that. When you release the other interface you obtained, the reference count will be 0 so the object can be destroyed. If you don't release the IUnknown, however, the reference count will stay non-zero, so the object can't be destroyed.

The most obvious case where you would not immediately release the IUnknown is when/if you need to get more than one other interface. In such a case, you'd get the IUnknown, then the second and third interfaces before releasing the IUnknown. There are at least potentially cases where you might not know the third (or subsequent) interfaces right after you created the object either, so you might need to retain access to the IUnknown for some arbitrary length of time before releasing it.

Gremial answered 18/2, 2011 at 23:43 Comment(0)
E
10

Why is a QueryInterface call is always followed by a Release call?

Because QueryInterface will call AddRef which increases the reference count to the pointer. When there are 0 references to a pointer it is freed for you.

Note: There is some confusion in this question's answers about what QueryInterface actually does. It simply retrieves pointers to the supported interfaces on an object and increments the reference count on that object. It doesn't create a new object for each interface that it implements.

For example if you have an object which implements 2 interfaces, then the call would simply cast that object as each of the interface and increment a variable which is used as the reference count.

Note: The reference counting can be implemented in different ways, but the above explains the usual scenario. In particular @Ben describes a tear off interface below which stresses the importance of calling Release on the interface pointer that was returned to you.

Enchondroma answered 18/2, 2011 at 23:39 Comment(10)
As Jerry explains, the reference count on the interfaces should be considered separate (although they do cooperate).Edmond
@Ben: There is no such thing as a reference count on the interface, it's on the object that implements the interface. And each interface you get increases that reference count more... again on the object.Enchondroma
That QueryInterface 'may' call AddRef is an implementation detail. What happen is that if QI is successfull you have two objects, and you should release both of them when no longer in use.Gonfanon
@Ismael: You have 1 object that implements for example 2 interfaces.Enchondroma
@Brian: Wrong (to your first comment). You have to Release the right interface pointer, or tear-off interfaces break. @Ismael: The contract of QI is that it transfers ownership of a refcount to the caller. It could adjust the reference count internally instead of calling AddRef, but it is not a separate object (using QI for IUnknown on both interface pointers MUST return the same value). However, it does have its own reference count.Edmond
@Ben: QueryInterface retrieves a pointers to the supported interfaces on an object. AddRef increments the reference count for an interface on an object. Read the documentation here: msdn.microsoft.com/en-us/library/ms691379(v=vs.85).aspx and here as proof: msdn.microsoft.com/en-us/library/ms682521(v=vs.85).aspxEnchondroma
@Brian: Absolutely. AddRef increments the reference count for an interface. Not for the object as a whole. Reference count is managed per interface pointer to allow the object to free resources associated with a particular interface when no longer needed (tear-off interface). Read msdn.microsoft.com/en-us/library/h9f7kdd8.aspx "implements a tear-off interface as a separate object that is instantiated only when that interface is queried for. The tear-off is deleted when its reference count becomes zero." There are two C++ objects aggregated into a single COM object via QI.Edmond
@Ben: I never claimed that you should call Release on different interfaces. I only claimed that the reference count is on the object as the documentation I quoted above claims. In most cases you have a COM object and it implements many interfaces, and you cast that interface as a pointer to the type you are asking for and you increase the ref count.Enchondroma
@Brian: Your own sources say there is potentially a reference count for each interface pointer, not just one per object. The client must assume that interface pointers have independent reference counts. The object may choose to share a single count as an implementation detail (if it isn't using tear-off interfaces).Edmond
@Ben: I don't know about tear off interfaces, but I have read a book on COM before and also implemented several commercial products that expose COM objects. In any case I put a disclaimer in my answer above that says to read your comment on tear off interfaces and that reference counting implementation is not guaranteed. Thanks for your comments.Enchondroma
U
4

This particular code snippet appears to only be interested in obtaining the ppv value. Note that it is not that interface pointer being released. The CDecoder class appears to be the vehicle to get it. There a new statement to create it, not otherwise the standard COM way to create a COM class, that takes CoCreateInstance(). Apparently proper usage of that class requires a Release() call instead of using the delete operator. Again, not standard at all but not impossible. I can only guess that CDecoder is a C++ class that implements a COM coclass and this code is using it directly rather than going through the normal COM procedures.

Don't assume this code is standard. It is not at all.

Unsaddle answered 19/2, 2011 at 0:43 Comment(1)
You couldn't use delete, because QI for the original interface has to succeed, and I believe may have to return the original pointer.Edmond

© 2022 - 2024 — McMap. All rights reserved.