在 C# 中使用 ComInterop 的 COM 对象后期绑定

Posted

技术标签:

【中文标题】在 C# 中使用 ComInterop 的 COM 对象后期绑定【英文标题】:COM object late binding using ComInterop in C# 【发布时间】:2017-11-27 08:52:12 【问题描述】:

我有一个用于一些简单数学实用程序的 COM 对象。其中,它导出 2 个接口 - IDL 如下:

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);
;

然后IOneDimSolver的实现是:

coclass Brent

    [default] interface IOneDimSolver;
;

如果我想在 C# 中使用这个对象并且我有可用的 TypeLibrary,那么使用这个功能非常容易——我可以实现一些函数来解决:

public class CalculatePi : IUnivariateFunction

    public double evaluate(double x)
    
        return x - Math.PI;
    

然后使用它:

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

这很好用,所以没有问题。但是,我还想证明我可以将它与后期绑定一起使用(此时几乎纯粹用于学术目的)。

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() );

看起来brent 对象创建成功,但对InvokeMember 的调用失败并显示消息"Specified cast is not valid."

我猜CalculatePi 的类型与 COM IDispatch 机器所期望的不兼容,但我只是不确定如何使其工作。任何帮助都会很棒!

【问题讨论】:

首先,您可以使用dynamic 关键字与COM 后期绑定,它更容易,如dynamic brent = /*create instance*/; var result brent.Solve(-10.0, 10.0, 1E-10, 100, new CalculatePi());。如果这仍然不起作用,IDispatch 是如何实现的? IOneDimSolver 接口可能没用,后期绑定只会使用 IDispatch。 您说得对,将其创建为dynamic 会更好!不幸的是,我得到了完全相同的错误 - 我会考虑 IDispatch,我不完全确定接下来要尝试什么。 你这个绝对的巫师@HansPassant,这正是问题所在!非常感谢!如果您想将其发布为答案,我很乐意接受:) 【参考方案1】:

"指定的转换无效。"

这是一条 CLR 异常消息,由 InvalidCastException 产生。这极大地限制了这种事故的可能原因,您知道实际上并不是本机代码炸弹。在后期绑定代码中没有很多可能的原因。我只能想到一个。

最可能的原因是CalculatePi 类缺少[ComVisible(true)] 属性。因此,当 CLR 试图从对象中获取 IDispatch 接口指针时,它就会崩溃。它在早期绑定的情况下工作,C# 编译器可以从类型库中得知它需要生成 IUnivariateFunction 引用。这是可见的,因为它来自互操作库。

应该解决问题。但请记住,IOneDimSolver::Solve() 方法实际上与喜欢 使用后期绑定的代码类型不兼容。脚本语言不知道如何实现 IUnivariateFunction 接口,它对它一无所知。您应该声明参数IDispatch*,以便任何人都可以调用它。在实现中,使用QueryInterface() 获取IUnivariateFunction 接口指针。当它成功时,您会很高兴,让您可以快速轻松地拨打电话。但是当它没有时必须回退到 IDispatch::Invoke() 。如果您不想编写该代码,那么最好不要承诺 IDispatch 支持并从 IUnknown 派生接口。

【讨论】:

很好的答案和很好的建议 - 我更新了 IOneDimSolver 界面以采用 IDispatch*,它按预期工作。谢谢!

以上是关于在 C# 中使用 ComInterop 的 COM 对象后期绑定的主要内容,如果未能解决你的问题,请参考以下文章

PB调用C#编写的WINFORM,并且注册好的COM组件

COM Interop .NET 程序集 - 一个术语问题

C# 中的非托管 C++ 类

将 BSTR 字符串作为托管代码和非托管代码之间的边界传递(COM 互操作)

在 C++ 中使用 COM 互操作对象

COM Interop 和 .NET 3.5 的问题 - 调试和发布模式下的不同行为