在没有类型库的情况下使用 C# 中的 COM dll
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1037786/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using a COM dll from C# without a type library
提问by toxvaerd
I need to use a COM component (a dll) developed in Delphi ages ago. The problem is: the dll does not contain a type library... and every interop feature (eg. TlbImp) in .NET seem to rely on TLBs. The component has been used in Delphi programs here for many years without problems because "It's not much of a problem using COM objects from Delphi, because we know the interfaces" (quote Delphi developer).
我需要使用很久以前在 Delphi 中开发的 COM 组件(一个 dll)。问题是:dll 不包含类型库……而且 .NET 中的每个互操作功能(例如 TlbImp)似乎都依赖于 TLB。该组件在 Delphi 程序中使用多年没有问题,因为“使用 Delphi 的 COM 对象没有太大问题,因为我们知道接口”(引用 Delphi 开发人员)。
Is there any way I can use this DLL from c# without a TLB? I've tried using the DLL as unmanaged, but the only method it exports are DllUnregisterServer
, DllRegisterServer
, DllCanUnloadNow
and DllGetClassObject
. I know the names of the classes and functions I'm going to use, if that can be of any help.
有什么方法可以在没有 TLB 的情况下从 c# 使用这个 DLL?我已经使用DLL作为未试过,但唯一的方法是出口DllUnregisterServer
,DllRegisterServer
,DllCanUnloadNow
和DllGetClassObject
。我知道我将要使用的类和函数的名称,如果这有帮助的话。
UPDATE:I've tried implementing Jeff's suggestion, but I'm getting this error:
更新:我已尝试实施杰夫的建议,但出现此错误:
"Unable to cast COM object of type 'ComTest.ResSrvDll' to interface type 'ComTest.IResSrvDll'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{75400500-939F-11D4-9E44-0050040CE72C}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE))."
“无法将‘ComTest.ResSrvDll’类型的 COM 对象转换为接口类型‘ComTest.IResSrvDll’。此操作失败,因为 COM 组件上的 QueryInterface 调用 IID 为‘{75400500-939F-11D4-9E44-0050040CE72C} ' 由于以下错误而失败:不支持此类接口(来自 HRESULT 的异常:0x80004002 (E_NOINTERFACE))。”
This is what I've done:
这就是我所做的:
I got this interface definition from one of the Delphi-guys:
我从一个 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.
From this I've made this interface
从这个我做了这个界面
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);
}
}
And this coclass (got the guid from the delphi-guys)
还有这个coclass(从delphi-guys那里得到了guid)
using System.Runtime.InteropServices;
namespace ComTest
{
[ComImport]
[Guid("75400503-939F-11D4-9E44-0050040CE72C")]
public class ResSrvDll
{
}
}
UPDATE
更新
The solution from Jeff is the way to do it. It is worth noticing, though, that the interface definition must match the COM-components exactly! ie. same order, same names, etc.
杰夫的解决方案就是这样做的方法。不过值得注意的是,接口定义必须与 COM 组件完全匹配!IE。相同的顺序,相同的名称等。
采纳答案by Jeff Moser
You just need the CLS_ID and interface id. I wrote about this specific issue on my blog:
您只需要 CLS_ID 和接口 ID。我在我的博客上写了关于这个特定问题的文章:
回答by Groo
You can also do late bindingand then invoke methods through reflection (myObject.InvokeMember("NameOfTheMethod", options, params, etc.)
).
您还可以进行后期绑定,然后通过反射 ( myObject.InvokeMember("NameOfTheMethod", options, params, etc.)
)调用方法。
A wrapper should, however, offer better performance and faster marshalling.
然而,包装器应该提供更好的性能和更快的编组。
回答by JaredPar
Yes and no.
是和否。
All C# (and any CLR language) needs in order communicate with a COM object is a compatible interface signature. Typically specifying the methods, GUID and apartment style of the interface. If you can add this definition into your code base then the TLB is not necessary.
为了与 COM 对象通信,所有 C#(和任何 CLR 语言)都需要兼容的接口签名。通常指定接口的方法、GUID 和单元样式。如果您可以将此定义添加到您的代码库中,则不需要 TLB。
There is a small caveat that comes with that statement. I believe you will get into trouble if you try and use a COM object across apartment boundaries and don't have a suitable TLB registered. I can't 100% remember on this point though.
该声明有一个小警告。我相信如果您尝试跨单元边界使用 COM 对象并且没有注册合适的 TLB,您会遇到麻烦。我不能100%记住这一点。
回答by John Gwynn
Write a wrapper in VB.Net. VB.Net supports true late binding (no messy reflection). All you need is the progId. You should also implement IDisposable to explicitely manage the component lifecycle.
在 VB.Net 中编写一个包装器。VB.Net 支持真正的后期绑定(无杂乱反射)。您只需要progId。您还应该实现 IDisposable 以明确管理组件生命周期。
回答by Brian
I suspect that the dynamic
keyword (C# 4.0) will accomplish this. If it does, it will give results which are largely equivalent to invoking the methods, i.e. how Groo suggests.
我怀疑dynamic
关键字 (C# 4.0) 会实现这一点。如果是这样,它将给出与调用方法大致相同的结果,即 Groo 建议的结果。
回答by Ryan VanIderstine
It is quite frequent that you will encounter an interface implementation that is not backed by a type library (Delphi or otherwise). Shell extensions are one example.
您经常会遇到不受类型库(Delphi 或其他)支持的接口实现。Shell 扩展就是一个例子。
You basically need to make a Windows API call to create the instance through the proper COM function calls. The API will take care of managing the DLL via the exported functions you mentioned earlier.
您基本上需要进行 Windows API 调用以通过正确的 COM 函数调用来创建实例。API 将通过您之前提到的导出函数负责管理 DLL。
You will need to recreate the interface definition in C# code but after that you simply create the object, cast it to the interface, and it is no different than anything else. The only real caveat here is, depending on your usage, you may have some threading issues to deal with so check the "threading model" that was used for the DLL and consider your usage based on that.
您将需要在 C# 代码中重新创建接口定义,但之后您只需创建对象,将其转换为接口,它与其他任何东西都没有什么不同。这里唯一真正的警告是,根据您的使用情况,您可能有一些线程问题需要处理,因此请检查用于 DLL 的“线程模型”,并在此基础上考虑您的使用情况。
Here is a link to a tutorial on consuming the interfaces that are not TLB based. Tutorial
这是有关使用不基于 TLB 的接口的教程的链接。 教程
回答by Daniel Earwicker
If you've managed to create an instance of the object, you're over the first major hurdle!
如果您已经成功创建了对象的实例,那么您就跨越了第一个主要障碍!
Now try this:
现在试试这个:
myObject.GetType().InvokeMember(
"ResObjOpen", // method name goes here
BindingFlags.InvokeMethod,
null,
myObject,
new object[] {
someClientID, // arguments go here
someSubId,
somFileName,
someInt} );
The reason I think you may need to do this is if the Delphi COM object is not a "dual" object. It may only support late binding, i.e. the kind of invocation you see above.
我认为您可能需要这样做的原因是 Delphi COM 对象不是“双”对象。它可能只支持后期绑定,即您在上面看到的那种调用。
(In C# 4.0 they're making this easier with the dynamic
keyword.)
(在 C# 4.0 中,他们使用dynamic
关键字使这更容易。)
EDIT:Just noticed something very suspicious. The IID for the interface and the CLSID for the object itself appear to be the same. That's not right.
编辑:刚刚注意到一些非常可疑的事情。接口的 IID 和对象本身的 CLSID 似乎相同。那是不对的。
Given that you've succeeded in creating the object, it would appear to be the CLSID of the object. So it's not the right IID. You need to go back to your Delphi folks and ask them to tell you what the IID of the interface IResSrvDll
is.
鉴于您已成功创建该对象,它似乎是该对象的 CLSID。所以这不是正确的 IID。您需要回到 Delphi 人员那里,让他们告诉您接口的 IID 是什么IResSrvDll
。
Edit again:You could try changing the enum member you specify from ComInterfaceType
. There should be ones for IDispatch
and "dual" - although as your object doesn't support IDispatch
, neither of those should be the right choice. The IUnknown
setting (which appears in your sample code) should work - suggesting that the IID is wrong.
再次编辑:您可以尝试更改您从ComInterfaceType
. 应该有 forIDispatch
和 "dual" - 虽然你的对象不支持IDispatch
,但这些都不应该是正确的选择。该IUnknown
设置(出现在您的示例代码中)应该可以工作 - 表明 IID 是错误的。