You can't prove much either way - it's an internal implementation choice that has been deliberately concealed from you.
objEarlyBound
and objLateBound
are required to have the same identity, that is ==
on them will return true. So from each of them you can always obtain the other, and ditto for any other interfaces they support, and also if you assign either of them to object
. But that doesn't prove anything much.
If you directly referenced the assembly containing the class that is exposed through COM as My.ProgId
, you could say:
IMyInterface objEarlyBound = new MyClassExposedThroughCOM();
At this point the CLR's COM support has not been required at all. But then you could pass that object to some external COM library, and at that point the CLR would create a CCW to associate with the object. And having done that once, it can always get back to the same CCW for any given CLR object.
So to relate that to your example, you start with a RCW around a COM object, you cast it to an interface, and at that point the CLR could (for all we know) query for a special internal COM interface that, if found, allows it to get hold of the internal CLR object, and thus bypass COM completely from then on. If at any time it needs to get back to the CCW, it can do so, as it has to be able to do that at any time when working in the other direction.
I've tried putting a breakpoint in a hand-implemented C++ COM object's QueryInterface function, to see what the CLR queries for. Basically it tries a lot of things, some of which I couldn't immediately identify, so it could well be "sniffing" for a fellow CLR object. It makes sense for it to do that, to avoid a crazy COM sandwich situation, in which a CLR reference points to a RCW, which points to a CCW, which points to a CLR object. This can clearly be simplified by having the CLR reference just point directly to the CLR object.
Actually now I think about it, it doesn't need to query to find this out: it has a global table of CCWs that it has previously generated, so it can just look up any new IUnknown
in there. COM objects are required to always return the exact same address for IUnknown
so it can be used for object identity comparison. Hence the CLR can always recognise a COM object that it is implementing itself, and get the corresponding CLR object.
By the way, all of this discussion assumes that the COM object is in-process. If it is out-of-process then the situation is totally different; each process has its own CLR instance and so may as well use the COM implementation of interprocess marshalling.