From: Igor R. on
> But, let's assume that the object returned by "SDKLib.Child()" is a COM object wrapped in RCW.

Right. I deal with COM-objects in C#, so all the entities in my
example are COM-objects as they're seen from within .NET (i.e. RCWs).

>  Let's further assume that "someParent.setChild(child)" for whatever reason does _not_ retain a reference to the RCW object that was returned by "SDKLib.Child()"
> (perhaps it itself is an RCW, and thus the only thing it internally is retaining is the unmanaged COM interface reference).

Exaclty.

> If all that's true, then yes…without any references to the wrapper object, it will be GC'ed, and the COM object released when that happens.

The COM object is not released, as it's referenced internally by
"someParent" (i.e. by its underlying COM object) after I apply
setChild(). But the wrapping RCW is really GC-ed.

> As for the solution, it's the same as if you had a managed object with an event that you subscribed to.  You have to keep a reference to it, otherwise the object with the event will be collected.  Why should the RCW be any different?

Ok, I see... That's what I'm doing.

I just thought that maybe there exists some way to reference the RCW
*inside* setChild() method. I.e. I'd like to cause the following
expression to increase child's RCW reference count:
aParent.setChild(child) -- where aParent itself is a RCW of a COM
object and setChild is COM method.
By the way, what if "child" were a regular C# object exposing a COM
interface? IIUC, then setChild() would receive its CCW, and the CCW
would reference the underlying .NET object -- right? So it wouldn't
get GC-ed! What I want is to get a similar effect with RCW.
What I don't quite understand is what happens exactly when I pass
"child" RCW to the setChild() method. Inside setChild, do I still have
an access to the "child" RCW, or it's "unwrapped", and the "naked" COM
object is passed as a parameter?
From: Peter Duniho on
Igor R. wrote:
> [...]
> The COM object is not released, as it's referenced internally by
> "someParent" (i.e. by its underlying COM object) after I apply
> setChild(). But the wrapping RCW is really GC-ed.

It's released by the RCW (as in, the RCW calls the IUnknown.Release()
method). Just because its reference count didn't reach zero when it was
released doesn't mean it wasn't released.

> [...]
> I just thought that maybe there exists some way to reference the RCW
> *inside* setChild() method. I.e. I'd like to cause the following
> expression to increase child's RCW reference count:
> aParent.setChild(child) -- where aParent itself is a RCW of a COM
> object and setChild is COM method.

..NET doesn't use reference counting. The RCW doesn't itself have a
reference count to be increased. The COM object's reference count may
be increased by the setChild() method itself, but that only affects the
COM object itself, not the RCW that is wrapping it.

> By the way, what if "child" were a regular C# object exposing a COM
> interface? IIUC, then setChild() would receive its CCW, and the CCW
> would reference the underlying .NET object -- right?

Yes. Going the other way, with a managed implementation of a COM object
referenced outside managed code, obviously the reference count from
outside managed code must affect the lifetime of the managed implementation.

> So it wouldn't
> get GC-ed! What I want is to get a similar effect with RCW.

The "similar effect" follows the same rules for any other managed
object: you have to keep a reference to it somewhere.

> What I don't quite understand is what happens exactly when I pass
> "child" RCW to the setChild() method. Inside setChild, do I still have
> an access to the "child" RCW, or it's "unwrapped", and the "naked" COM
> object is passed as a parameter?

The unmanaged COM object's setChild() method will see only the unmanaged
COM object that was wrapped by the RCW. .NET's COM marshaling has
"unwrapped" the object by that point in the call chain. It cannot be
any other way; after all, what would unmanaged code expecting a plain,
unmanaged COM interface instance do with a managed object reference?

Pete
From: Igor R. on
> It's released by the RCW (as in, the RCW calls the IUnknown.Release()
> method). Just because its reference count didn't reach zero when it was
> released doesn't mean it wasn't released.

Ah, sorry, by saying "released" I meant "totally released", i.e. freed/
disposed.


> The unmanaged COM object's setChild() method will see only the unmanaged
> COM object that was wrapped by the RCW. .NET's COM marshaling has
> "unwrapped" the object by that point in the call chain. It cannot be
> any other way; after all, what would unmanaged code expecting a plain,
> unmanaged COM interface instance do with a managed object reference?

