ReleaseComObject


style="mso-bidi-font-family: Tahoma">Developers
who are accustomed to the IDisposable pattern or to C#’s ‘using’ syntax
sometimes ask why COM Interop doesn’t support IDisposable on every Runtime
Callable Wrapper (RCW).  That way,
managed code could indicate that it is finished using the unmanaged COM
resource.  This would allow the
resources to be cleaned up much earlier than they would be if we waited for a
GC.  Also, it might better
approximate the way an unmanaged client would have used this COM object through
explicit Release calls. "urn:schemas-microsoft-com:office:office" />


style="mso-bidi-font-family: Tahoma"> size=2> 


style="mso-bidi-font-family: Tahoma">There’s a
service called System.Runtime.InteropServices.Marshal.ReleaseComObject() that
looks suspiciously like it could be used as a Dispose() call. style="mso-spacerun: yes">  However, this is misleading. style="mso-spacerun: yes">  ReleaseComObject is quite different from
Dispose() and it’s also quite different from IUnknown::Release() as I’ll
explain.


style="mso-bidi-font-family: Tahoma"> size=2> 


face=Tahoma>The COM Interop layer in
the CLR can make do with a single reference count against the unmanaged pUnk,
regardless of how many managed clients refer to that object.  In other
words, the Interop layer does not hold a reference count for each managed
client of that pUnk.  Instead, we rely on the reachability magic of the GC
to determine when nobody needs that pUnk anymore.  When nobody needs the
pUnk, then we drop our single reference count on that pUnk.
style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">


style="mso-bidi-font-family: Tahoma"> size=2> 


style="mso-bidi-font-family: Tahoma">Furthermore,
negotiation for interfaces in managed code via COM Interop does not necessarily
affect the unmanaged refcount of the COM object.  For instance, the managed
wrapper might have already cached a pUnk for this
interface.


style="mso-bidi-font-family: Tahoma"> size=2> 


style="mso-bidi-font-family: Tahoma">Regardless
of the actual refcount that the wrapper holds on the underlying COM object,
ReleaseComObject will release all these refcounts at one
time.


style="mso-bidi-font-family: Tahoma"> size=2> 


face=Tahoma>However, the return value
from ReleaseComObject reveals that there’s an additional refcounting scheme
involved.  This is unrelated to the COM refcount. style="mso-spacerun: yes"> 
The same pUnk might be marshaled into
the managed process a number of times.  We keep track of this count. 
You can then call ReleaseComObject that same number of times before we will call
IUnknown::Release on the pUnks held by the wrapper and start giving throwing
InvalidComObjectExceptions.  If you are passing the pUnk backwards and
forwards across the layer, this means that the “marshaling count” will be a
large and arbitrary number.  But, for some usage patterns, the number of
times the pUnk is marshaled across may correspond to the number of distinct
managed clients that have got their hands on the wrapper.  If this happens
to be the case, then that many managed clients can independently call
ReleaseComObject before the wrapper is zombied and the underlying pUnks are
Release’d. style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">


face=Tahoma>  style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">


style="mso-bidi-font-family: Tahoma">I guess that
this behavior is slightly more useful than a simple Release in some
circumstances.  And you can turn it into the equivalent of
IUnknown::Release by calling it in a loop until it returns 0.  At that
point, our internal “marshaling count” has been decremented to 0 and we have
Release’d the pUnks.  (We really
need to add a ReleaseComObjectFully() service to avoid that silly
loop).


style="mso-bidi-font-family: Tahoma"> size=2> 


face=Tahoma>Application code can
either be on the GC plan, where we track whether there are references
outstanding – but in a non-deterministic manner that is guided by memory
pressure – or application code can do the tracking itself.  But if the
application does the tracking, it is responsible for knowing whether there are
other managed clients still using the COM object.
style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">


face=Tahoma>  style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">


face=Tahoma>One way you might do this
is by subtyping the wrapper and adding a Dispose protocol on the managed side
that is reference counted.  But all managed clients in the process must
observe the discipline you define.  A more practical approach is to ensure
that you are the only client of the pUnk by creating the COM object yourself and
then never sharing that reference with anyone else.
style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">


face=Tahoma>  style="FONT-SIZE: 12pt; FONT-FAMILY: 'Times New Roman'">


style="mso-bidi-font-family: Tahoma"> size=2> 


style="mso-bidi-font-family: Tahoma">If you are
using a COM object in a scoped, single-threaded manner then you can safely call
ReleaseComObject on that object when you are done with it.  This will
eagerly release any unmanaged resources associated with that object.
 Subsequent calls would get an InvalidComObjectException.  So don’t
make subsequent calls.


style="mso-bidi-font-family: Tahoma"> size=2> 


style="mso-bidi-font-family: Tahoma">But if you
are using a COM object from multiple places or multiple threads in your
application (or from other applications in the same process), you should not
call ReleaseComObject.  That’s because you will inflict
InvalidComObjectExceptions on those other parts of the
application.


style="mso-bidi-font-family: Tahoma"> size=2> 


