COM object related question

M

Michael Moreno

Hello,

In a class I have this code:

public object Obj;

If Obj is a COM object I would like to call in the Dispose() method the
following code:

System.Runtime.InteropServices.Marshal.ReleaseComObject(Obj);

So I need to do a test such as:

if (Obj is IUnknown)

but I cannot find out how to do it (where is declared IUnknown) and
whether it actually is the right way of doing it.

Can you help please?

thanks,
MM
 
N

Nicholas Paldino [.NET/C# MVP]

Michael,

That's not a good idea. Instead, I would do is get the type, and then
check to see if it is a COM object, like so:

if (Obj.GetType().IsCOMObject)
{
// Call ReleaseComObject.
Marshal.ReleaseComObject(Obj);
}

Hope this helps.
 
G

Guest

Calling ReleaseComObject will not result in the actual COM object given its
final Release call by the interop layer. That will not happen until the GC
object performs garbage collection on the wrapper object. So you might want
to run GC.Collect, GC.WaitForPendingFinalizers, and GC.Collect again to force
the RCW out of existence. Otherwise, when GC does (if ever) finally run, you
may find that mscrowrks.dll will encounter an access violation (depending on
the implementation of the COM server) in the COM server.

At least that's how it was in the 1.1 framework. Perhaps the 2.0 framework
has been modified so that when the last reference to a RCW in the interop
layer is released, making it eligible for garbage collection, it will release
the COM interface it is wrapping.

Unfortunately not all COM servers will refuse to shut down or unmap memory
simply because there is a reference (leak) on one of its objects. In the RCW
case, its not really a leak, but when GC runs on its own is
psuedo-indeterminate and a COM server might be forced to unmap memory.
Calling CoDisconnectObject does not help in the interop case.

In a case I had, the COM server's file memory footprint could very easily
reach into the hundreds of megabytes or even more. When the user closes a
file to open another file, the .NET object I had was notified via an event
and released all references to objects in the file by calling
ReleaseComObject. But the app still crashed in mscorwrks when GC finally did
run.

In the case of COM interop, the RCW object really should have a Dispose
method on it that would call Release on the interface it is holding of the
COM object. That sure would make life easier. I wonder if that made it into
the 2.0 framework?
 
N

Nicholas Paldino [.NET/C# MVP]

RD,

You are incorrect. If you reduce the reference count of the RCW to zero
through a call to ReleaseComObject, then the COM object is released. The
RCW isn't collected, but it doesn't matter, since any call to any method on
the RCW will result in an object disposed exception. The COM object is dead
at that point. There is not a magical reference added to the RCW which only
a GC can remove. A GC on a RCW which did not release the COM object will
just release the reference to the COM object (calling Release itself on
IUnknown) and then collect the wrapper.

What happened in your case most likely is that you missed a reference
that was added to the RCW due to re-entrancy.
 
G

Guest

Nicholas,

I do not believe you are reducing the ref count on the RCW by calling
ReleaseComObject. Making that call does reduce the ref count on the COM
object. The trick is knowing just how many times to call ReleaseComObject.
And that depends on the underlying implemention of the interop framework. In
the very least, one needs to call ReleaseComObject until the count returned
is one.

This problem quickly showed up with my application as soon as clients
started working with our app using vb.net and C#. The access violations
occuring in my process I traced to mscorwrks. I ended up opening a support
case with Microsoft to resolve the issue since mscorwrks was causing the
exception and I could not determine just what was going on. I did that when
our customer sent me a very very simple VB .NET project that was simply
opened using Visual Studio .NET and converted and where I could debug both
our side and the client side code and could find no place where the customer
missed a reference. Believe me when I say I painstakenly spent many many
hours tracing the addref and release calls using the debugger. None of the
references occurred once I was into the client's code. All references were
occurring from withing the interop layer (mscorwrks.dll I believe did all the
AddRefs and Releases).

I worked with the Microsoft .NET interop development team to determine what
was happening and the explanation as to why there was still outstanding
references to our COM objects is as follows:

A VB .NET client connects to one of our connection points. The connection
point has a number of events it fires, some of which pass the event the
IDispatch of one of one or more of our objects. The VB .NET programmer does
not have to respond to all of those events (neither does a C# programmer).
The interop layer intercepts all events we raise (it's an interface and all
method have to be implemented at some level) and creates (or reuses) an
interop object when we pass in a dispatch. The interop object calls AddRef on
our object. I verified the (mulitple) add refs to our interface by setting a
break point in our addref and release methods and then stepping over the
event callout. The number of releases between the time I called out and the
callout returned did not match. I eventually implemented my own VB .NET code
to duplicate the problem so I had complete control over the VB .NET client
(and shared it with the interop team, which also had our application for the
opened support case) and not only did the code not implement all events, the
events that were implemented did not store any reference to the object passed
in. Hence, the .NET wrapper object passed in had absolutely no reference to
it in the actual VB .NET client code (acutally in the test case I supplied
there was no code in the event handlers whatsoever.) For each event we fired
that had a dispatch passed in, we experienced a "reference leak".

The interop developers I worked with noted that even though the VB .NET
client did not explicitly reference (or hold a reference to) the interop
object, and the object immediately became a gc candidate when the call to the
delegate returned, the wrapper object would not release our COM object. The
crash was occurring becaue by the time gc did run, we had closed our document
and unmapped its memory. The development team noted that we were violating
the COM rules, and we were. But I pointed out that the CLR does not specify
when gc will run and even if it did, it did not gurantee that all gc
candidates would get collected. After some discussion amongst themselves, the
developers agreed that it would be very hard to ever predict exactly when gc
would run and collect the wrappers at which time they would release our
interfaces.

We were presented with two options. One was for every .NET programmer to
spin the COM ref count down using ReleaseComObject. The method suggested by
the interop team was to make sure to spin the count down to one. That way, if
future changes to the interop layer affected the count a wrapper held the
spin down would still work. This would have to happen for every object ever
accessed by the .NET client, explicitly or implicitly. Think of how
complicated a sophisticated client would have to become, especially when the
client's code could potentially be legitimately storing references on the COM
objects in a variety of places and that some of those places may be
referencing the same object. You sure don't want to call ReleaseComObject
until the count goes to one if your code actually is holding onto an object
more than once. Nevertheless, the developers indicated that only by calling
ReleaseComObject until the count hit one and then making sure the wrapper was
not referenced anywhere else by the client would we then get all references
released. The team agreed this would be nearly impossible to manage,
especially since they do not create a new wrapper each time the same object
is passed to, or obtained by, the .NET client. That is, one wrapper object
can be reused, for example when we fire multiple events and pass in the same
com object from our side or when the .NET client explicitly accessed one of
our objects mulitple times.

You are correct in noting that ReleaseComObject does release the com object
and I was overly broad in stating that simply calling ReleaseComObject would
not necessarily result in the com object being fully released. Consider the
case of an event where the client stores a reference to the com wrapper
passed into the event handler. Even though the client can later release the
reference to the wrapper (in our case we fired a DocumentClosing event and VB
clients know to set their stored references to NULL), and even call
ReleaseComObject on their stored wrapper object before removing the reference
to the wrapper, the reference leak has already occurred when the object was
passed in via the earlier event (or explicitly obtained earlier via our API
if not via an event).

As for strictly following the com rules, before we close a document or
delete an object we call CoDisconnectObject, which is sufficient for a non
..NET client (say a VB 6 client) to avoid having the client's object proxy
from accessing the stub in our process when the actual com object the stub
referenced has gone away (the stub goes away when we call CoDisconnect). I
brought up the fact that even though we deleted our objects we first call
CoDisconnectObject. If I remember correctly, the developers said that
CoDisconnectObject has no affect on how .NET wrappers work. I noted that
since our documents (and objects) can consume massive amounts of memory, it
was unrealistic to wait until gc ran and all references to our objects were
officially released by the .NET wrapper objects. The team agree since managed
memory is separate from our memory and gc might not even run until the
process was shutting down (which by the way was one of the cases when
mscorwrks always crashed us) that it was understandable to "violate" the
rules. Suffice it to say that our customers could care less about .NET and
how memory is managed and when they need to close one document in order to
have enough memory to open another, we have to give up the memory of the
document being closed.

So with all that in mind, we had to find a way to make sure all wrappers
were collected before we closed a document and before we closed our
application. Furthermore, it is an undue burden, IMHO, for VB users who have
never had to worry about calling a "release" method to all of a sudden have
to not only call ReleaseComObject in all cases where they explicitly
referenced a wrapper, but also had to call ReleaseComObject in cases where
they implicitly caused a wrapper to be created such as when they connect to
one of our connection points but only implement a subset of the events. Ditto
for the cases where they invoke an API that returns an array of objects but
for which they may only access the first element or some subset of the
elements in the array. The array contains a set of wrappers, and each has to
have it com count spun down. And of course all of a sudden the code in a "for
each object in collectionX" that has worked for years has to now include
calls to ReleaseComObject too.

Getting back to cleaning up references to wrappers, I determined that the
best way for a .NET client to make sure all wrappers released all COM objects
was to make sure the wrapper itself was not referecned anymore in the VB
client process and for the VB client to perform the outlined gc steps. After
all, VB users are used to setting an object to nothing. Well perhaps not in
the "for each object in collectionX" case. Of course leaving gc to the VB
client does not keep us from getting problem reports when some VB client
fails to perform gc. That was starting to happen too often as VB 6 users
moved to Visual Studio .NET. Informing VB .NET users, almost all of which
were really VB 6 users and not professional programmers of the "new rules" of
engagement quickly lead to a small revolt in our customer base. Not just
against us but also against Microsoft as Microsoft announced that VB 6 was
being pulled from the market place.

In any case, the solution the interop developers and I came up with for our
app was for me to examine my process to see if mscorwrks was in my process
space and if so, I load my own .NET object and call a method I wrote on that
object. The method was to call collect, wait for pending finalizers and call
collect one more time. The developers said they still would not gurantee that
all the wrappers would be collected but that doing those calls gave me the
greatest probability that wrappers would indeed be collected. I also wrote a
method that did the same calls except that I collected all generations. I
have not had to call that method. The approach we took has been working.

As for the .NET team, to relieve this burden of reference counting from a
system where the programmers no longer (supposedly) have to manage lifetime
of objects, I suggested a Dispose method be implemented by the wrappers that
would be called when a wrapper object became a candidate for gc. It sure
would have been nice if I as a consumer of some other company's objects did
not have to worry about trying to force it's objects to release any hard
resources simply because this was not built in as a default to the system.
Heck I have enough to worry about without having to know whether bitmaps,
files and other expensive and limited resources are going to be held by some
object until gc runs. I was told that was an interesting concept but as I
noted before, I was told that interop development was essentially complete
and to not hold my breath for any changes in that part of .NET. What a
paradox, in an attempt to remove lifetime management from software
development, .NET actually forces VB users that have never had to manage
object lifetime to get into that business. No wonder VB users revolted. The
last I heard from our Microsoft support contact I was told that Microsoft had
moved the phase-out of VB 6 to 2008.

Regardless of whether a .NET programmer is forced to call gc to help out an
automation server because the server does not do what our server does, or the
server itself invokes gc, it is a much easier approach to implement. Give me
that any day over trying to call ReleaseComObject in a spin down loop. The
spin down is as easy as is calling gc. What can be very hard to do is find
all implicit references to wrappers so they too can be spun down. I mean who
wants to start responding to every event that is passed a com object simply
in order to spin down the wrapper's com reference count?

By the way, having a .NET programmer spin down the count to zero was another
source of of exceptions in our server. It's actually an over-release by one.
For persistent objects, we have had to make sure that we do not delete the
object in such cases by making sure our persistence manager is in charge of
persistent object deletion. That is, our implemenations of Release are now
such that when the count hits zero, the object has to ask permission of the
persist manager before calling "delete this". The case I have looked at have
always been due to this coding (error)

while( x.ReleaseComObject );


Nicholas Paldino said:
RD,

You are incorrect. If you reduce the reference count of the RCW to zero
through a call to ReleaseComObject, then the COM object is released. The
RCW isn't collected, but it doesn't matter, since any call to any method on
the RCW will result in an object disposed exception. The COM object is dead
at that point. There is not a magical reference added to the RCW which only
a GC can remove. A GC on a RCW which did not release the COM object will
just release the reference to the COM object (calling Release itself on
IUnknown) and then collect the wrapper.

What happened in your case most likely is that you missed a reference
that was added to the RCW due to re-entrancy.


--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)

RD said:
Calling ReleaseComObject will not result in the actual COM object given
its
final Release call by the interop layer. That will not happen until the GC
object performs garbage collection on the wrapper object. So you might
want
to run GC.Collect, GC.WaitForPendingFinalizers, and GC.Collect again to
force
the RCW out of existence. Otherwise, when GC does (if ever) finally run,
you
may find that mscrowrks.dll will encounter an access violation (depending
on
the implementation of the COM server) in the COM server.

At least that's how it was in the 1.1 framework. Perhaps the 2.0 framework
has been modified so that when the last reference to a RCW in the interop
layer is released, making it eligible for garbage collection, it will
release
the COM interface it is wrapping.

Unfortunately not all COM servers will refuse to shut down or unmap memory
simply because there is a reference (leak) on one of its objects. In the
RCW
case, its not really a leak, but when GC runs on its own is
psuedo-indeterminate and a COM server might be forced to unmap memory.
Calling CoDisconnectObject does not help in the interop case.

In a case I had, the COM server's file memory footprint could very easily
reach into the hundreds of megabytes or even more. When the user closes a
file to open another file, the .NET object I had was notified via an event
and released all references to objects in the file by calling
ReleaseComObject. But the app still crashed in mscorwrks when GC finally
did
run.

In the case of COM interop, the RCW object really should have a Dispose
method on it that would call Release on the interface it is holding of the
COM object. That sure would make life easier. I wonder if that made it
into
the 2.0 framework?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top