Loading a COM object in C # raises the exception "It is not possible to pass a COM object of type" System .__ ComObject "to an interface type ...", but C ++ or VB does not

I need to make a COM server in unmanaged C ++ and a COM client in C #. I found the COM Hello World tutorial in C ++ ( http://antonio.cz/static/com/5.html ). The page is in Czech. The COM server displays a MessageBox with the text "Hello world" after the Print () call function from the IHello interface. The source code is here: http://antonio.cz/static/com/Hello.zip . The archive contains the source code of the COM server and COM client in C ++, and it works.

But my C # COM client is not working. This is a C # console application with a link to "Interop.Hello.dll". I make the interop dll command:

tlbimp Hello.tlb /out:Interop.Hello.dll 

C # code:

 static void Main(string[] args) { Interop.Hello.IHello Hello = new Interop.Hello.CHello(); Hello.Print(); } 

But the C # client throws an exception:

 Unable to cast COM object of type 'System.__ComObject' to interface type 'Interop.Hello.CHello'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{B58DF060-EAD9-11D7-BB81-000475BB5B75}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)). 

I also tried loading the COM server from Visual Basic. And it works. I made a console application in VB with a link to "Interop.Hello.dll".

VB Code:

 Module Module1 Sub Main() Dim ic As Interop.Hello.CHello ic = CreateObject("MyCorporation.Hello") ic.Print() End Sub End Module 

I debugged a COM server when booting from a C # client. The QueryInterface () method returns S_OK when the variable "riid" is an IHello interface.

Any ideas why C # code is not working?

+4
source share
2 answers

Support for this interface is not supported.

The error message is ambiguous. Everyone will consider that their interface is not supported, IHello in your case. But this is not the case and the error message does not make it clear enough. This is an IMarshal interface that is not supported.

COM takes care of the programming details that .NET does not; it does not ignore threads. Topics are known to be difficult to get right, there is a lot of code that is not thread safe .. NET allows you to use such code in a work thread and will not mind that you are mistaken, as a rule, it is very difficult to diagnose an error. COM designers initially thought threading was too difficult to get better, and they should be taken care of by smart people. And itโ€™s built in the infrastructure, in any case, to use code that is not thread safe in the workflow. Which works very well, it takes care of 95% of typical threading issues. The last 5%, however, usually gives you a pretty serious headache. Like this.

A COM component, such as yours, can publish whether it is safe to use it from a stream in the registry. The name of the registry value is "ThreadingModel". A very common value, also by default when it is absent, is โ€œApartmentโ€. The explanation of the apartments is slightly beyond the scope of this answer, it really means "I'm not thread safe." The COM infrastructure ensures that any calls to the object are made from the same thread that created the object, thereby ensuring thread safety.

This, however, requires some magic. Marshaling a call from one thread to a specific other thread is a very non-trivial thing..NET makes it simple with the Dispatcher.BeginInvoke and Control.BeginInvoke methods, but it hides a rather large code iceberg, which is 99% under water. And COM does not do this easily, it lacks the .NET function, which simplifies its implementation, it does not directly support Reflection.

For example, you need to create a stack frame in the target stream so that a call can be made. This requires knowledge of how the arguments of the method look. COM needs help with this, it does not know what they look like, because it cannot rely on Reflection. Two pieces of code are required, called a proxy server and a stub. The proxy knows what the arguments look like and serializes the method arguments into an RPC package. This code is automatically called by COM using a dummy interface that looks exactly like the original interface, but with every method that calls a proxy call. In the target stream, the stub code receives an RPC packet, creates a stack frame, and makes a call.

All this may seem familiar in .NET terms, this is exactly how .NET Remoting and WCF work. Except that .NET can automatically create proxies and stubs thanks to Reflection. In COM, they must be created by you. Two main ways to do this, the general way is to describe the IDL COM interfaces and compile it using the midl.exe tool. Which can automatically generate a proxy server and a stub code from the interface descriptions in the IDL. Or is there an easy way available when your COM server is limited to a subset of Automation and can generate a type library. In this case, you can use the proxy / stub implementation built into Windows, it uses a type library to figure out what the arguments look like. Which is really very similar to Reflection. With an additional step, it is necessary to register this in the registry, HKCR \ Interfaces, so COM can find the code it needs.

