Search code examples
c#comcom-interopidispatch

COM object late binding using ComInterop in C#


I've a COM object for some simple mathematical utilities. Amongst others, it exports 2 interfaces - the IDL is as follows:

interface IUnivariateFunction : IDispatch
{
    HRESULT evaluate([in] double a, [out, retval] double* b);
};

interface IOneDimSolver : IDispatch
{
    HRESULT Solve([in] double min, [in] double max, [in] double targetAccuracy, [in] LONG maxIterations, [in] IUnivariateFunction* function, [out, retval] double* result);
};

And then an implementation of IOneDimSolver is:

coclass Brent
{
    [default] interface IOneDimSolver;
};

If I want to use this object in C# and I have the TypeLibrary available, using this functionality is very easy - I can implement some function to solve:

public class CalculatePi : IUnivariateFunction
{
    public double evaluate(double x)
    {
        return x - Math.PI;
    }
}

And then use it:

var brent = new Brent();
var result = brent.Solve(-10.0, 10.0, 1E-10, 100, new CalculatePi());

This works really well, so no issues there. However, I also want to demonstrate that I can use it with late binding (almost purely for academic purposes at this point).

var brent = Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")));
var result = brent.GetType().InvokeMember("Solve", BindingFlags.InvokeMethod, null, brent, new object[] { -10.0, 10.0, 1E-10, 100, new CalculatePi() });

It looks like the brent object is being created OK, but the call to InvokeMember fails with the message {"Specified cast is not valid."}

I'm guessing the type of CalculatePi isn't compatible with whatever the COM IDispatch machinery is expecting, but I'm just not sure how to make it work. Any help would be great!


Solution

  • {"Specified cast is not valid."}

    This is a CLR exception message, produced by InvalidCastException. That greatly limits the possible reasons for this mishap, you know that it isn't actually the native code that bombs. There are not a lot of possible reasons for it in late-bound code. I can only think of one.

    The most likely reason is that the CalculatePi class is missing the [ComVisible(true)] attribute. So the CLR keels over when it tries to obtain the IDispatch interface pointer from the object. It worked in the early-bound case, the C# compiler could tell from the type library that it needed to produce a IUnivariateFunction reference. Which is visible since it came from the interop library.

    Ought to fix the problem. But keep in mind that the IOneDimSolver::Solve() method is not in fact compatible with the kind of code that likes to use late-binding. A scripting language does not know how to implement the IUnivariateFunction interface, it doesn't know anything about it. You should declare the parameter IDispatch* so anybody can call it. In the implementation, use QueryInterface() to obtain the IUnivariateFunction interface pointer. You'll be happy when it succeeds, allowing you to make the call quickly and easily. But have to fall back to IDispatch::Invoke() when it doesn't. If you don't want to write that code then it is arguably better to not promise IDispatch support and derive the interface from IUnknown.