Reputation: 19585
I'm having the problem described in this question: Unable to cast COM object of type exception which manifests as the error:
Unable to cast COM object of type 'System.__ComObject' to interface type 'IMyInterface'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{GUID}' failed due to the follow error: No such interface supported
My WPF app is calling a .NET library that eventually calls a COM object. This works fine, but it runs on the main thread and blocks the UI. When I spawn a new thread and call the library from it, I'm getting that error. None of those solutions on the other question are working for me. I'm trying to understand how the runtime can have the type information loaded, but not share it between threads.
I understand that WPF apps are STA, and I know that means that any objects moving between threads will be COM marshalled. I don't understand how the type information to say "This is a COM object, and its GUID is this" can be in the AppDomain, but inaccessible to the second thread.
Where does loaded type information live? Is it in the AppDomain, or per thread? In any case, how can I make the threads share type information? How can I fix this?
I've read this:
http://www.codeproject.com/Articles/9190/Understanding-The-COM-Single-Threaded-Apartment-Pa
and this:
http://msdn.microsoft.com/en-us/library/ms973913.aspx#rfacomwalk_topic10
and a bunch of other stuff that talks about COM interop, but hasn't helped me fix it.
As per Hans Passant's answer, I'm creating my library object inside the second STA thread:
var thread = new Thread(delegate()
{
var aeroServer = new AeroServerWrapper(Config.ConnectionString);
var ct = new CancellationToken();
aeroServer._server.MessageReceived += ServerMessageReceived;
aeroServer.Go(@"M:\IT\Public\TestData\file.dat", ct);
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
I understand that I might need to call Application.Run() to start the message queue if events fail to fire but I'm not getting that far: it crashes as soon as it attempts to create the COM object. The AeroServerWrapper is in a separate DLL, which calls a second DLL, which finally attempts to instantiate the COM object.
Any hints or articles would be greatly appreciated. I'd like to solve this as it is: my Plan B is to wrap the library in a console app, spawn the console app from the UI, and get status messages from it through a named pipe. That'll work, but it seems ugly.
Upvotes: 4
Views: 690
Reputation: 941585
This goes wrong because you are calling the COM interface method from another thread. COM ensures that COM classes that declare themselves to be not thread-safe are called in a thread-safe way. The ThreadModel key in the registry specifies this, a very common value is "Apartment" (or is missing), indicating that the class is not thread-safe.
So if you call the interface method from another thread then COM steps in and marshals the call to the thread on which the object was created, thus ensuring thread-safety. A pretty nice feature, entirely missing from .NET classes. However, COM needs help when it marshals the interface method arguments. Required because the call is made on another thread and the method call argument values need to be copied. Reflection is not a COM feature. The first thing it does is look in the registry, the HKCR\Interface\{guid}
key for the ProxyStubClsid key, the guid of a helper class that knows how to serialize the arguments.
Clearly that key is missing on your machine. Next thing it does is ask the COM object for the IMarshal interface. Clearly your COM server doesn't implement it. And that produces the E_NOINTERFACE error. Producing good error messages has never been COM's strength.
Well, the writing is on the wall. The COM class you are using is not thread-safe and is missing all the plumbing required to make calling it thread-safe. It is normally pretty easy to provide the proxy/stub but the author of the component didn't bother, not entirely uncommon. Not that it would help if he did, that plumbing ensures the method runs on the UI thread, surely what you were trying to avoid in the first place.
The only reasonable thing you can do is create your own thread, call its SetApartmentState() method to switch to STA and create the object on that thread so the class is used in a thread-safe manner. No concurrency, but at least it is concurrent with the rest of your code. That thread must typically also pump a message loop, Application.Run(), you might get away with not pumping. You'll know you need to pump when you see deadlock or events don't get fired. You can find example code in this post.
Upvotes: 6
Reputation: 5405
The .NET interface type IMyInterface which is a wrapper for the actual COM interface type is loaded alright - your problem is not that it is somehow not "visible" in the other thread. The actual type of the object you're trying to cast is, as you can see, System.__ComObject, which does not implement that interface anyway. It is not treated like any other .NET object when casting and type checking (is/as operators). What happens is that .NET runtime asks the COM object for the interface you're trying to cast to by calling QueryInterface. The call fails because the COM object does not correctly marshal (or otherwise: the end result is that it fails). It manifests as a InvalidCastException because it is the closest thing in meaning in the .NET world to a QueryInterface returning E_NOINTERFACE in the COM world.
Upvotes: 1