style="mso-bidi-font-family: Tahoma">So my advice
is:


style="mso-bidi-font-family: Tahoma"> size=2> 


style="MARGIN: 0in 0in 0pt 0.25in; TEXT-INDENT: -0.25in"> style="mso-bidi-font-family: Tahoma"> size=2>1) style="FONT-SIZE: 7pt; FONT-FAMILY: 'Times New Roman'">     
face=Tahoma>If you are a server application, calling ReleaseComObject may be an
important and necessary requirement for getting good throughput.  This is
especially true if the COM objects live in a Single Threaded Apartment
(STA).  For example, ASP
compatibility mode uses the DCOM STA threadpool.  In these scenarios, you
would create a COM object, use it, then eagerly call ReleaseComObject on each
request.


style="mso-bidi-font-family: Tahoma"> size=2> 


style="MARGIN: 0in 0in 0pt 0.25in; TEXT-INDENT: -0.25in"> style="mso-bidi-font-family: Tahoma"> size=2>2) style="FONT-SIZE: 7pt; FONT-FAMILY: 'Times New Roman'">     
face=Tahoma>If you are a client application using a modest number of COM objects
that are passed around freely in your managed code, you should not use
ReleaseComObject.  You would likely inflict Disconnected errors on parts of
the application by doing so.  The performance benefit of eagerly cleaning
up the resources isn’t worth the problems you are
causing.


style="mso-bidi-font-family: Tahoma"> size=2> 


style="MARGIN: 0in 0in 0pt 0.25in; TEXT-INDENT: -0.25in"> style="mso-bidi-font-family: Tahoma"> size=2>3) style="FONT-SIZE: 7pt; FONT-FAMILY: 'Times New Roman'">     
face=Tahoma>If you have a case where you are creating COM objects at a high
rate, passing them around freely, choking the Finalizer thread, and
consuming a lot of unmanaged resources… you are out of
luck.


style="mso-bidi-font-family: Tahoma"> size=2>

Comments (14)

  1. Sam Gentile says:

    Any comments? Do you read your readers? -)

    I am very interested in this discussion. Thanks for your blog. It is a bright light in the CLR world.

  2. Chris Brumme says:

    I agree… the marriage of refcounted COM and traced GC is an uneasy one. We actually spent a lot of time designing hybrid systems to provide deterministic finalization of managed objects based on injected refcounting by the JIT. The technical complexity to the CLR was very high; the performance looked like it would be painful; the programming model was lame; and ultimately the approach still didn’t give the application what it needed. As soon as a refcounted object was leaked into a traced reference, its lifetime became non-deterministic (at least until the next GC).

    We also considered breaking identity during marshaling. If we created a different ComObject every time a pUnk was marshaled into managed code, the application could call ReleaseComObject with impunity. However, this also polluted the programming model. And imagine the difficulty of calling ReleaseComObject on every reference you acquire in this manner.

    This leaves many COM Interop scenarios in a difficult position. Scenarios #1 & #2 from my post above really do work. It’s scenario #3 which fails. There are a couple of things we want to do to give some relief in the future.

    We want to handle the case where a managed 20-byte ComObject is holding onto a 4 MB unmanaged bit map via a COM object. Currently, the GC is unaware of the "value" of the unmanaged resource associated with the tiny ComObject and will delay collection. (Eventually we will notice the overall memory load on the system, but this is beyond the most efficient time for the collection).

    So we want to allow the developer to inform the GC of this cost so that we collect more aggressively. We would also like to provide some services that let you control how many of a scarce resource can be in use at one time.

    Obviously I can’t give details on either of those efforts. They should be generally useful outside of COM Interop. And they should also take some of the scenarios that are currently in #3 and move them to #2.

    That still leaves a messy problem with any scenarios that remain in bucket #3. I’ll talk to our Interop team and see if we can come up with patterns that allow different components to usefully agree on whether a COM object is still in use or whether it should be aggressively Release’d. But I’m not particularly hopeful that we’ll find a magic bullet.

  3. I wonder if I run into any trouble if use Single Threaded Apartment, an RCW and ThreadStatic together?

  4. ThoughtWorks says:

    In a test program, I call Mashal.ReleaseComObj several times and it return -1 at last. What this -1 means? Shouldn’t it always stop at 0?

  5. code weaver says:

    Hi Just wanted to know the semantics of Releasecomobject with COM+ usage

  6. Chris Brumme says:

    There is a detailed discussion of ReleaseComObject combined with EnterpriseServices (managed COM+) in chapter 8 of the excellent Prescriptive Architecture Guidance book on "Improving .NET Application Performance and Scalability".

    Chapter 8 can be found at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt08.asp. There is also some general guidance about ReleaseComObject in Chapter 7.

  7. I recently was asked to take a look at some VSTO test automation that wasn’t behaving correctly on lab

  8. [Furl] [DotNetJunkies] [COM interop reference counting in .NET] Excel VBA code to convert collection into an array >Function collectionToArray(c As Collection) As Variant() Dim a() As Variant: ReDim a(0 To c.Count – 1) Dim i As Integer For i =…