So really an exception message means that COM cannot find a way to marshal the call. He looked in the registry and could not find the registry key for the proxy / stub. He then asked your COM object: "Do you know how to marshal yourself?" request for IMarshal. The answer was no! and that was the end of it, leaving you with an exception message that is pretty hard to interpret. The bug report is the Achilles heel.


Next, I need to focus on why COM decided that it should route calls to your COM server, which you did not expect. One of the main requirements for threads calling COM objects is that it needs to tell COM what support it provides for marshaling calls. What is the second thing that is difficult to do, besides creating a stack frame, the call must be made in a very specific thread that created the COM object. The code that implements the thread should make this possible, and it is not trivial. This requires a solution to the common problem of the manufacturer / consumer , a common problem in software development. Where "producer" is the thread that made the call, and "consumer" is the thread that created the object.

So the thread should tell COM: "I have implemented a solution to the producer / consumer problem, go ahead and arbitrarily create it." A universal solution to the problem is well known to most Windows programmers; it is a "message loop" that implements a GUI stream.

You tell COM about this very early, every thread that calls COM calls must call CoInitializeEx (). You can specify one of two options, you can specify COINIT_APARTMENTTHREADED (aka STA) to promise that you will give a safe home to COM objects that are not thread safe. There again the word "apartment". Or you can specify COINIT_MULTITHREADED (aka MTA), which basically says that you are not doing anything to help COM and leave it in the COM infrastructure to sort it.

The .NET program does not call CoInitializeEx () directly; the CLR makes a call for you. You also need to know if your stream is STA or MTA. You do this with the Main () method attribute for the main thread of your program, specifying either [STAThread] or [MTAThread]. MTA is the default, also the default and the only option for threadpool threads. Or, when you create your own thread, you specify it by calling Thread.SetApartmentState ().

The combination of an MTA and a COM object that is not thread safe, or, in other words, the script "I do nothing to help COM," is part of the problem here. You force COM to give the object a safe home. The COM infrastructure will create a new thread , automatically, an STA thread. This should not be any other way to guarantee that the calls to the object will be thread safe, as you have refused help. This way, any call you make at the facility will be marshaled. This is quite inefficient, creating your own STA stream avoids the cost of marshaling. But most importantly, COM will require a proxy and a stub to call. You did not implement them so that kaboom.

This worked in your C ++ client code because it is probably called CoInitialize (). What STA chooses. And it worked in your VB.NET code because vb.net runtime support automatically selects the language-specific STA, it does a lot of things automatically to help programmers fall into the pit of success.

But this is not a C # way, it does very few things automatically. You have kaboom because your Main () method does not have the [STAThread] attribute, so it defaults to MTA.

However, note that this is not actually a technically correct solution. When you promise an STA, you must also fulfill that promise. Which says that you are solving a producer / consumer problem. This requires that you throw the message loop, Application.Run () in .NET. You have not done so.

Breaking this promise can have unpleasant consequences. COM will rely on your promise and try to mobilize the challenge when it needs it, waiting for it to work. This will not work, the call will not be sent to your thread since you are not calling GetMessage (). You do not consume. You can easily see this with the debugger, the thread will be deadlocked, the call just never ends. Threaded COM servers often also assume that your STA thread runs a message loop and will use it to implement its own cross-thread marshaling, usually by calling PostMessage () from a work thread. A good example is the WebBrowser control. A side effect of this PostMessage () message, which is not going anywhere, is usually that the component will not raise an event or otherwise fail to perform duties. In the case of WebBrowser, you will never get a DocumentCompleted event, for example.

It looks like your COM server is not making these assumptions and that you otherwise are not making workflow calls. Or, you would notice that it is faulty with your C ++ or VB.NET client code. This is a dangerous assumption, which can be a byte at any time, but you can quite handle it.

+14
source

The correct C # code is:

  [STAThread] static void Main(string[] args) { Interop.Hello.IHello Hello = new Interop.Hello.CHello(); Hello.Print(); } 
+3
source

All Articles