在没有类型库的情况下使用来自 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 用作非托管的,但它导出的唯一方法是 DllUnregisterServer
、DllRegisterServer
、DllCanUnloadNow
和 DllGetClassObject
。我知道我要使用的类和函数的名称,如果有帮助的话。
更新: 我已尝试实施 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 中没有外部库的情况下获取有效负载内容
如何在没有任何 vue 库的情况下在 vue 回调中获取 http 响应标头(axios)