在没有类型库的情况下使用来自 C# 的 COM dll

Posted

技术标签:

【中文标题】在没有类型库的情况下使用来自 C# 的 COM dll【英文标题】:Using a COM dll from C# without a type library 【发布时间】:2010-11-05 11:38:36 【问题描述】:

我需要使用很久以前在 Delphi 中开发的 COM 组件(一个 dll)。问题是:dll 不包含类型库……而且 .NET 中的每个互操作功能(例如 TlbImp)似乎都依赖于 TLB。该组件已在 Delphi 程序中使用多年,没有出现任何问题,因为“使用 Delphi 的 COM 对象不是什么大问题,因为我们知道接口”(引用 Delphi 开发人员)。

有没有什么方法可以在没有 TLB 的情况下从 c# 使用这个 DLL?我尝试将 DLL 用作非托管的,但它导出的唯一方法是 DllUnregisterServerDllRegisterServerDllCanUnloadNowDllGetClassObject。我知道我要使用的类和函数的名称,如果有帮助的话。

更新: 我已尝试实施 Jeff 的建议,但出现此错误:

“无法将类型为“ComTest.ResSrvDll”的 COM 对象转换为接口类型“ComTest.IResSrvDll”。此操作失败,因为对具有 IID 的接口的 COM 组件调用 QueryInterface '75400500-939F- 11D4-9E44-0050040CE72C' 由于以下错误而失败:不支持此类接口(来自 HRESULT 的异常:0x80004002 (E_NOINTERFACE))。”

这就是我所做的:

我从 Delphi 的一个人那里得到了这个接口定义:

unit ResSrvDllIf;

interface

type
   IResSrvDll = interface
   ['75400500-939F-11D4-9E44-0050040CE72C']
    procedure clearAll;

    function  ResObjOpen(const aClientID: WideString; const aClientSubID: WideString;
                         const aResFileName: WideString; aResShared: Integer): Integer; safecall;
    ...
   end;
implementation
end.

由此我制作了这个界面

using System.Runtime.InteropServices;
namespace ComTest

    [ComImport]
    [Guid("75400500-939F-11D4-9E44-0050040CE72C")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IResSrvDll
    
        int ResObjOpen(string aClientID, string aClientSubID, string aResFileName, int aResShared);

    

还有这个 coclass(从 delphi-guys 那里得到了指导)

using System.Runtime.InteropServices;

namespace ComTest

    [ComImport]
    [Guid("75400503-939F-11D4-9E44-0050040CE72C")]
    public class ResSrvDll
    
    

更新

Jeff 的解决方案就是解决问题的方法。不过值得注意的是,接口定义必须与 COM 组件完全匹配! IE。相同的顺序,相同的名称等。

【问题讨论】:

这个dll的源代码丢失了吗? 不,但 Delphi 开发人员目前非常支持,一想到要进入这个老庞然大物,他们就会翻白眼;-) 【参考方案1】:

你也可以late binding,然后通过反射调用方法(myObject.InvokeMember("NameOfTheMethod", options, params, etc.))。

但是,包装器应该提供更好的性能和更快的编组。​​

【讨论】:

这要求对象支持后期绑定。并非所有对象都实现 IDispatch。 鉴于他现在收到的错误消息,可能是该对象仅支持 IDispatch!【参考方案2】:

是和不是。

为了与 COM 对象通信,所有 C#(和任何 CLR 语言)都需要一个兼容的接口签名。通常指定接口的方法、GUID 和单元样式。如果您可以将此定义添加到您的代码库中,则不需要 TLB。

该声明附带一个小警告。如果您尝试跨公寓边界使用 COM 对象并且没有注册合适的 TLB,我相信您会遇到麻烦。不过,我不能 100% 记得这一点。

【讨论】:

【参考方案3】:

在 VB.Net 中编写一个包装器。 VB.Net 支持真正的后期绑定(没有杂乱的反射)。您只需要 progId。您还应该实现 IDisposable 以明确管理组件生命周期。

