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

11
我需要使用一个在很久以前用Delphi开发的COM组件(dll)。问题是:该dll不包含类型库...而且.NET中的每个Interop功能(例如TlbImp)似乎都依赖于TLB。该组件已经在这里的Delphi程序中使用了很多年,没有问题,因为“从Delphi使用COM对象并不是太大的问题,因为我们知道接口”(引用Delphi开发人员的话)。
有没有办法在没有TLB的情况下从C#使用此DLL?我尝试将DLL用作非托管代码,但它导出的唯一方法是DllUnregisterServer、DllRegisterServer、DllCanUnloadNow和DllGetClassObject。如果这可以有所帮助,我知道要使用的类和函数的名称。
更新: 我尝试了Jeff的建议,但我收到了以下错误消息:
“无法将类型为“ComTest.ResSrvDll”的COM对象强制转换为接口类型“ComTest.IResSrvDll”。此操作失败,因为针对IID“{75400500-939F-11D4-9E44-0050040CE72C}”的COM组件上的QueryInterface调用失败,原因是出现以下错误:不支持此类接口 (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 开发者那里获取了 guid)

using System.Runtime.InteropServices;

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

更新

Jeff的解决方案是正确的。需要注意的是,接口定义必须与COM组件完全匹配!即相同的顺序、相同的名称等。


这个dll的源代码丢失了吗? - Scott Langham
不过,Delphi开发人员目前非常忙,他们听到这个老古董的事情就会翻白眼。;-) - toxvaerd
7个回答

12

1
这看起来很有前途... 我会试一下! - toxvaerd

2
如果您成功创建了对象的实例,那么您已经跨过了第一个重要障碍!现在请尝试这样做:
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错误。

好的 - 我已经成功创建了一个实例,但是我无法将其转换为接口类型。 - toxvaerd
正确 - 强烈建议它不支持该接口,而不是字面上的意思。它可能只支持称为IDispatch的低级接口。上面的代码示例通过使用IDispatch来进行调用内部工作。你试过吗? - Daniel Earwicker
这给了我一个“COM目标未实现IDispatch”的错误。此外,我想避免使用反射。 - toxvaerd
实际上它们不一样...我怀疑是同样的事情,但第8位数字不同。Delphi的开发人员说这只是Delphi创建guid的方式。 - toxvaerd
啊,我的错误…即便如此,CLR也没有欺骗你。那个对象不支持该接口。 - Daniel Earwicker
谢谢您的帖子。我遇到了一个类似的问题,使用第三方COM库的方法返回“对象”类型。我知道返回的对象包含我需要的一些属性,但是没有实际的类型库。我能够使用您的解决方案获取所需的属性,例如:var xml = objectInfo.GetType().InvokeMember("xml", System.Reflection.BindingFlags.GetProperty, null, objectInfo, null); - Mark Uebel

2

使用VB.Net编写一个包装器。VB.Net支持真正的后期绑定(无需混乱的反射)。您只需要progId即可。您还应该实现IDisposable以显式管理组件的生命周期。


我不会VB,而且希望避免在混合中加入任何其他语言。只使用C#应该可以找到解决方案。但还是谢谢你的回答 :-) - toxvaerd

2
很常见的情况是,您会遇到没有类型库(无论是Delphi还是其他)支持的接口实现。Shell扩展就是一个例子。
您基本上需要通过适当的COM函数调用来创建实例的Windows API调用。API将通过您之前提到的导出函数来管理DLL。
您需要在C#代码中重新创建接口定义,但之后您只需创建对象,将其强制转换为接口,它与任何其他内容没有区别。唯一真正的注意事项是,根据您的使用情况,您可能需要处理一些线程问题,因此请检查用于DLL的“线程模型”,并根据此考虑您的使用情况。
这里是一个链接,指向一个关于消耗非TLB接口的教程。 教程

1
你也可以使用晚期绑定,然后通过反射调用方法(myObject.InvokeMember("NameOfTheMethod", options, params, etc.))。
然而,包装器应该提供更好的性能和更快的编组。

这需要对象支持迟绑定。并非所有对象都实现了IDispatch。 - Rob Kennedy
1
根据他现在收到的错误信息,可能是该对象仅支持IDispatch! - Daniel Earwicker

1

是和不是。

C#(以及任何CLR语言)与COM对象通信所需的仅是兼容的接口签名。通常需要指定接口的方法、GUID和公寓风格。如果您可以将此定义添加到代码库中,则不需要TLB。

这个说法有一个小小的警告。我认为,如果您尝试跨公寓边界使用COM对象并且没有适当的TLB注册,则会遇到麻烦。不过在这一点上,我记不清了。


0
我怀疑使用C# 4.0中的关键字dynamic可以实现这一点。如果真的可以,那么结果在很大程度上等同于调用方法,就像Groo所建议的那样。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接