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

时间:2009-06-24 11:31:51

标签: c# delphi com interop

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

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

更新 我已经尝试过实施Jeff的建议,但我收到了这个错误:

“无法将'ComTest.ResSrvDll'类型的COM对象转换为接口类型'ComTest.IResSrvDll'。此操作失败,因为QueryInterface调用COM组件上的IID为{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(得到了德尔福人的指导)

using System.Runtime.InteropServices;

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

更新

杰夫的解决方案是这样做的。但值得注意的是,接口定义必须与COM组件完全匹配 !即。相同的顺序,相同的名称等。

7 个答案:

答案 0 :(得分:12)

您只需要CLS_ID和接口ID。我在博客上写过这个具体问题:

Using Obscure Windows COM APIs in .NET

答案 1 :(得分:2)

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

答案 2 :(得分:2)

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

您基本上需要进行Windows API调用才能通过正确的COM函数调用来创建实例。 API将通过您之前提到的导出函数来管理DLL。

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

是和否。

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

该陈述附带一个小警告。我相信如果您尝试在公寓边界使用COM对象并且没有注册合适的TLB,您将遇到麻烦。我不能百分之百地记住这一点。

答案 5 :(得分:1)

如果您已设法创建该对象的实例,那么您已经超越了第一个主要障碍!

现在试试这个:

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错误。

答案 6 :(得分:0)

我怀疑dynamic关键字(C#4.0)会实现这一点。如果是这样,它将给出的结果大致相当于调用方法,即Groo的建议。