Ok, I see, that's the way it works... Too bad!
But IMHO it *could* (should?) behave another way. After all, the whole
purpose of all the RCW/CCW machinery is to make the COM <--> .NET
interaction as transparent and seemless to the user as possible. And
what we get now? Assume there're 2 COM classes: Class1 is from an
unmanaged COM server and Class2 is a .NET object - both exposing
*exactly* the same COM interface defined in the unmanaged SomeLib.

IMyInterface class1 = new SomeLib.Class1();
IMyInterface class2 = new Class2();

And an unmanaged Container COM class:

IContainer container = new SomeLib.Container();

Now, what a surprise:

container.add(class1); // class1 will be GC-ed soon
container.add(class2); // class2 will be alive!

IMHO, such a behaviour is not transparent and not intuitive. It
requires from the user to understand all the CCW/RCW machinery, and to
adjust his code accordingly.

But it could be done another way. If RCW is a managed wrapping object
that exposes the relevant interface (or pretends so), then why not to
wrap it with CCW, just like any other managed COM-object? If class1
would be wrapped with CCW *on the top* of RCW, when passing it to an
unmanaged code, the whole mess would be avoided!
From: Peter Duniho on
Igor R. wrote:
> [...]
> Ok, I see, that's the way it works... Too bad!
> But IMHO it *could* (should?) behave another way. After all, the whole
> purpose of all the RCW/CCW machinery is to make the COM <--> .NET
> interaction as transparent and seemless to the user as possible.

I'd say "as practical". But sure, it's there to help you.

> And what we get now?

Something that is entirely consistent with the rest of .NET.

> Assume there're 2 COM classes: Class1 is from an
> unmanaged COM server and Class2 is a .NET object - both exposing
> *exactly* the same COM interface defined in the unmanaged SomeLib.
>
> IMyInterface class1 = new SomeLib.Class1();
> IMyInterface class2 = new Class2();
>
> And an unmanaged Container COM class:
>
> IContainer container = new SomeLib.Container();
>
> Now, what a surprise:
>
> container.add(class1); // class1 will be GC-ed soon
> container.add(class2); // class2 will be alive!
>
> IMHO, such a behaviour is not transparent and not intuitive. It
> requires from the user to understand all the CCW/RCW machinery, and to
> adjust his code accordingly.

I disagree. The behavior is entirely intuitive.

The whole point of a GC system is for the GC to clean up objects that,
in the specific context of the memory managed by the GC, are no longer
needed.

In your example above, there is nothing left referencing the managed
object for "class1". Thus, it's no longer needed and the GC can clean
it up. For "class2", there is still something left referencing the
managed object, and thus it _is_ still needed and does not get cleaned up.

> But it could be done another way. If RCW is a managed wrapping object
> that exposes the relevant interface (or pretends so), then why not to
> wrap it with CCW, just like any other managed COM-object?

Because the RCW isn't there to provide access to a managed
implementation of the COM interface. It's there to provide managed
access to an _unmanaged_ implementation of the COM interface. The RCW
needs to exist only as long as there is managed code referring to it,
because the only reason the RCW exists at all is to provide a conduit
from managed code to the unmanaged interface.

Also, the idea of marshaling COM calls through TWO layers of wrapper
rather than one, just so you can have your RCW lifetimes match that of
the unmanaged object seems rather silly to me. That's a significant
performance hit, to accomplish something your own code really ought to
be doing itself anyway (and which it has to for managed objects that
provide the exact same kind of features anyway!)

> If class1
> would be wrapped with CCW *on the top* of RCW, when passing it to an
> unmanaged code, the whole mess would be avoided!

IMHO, the only "mess" here is your expectation that a managed object
that is used only by your managed code should somehow be magically
preserved without your managed code explicitly referring to it.

Consider this code, containing nothing involving COM at all:

class A
{
public event EventHandler Event;
}

class B
{
void Method()
{
new A().Event += Handler;
}

void Handler(object sender, EventArgs e) { }
}

How long do you think the instance of A allocated in B.Method() should
live when B.Method() is called?

I know how long _I_ think it should live: no longer than the next
collection cycle.

This is the exact same scenario you are dealing with.

Pete