【讨论】:

我不懂 VB,并且想避免在混合中加入任何其他语言。应该可以仅使用 C# 找到解决方案。但是感谢您的回答:-)【参考方案4】:

我怀疑 dynamic 关键字 (C# 4.0) 将完成此操作。如果是这样,它将给出在很大程度上等同于调用方法的结果,即 Groo 建议的方式。

【讨论】:

【参考方案5】:

您只需要 CLS_ID 和接口 ID。我在博客上写了关于这个特定问题的文章:

“Using Obscure Windows COM APIs in .NET”

【讨论】:

这看起来很有希望......我会试试看!【参考方案6】:

您经常会遇到不受类型库(Delphi 或其他)支持的接口实现。 Shell 扩展就是一个例子。

您基本上需要调用 Windows API 来通过适当的 COM 函数调用来创建实例。 API 将通过您前面提到的导出函数来管理 DLL。

您将需要在 C# 代码中重新创建接口定义,但之后您只需创建对象,将其强制转换为接口,它与其他任何东西没有什么不同。这里唯一真正需要注意的是,根据您的使用情况,您可能需要处理一些线程问题,因此请检查用于 DLL 的“线程模型”,并据此考虑您的使用情况。

这是一个关于使用不基于 TLB 的接口的教程的链接。 Tutorial

【讨论】:

【参考方案7】:

如果您成功地创建了对象的实例,那么您就克服了第一个主要障碍!

现在试试这个:

myObject.GetType().InvokeMember(
                      "ResObjOpen",  // method name goes here
                      BindingFlags.InvokeMethod,
                      null,
                      myObject,
                      new object[]  
                         someClientID,   // arguments go here
                         someSubId, 
                         somFileName, 
                         someInt );

我认为您可能需要这样做的原因是 Delphi COM 对象不是“双重”对象。它可能只支持后期绑定,即您在上面看到的那种调用。

(在 C# 4.0 中,他们使用 dynamic 关键字使这变得更容易。)

编辑:刚刚注意到一些非常可疑的事情。接口的 IID 和对象本身的 CLSID 似乎相同。这是不对的。

假设您已成功创建对象,它似乎是该对象的 CLSID。所以这不是正确的 IID。你需要回到你的 Delphi 人员那里,让他们告诉你接口 IResSrvDll 的 IID 是什么。

再次编辑:您可以尝试更改从ComInterfaceType 指定的枚举成员。应该有 IDispatch 和“双重”的 - 尽管您的对象不支持 IDispatch,这些都不应该是正确的选择。 IUnknown 设置(出现在您的示例代码中)应该可以工作 - 表明 IID 错误。

【讨论】:

嗯 - 我已经设法创建了一个实例,是的,但我不能将它转换为接口类型。 正确 - 强烈暗示它不支持该接口,而不是字面意思。它可能只支持称为 IDispatch 的低级接口。上面的代码示例通过使用 IDispatch 进行调用在内部工作。你试过了吗? 这给了我一个“COM 目标没有实现 IDispatch。”。此外,我想避免使用反射。 其实它们不一样...我怀疑是一样的,但是第8位不同。 delphi 的人说这就是 Delphi 创建 guid 的方式。 啊,我的错……即便如此,CLR 也没有骗你。该对象不支持该接口。

以上是关于在没有类型库的情况下使用来自 C# 的 COM dll的主要内容,如果未能解决你的问题,请参考以下文章

如何在没有来自 c 库的 printf 的情况下在汇编级编程中打印整数?

解析 JWT 令牌以仅在 C# 或 Blazor 中没有外部库的情况下获取有效负载内容

在没有库的情况下读取 Excel 文件

如何在没有任何 vue 库的情况下在 vue 回调中获取 http 响应标头(axios)

在没有互联网或使用库的情况下配置 IQKeyboardManager?

如何使用映射类型删除索引签名