Does IUnknown::QueryInterface() increment the reference count?
Asked Answered
W

4

9

If I have an IUnknown *ptr, do I need to call Release() on every interface I obtain through ptr->QueryInterface(), in addition to calling ptr->Release() when I'm done with ptr?

I used to think that the answer is "Yes", but this quote from MSDN confused me:

Occasionally you may need to obtain a weak reference to an object (that is, you may wish to obtain a pointer to one of its interfaces without incrementing the reference count), but it is not acceptable to do this by calling QueryInterface followed by Release.

I don't understand why that's problematic -- if I call ptr->QueryInterface() and then call Release on the resulting pointer, shouldn't the reference count on the object still be positive? How does that result in an invalid pointer?

Wayside answered 11/9, 2011 at 4:22 Comment(0)
L
8

The documentation is correct. And you need to follow reference counting rules - that includes calling Release on interfaces obtained from QueryInterface in addition to after you created the object.

To clear up why you can't do weak pointers with Release - there exists a race condition in calling QueryInterface and then Release immediately after.

  • Thread1 creates object - reference count 1
  • Thread2 calls QueryInterface for weak reference - reference count 2
  • Thread1 releases object - reference count 1
  • Thread2 calls Release for weak reference - reference count 0. Object is destroyed.
  • Thread2 tries to use object - error.

The warning is there to guard against the above - presumably some programmers think that they can "call ptr->QueryInterface() and then call Release on the resulting pointer" and then use the object...

Landry answered 11/9, 2011 at 4:28 Comment(4)
Ahh... so it's because of threading, I didn't realize that. Thanks! +1Wayside
Not even threading, if you Release your strong (let's call it that) reference before the weak one then the weak reference is no longer valid. Calling Release is basically your way of saying "okay I'm done" - once you call it, stop using the object. If you need to keep using the object... don't call Release.Landry
Right; I was only wondering about the case where you call Release on the new interface immediately after your QueryInterface call, and so that's why I didn't think of a race condition.Wayside
This isn't just about threading. What if the object you got from the QueryInterface call was a COM tear-off? In that case, it has a reference count that is independent from the reference count of the master object (this is legal as long as the tear-off follows the object identity rules for COM). Your call to Release() could cause the tear-off to be freed and then you'd be left holding onto a pointer to freed memory.Disarrange
G
5

IUnknown::QueryInterface Method

Retrieves pointers to the supported interfaces on an object.

This method calls IUnknown::AddRef on the pointer it returns.

Straight from IUnknown::QueryInterface reference at http://msdn.microsoft.com/en-us/library/ms682521%28v=vs.85%29.aspx

Gulledge answered 11/9, 2011 at 4:27 Comment(0)
C
2

Theading is not the only scenario; I'd go so far as to say that threading is not actually the primary scenario at all: these COM rules date back to Win16 before preemptive multithreading was added to Windows in the first place.

The key issue is that as far as COM is concerned, reference counts are per-interface, not per-object. A COM implementation is free to actually implement a reference count by implementing it per-object - that's perhaps the simplest way of doing it in C++, especially when a COM object maps to a single C++ object - but that's nothing more than an implementation detail, and COM client code cannot rely on it being the case.

There are many COM objects out there that may generate interfaces on the fly as required, and then destroy them as soon as they are no longer needed. In those cases, if you call QI to get one of these interfaces, once you call Release, the memory for that interface can be dealloated, so using it could lead to a fault/crash/etc.

Generally speaking, you have to regard any call to ->Release() as potentially deallocating the memory behind the pointer.

(Also, note that COM does not really have a concept of weak references to begin with: there's counted (strong) references, and that's it.)

Corallite answered 11/9, 2011 at 9:19 Comment(2)
My initial guess was also that reference counts are per-interface, but I don't think that can actually hapen, because COM rules state that calling QueryInterface on an IUnknown twice must give back the same physical pointer. So really, it seems like it has to be the same object every time.Wayside
The only place where COM requires the same pointer value is where you QI for IUnknown specifically; this is a special case known as the canonical IUnknown. All other interfaces could potentially be generated on-demand. (It's actually this one case that allows the other interfaces to be on-demand, and still have some ability to compare object identity, by comparing Canonical IUnknown. Per-interface counting isn't just some theoretical thing: ATL has support for it, see msdn.microsoft.com/en-us/library/wh8b86c9(v=vs.80).aspx )Corallite
S
0

The suggestion to avoid weak references does not solve the race issue.

T1 operator new, create object, references: 1
T1     passes interface object reference to T2, thinking it can "share" ownership
T1     suspends
T2     resumes
T2 QueryInterface
T2     suspends before InterlockedIncrement, references: 1
T1     resumes
T1 Calls Release
T1     suspends between InterlockedDecrement and operator delete, references: 0
T2     resumes, InterlockedIncrement occurs, references 1
T2     suspends
T1     resumes, operator delete executes, references 1 !!!
T1     suspends
T2     resumes
T2 Any reference to the interface is now invalid since it has been deleted with reference count 1.

This is solvable in the COM server. The COM client, however, should not depend upon the server preventing this race condition. Therefore, COM clients MUST NOT share interface objects between threads. The only thread which should be allowed to access the interface object, is the ONE thread which currently "owns" the interface object.

T1 should NOT have called Release. Yes, it could have called AddRef prior to passing the interface object to T2. But that may not solve the race, only move it someplace else. The best practice is to always maintain the concept of one-interface-object, one-owner.

If the COM server wishes to support the concept that two (or more) interfaces can reference some shared server-internal state, the COM server should advertise the contract by supplying a CreateCopyOfInstance method and manage contention, internally. There are, of course, examples of servers which handle this sort of "fan-out". Take a look at the persistent storage interfaces from Microsoft. There, the interfaces do NOT "fan-out" .. each interface should be owned by a single user (thread/process/whatever) and the "fan-out" is managed, internally, by the server, with methods provided to the COM clients to control some facets of the contention issues. Microsoft's COM servers must, therefore, address the race conditions as part of their contracts to their clients.

Starlin answered 14/3, 2013 at 5:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.