Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why cannot I cast my COM object to the interface it implements in C#?

Tags:

c#

com

I have this interface in the dll (this code is shown in Visual Studio from metadata):

#region Assembly XCapture.dll, v2.0.50727
// d:\svn\dashboard\trunk\Source\MockDiagnosticsServer\lib\XCapture.dll
#endregion

using System;
using System.Runtime.InteropServices;

namespace XCapture
{
    [TypeLibType(4160)]
    [Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")]
    public interface IDiagnostics
    {
        [DispId(1)]
        void GetStatusInfo(int index, ref object data);
    }
}

So I created a COM server with such class:

[ComVisible(true)]
[Guid(SimpleDiagnosticsMock.CLSID)]
[ComDefaultInterface(typeof(IDiagnostics))]
[ClassInterface(ClassInterfaceType.None)]
public class SimpleDiagnosticsMock : ReferenceCountedObject, IDiagnostics
{
    public const string CLSID = "281C897B-A81F-4C61-8472-79B61B99A6BC";

    // These routines perform the additional COM registration needed by 
    // the service. ---- stripped from example

    void IDiagnostics.GetStatusInfo(int index, ref object data)
    {
        Log.Info("GetStatusInfo called with index={0}, data={1}", index, data);

        data = index.ToString();
    }
}

Server seems to work fine, and I am able to use the object from a VBScript. But then I try to use it from another C# client:

    [STAThread]
    static void Main(string[] args)
    {
        Guid mockClsId = new Guid("281C897B-A81F-4C61-8472-79B61B99A6BC");
        Type mockType = Type.GetTypeFromCLSID(mockClsId, true);
        IDiagnostics mock = (IDiagnostics)Activator.CreateInstance(mockType);

        //var diag = mock as IDiagnostics;

        object s = null;
        mock.GetStatusInfo(3, ref s);

        Console.WriteLine(s);
        Console.ReadKey();
    }

And it fails with

Unable to cast COM object of type 'System.__ComObject' to interface type 'XCapture.IDiagnostics'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

What am I doing wrong?

I have also tried to use InvokeMember, and that kinda worked except that I wasn't able to get the ref-returned data parameter.

EDIT: added STAThread attribute to my Main procedure. This does not solve the issue, but you really should use STAThread with COM unless you're absolutely sure you don't need it. See Hans Passant's answer below.

like image 640
catbert Avatar asked Jun 05 '13 11:06

catbert


People also ask

Can you cast something to an interface?

Yes, you can. If you implement an interface and provide body to its methods from a class. You can hold object of the that class using the reference variable of the interface i.e. cast an object reference to an interface reference.

Can we assign object to interface in C#?

Like abstract classes, interfaces cannot be used to create objects (in the example above, it is not possible to create an "IAnimal" object in the Program class) Interface methods do not have a body - the body is provided by the "implement" class. On implementation of an interface, you must override all of its methods.

What is casting to an interface?

You can access the members of an interface through an object of any class that implements the interface. For example, because Document implements IStorable , you can access the IStorable methods and property through any Document instance: Document doc = new Document("Test Document"); doc.


1 Answers

This exception can be a DLL Hell problem. But the simplest explanation is for what's missing from your snippet. Your Main() method is missing the [STAThread] attribute.

That's an important attribute that matters when you use COM objects in your code. Most of them are not thread-safe and they require a thread that's a hospitable home for code that cannot support threading. The attribute forces the state of the thread, the one you can set explicitly with Thread.SetApartmentState(). Which you can't do for the main thread of an app since Windows starts it, so the attribute is used to configure it.

If you omit it then you the main thread joins the MTA, the multi-threaded apartment. COM is then forced to create a new thread to give the component a safe home. Which requires all calls to be marshaled from your main thread to that helper thread. The E_NOINTERFACE error is raised when COM cannot find a way to do that, it requires a helper that knows how to serialize the method arguments. That's something that needs to be taken care of by the COM developer, he didn't do that. Sloppy but not unusual.

A requirement of an STA thread is that it also pumps a message loop. The kind you get in a Winforms or WPF app from Application.Run(). You don't have one in your code. You might get away with it since you don't actually make any calls from a worker thread. But COM components tend to rely on the message loop to be available for their own use. You'll notice this by it misbehaving, not raising an event or deadlocking.

So start fixing this by applying the attribute first:

[STAThread]
static void Main(string[] args)
{
    // etc..
}

Which will solve this exception. If you have the described event raising or deadlock problems then you'll need to change your application type. Winforms is usually easy to get going.

I cannot otherwise take a stab at the mocking failure. There are significant deployment details involved with COM, registry keys have to be written to allow COM to discover components. You have to get the guids right and the interfaces have to be an exact match. Regasm.exe is required to register a .NET component that's [ComVisible]. If you try to mock an existing COM component, and got it right, then you'll destroy the registration for the real component. Not so sure that's worth pursuing ;) And you'll have a significant problem adding a reference to the [ComVisible] assembly, the IDE refuses to allow a .NET program to use a .NET assembly through COM. Only late binding can fool the machine. Judging from the COM exception, you haven't gotten close to mocking yet. Best to use the COM component as-is, also a real test.

like image 146
Hans Passant Avatar answered Oct 21 '22 18:10

Hans Passant