3

I've run into an issue that I think is related to the way the CLR interops with COM objects, but I was hoping some folks here could maybe offer a little more insight. I want to apologize in advance for the vagueness of the question, unfortunately I'm integrating with a somewhat opaque system.

Consider the following code:

class Foo
{
    private IComInterface comObject;

    Foo(IServiceProvider provider)
    {
        this.comObject = serverProvider.GetService(typeof(ISomeService)) as IComInterface;
        Debug.Assert(this.comObject != null); // comObject is *not* null here
    }

    void Bar()
    {
        IOtherComInterface otherInterface = this.comObject as IOtherComInterface;
        Console.WriteLine(otherInterface == null);
    }
}

The COM interop types are embedded in my assembly, which is loaded as a plugin by another program. When I first create an instance of Foo, the COM object provided by the service provider (which is provided by the program) is non-null. However, when I immediately call Bar(), the cast to IComOtherInterface doesn't work: the method prints "true".

My problem, though, is that after some other plugins are loaded, calling Bar() again prints "false". I've verified that it's the same instance of Foo, and in fact the same instance of comObject (I tagged both with IDs using the debugger and the numbers didn't change). So now the cast is succeeding.

So my question is this: how is this possible? Is it possible that the object stored in comObject is actually wrapping a new native COM object the second time through the same RCW? Is it possible that loading other assemblies has somehow changed the type-identity of IOtherComInterface so that the cast now works? Some other crazy possibility that I can't actually fathom?

G K
  • 33
  • 2
  • 1
    Maybe the COM object's `QueryInterface` method is incorrectly implemented and yields inconsistent results. – Henrik Jul 15 '14 at 07:29
  • 1
    What about threading? Successful and failing calls, are they coming from the same thread (COM apartment, to be exact - this is what is important). – Roman R. Jul 15 '14 at 07:30
  • I tend to agree with @RomanR. - when you use `as` you effectively try to `QueryInterface()` which will fail if the interface pointer has to be marshalled and marshalling is not possible for whatever reason. – sharptooth Jul 15 '14 at 07:59
  • @RomanR. Yes, that's it! It looks like the first call is actually happening on a background thread, whereas the second is on the UI thread. Thanks so much for the pointer, if you want to put that down as an answer I'd be happy to accept it :) – G K Jul 15 '14 at 08:02

1 Answers1

3

COM objects "live" in their apartments, so do their proxies. Passing "raw" COM interface pointers across apartment boundaries (which might sometimes read "across thread boundaries") is a typical reason to have COM interface pointers still operational in terms of no access violations and otherwise crashes, but failing on the expected tasks, including failing to return another interface pointer via QueryInterface.

Once you have a COM interface pointer, you are interested to use it in the COM apartment and respective threads, where you obtained the pointer on.

To pass pointers between apartments/threads, you need to take additional steps (marshal the pointer on original thread, and then unmarshal back on the target thread). See also:

Roman R.
  • 68,205
  • 6
  • 94
  • 158
  • Either this, or you have to take care of ensuring that the interface can be marshalled when needed. – sharptooth Jul 15 '14 at 08:19
  • Marshaling is a part of this story. Failing `QueryInterface` basically means that the code is already dealing with marshaled pointer and then misuses threading. – Roman R. Jul 15 '14 at 08:24