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:
更新:我已经尝试过实施Jeff的建议,但是我收到了这个错误:
"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'。此操作失败,因为对于具有IID'{75400500-939F-11D4-9E44-0050040CE72C}的接口的COM组件上的QueryInterface调用'由于以下错误而失败:不支持此类接口(来自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(得到了德尔福人的指导)
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.
Jeff的解决方案是实现这一目标的方法。但值得注意的是,接口定义必须与COM组件完全匹配!即。相同的顺序,相同的名称等
7 个解决方案
#1
You just need the CLS_ID and interface id. I wrote about this specific issue on my blog:
您只需要CLS_ID和接口ID。我在博客上写过这个具体问题:
"Using Obscure Windows COM APIs in .NET"
“在.NET中使用模糊的Windows COM API”
#2
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以明确管理组件生命周期。
#3
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.
您基本上需要通过正确的COM函数调用进行Windows API调用来创建实例。 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的接口的教程的链接。教程
#4
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.
所有C#(和任何CLR语言)需要与COM对象进行通信是兼容的接口签名。通常指定接口的方法,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,您将遇到麻烦。我不能百分之百地记住这一点。
#5
You can also do late binding and then invoke methods through reflection (myObject.InvokeMember("NameOfTheMethod", options, params, etc.)
).
您也可以进行后期绑定,然后通过反射调用方法(myObject.InvokeMember(“NameOfTheMethod”,options,params等))。
A wrapper should, however, offer better performance and faster marshalling.
但是,包装器应该提供更好的性能和更快的编组。
#6
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.
我怀疑动态关键字(C#4.0)会实现这一点。如果是这样,它将给出的结果大致相当于调用方法,即Groo如何建议。
#7
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人员并要求他们告诉你IResSrvDll接口的IID是什么。
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更改指定的枚举成员。应该有IDispatch和“dual” - 虽然你的对象不支持IDispatch,但这些都不是正确的选择。 IUnknown设置(显示在示例代码中)应该有效 - 表明IID是错误的。
#1
You just need the CLS_ID and interface id. I wrote about this specific issue on my blog:
您只需要CLS_ID和接口ID。我在博客上写过这个具体问题:
"Using Obscure Windows COM APIs in .NET"
“在.NET中使用模糊的Windows COM API”
#2
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以明确管理组件生命周期。
#3
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.
您基本上需要通过正确的COM函数调用进行Windows API调用来创建实例。 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的接口的教程的链接。教程
#4
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.
所有C#(和任何CLR语言)需要与COM对象进行通信是兼容的接口签名。通常指定接口的方法,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,您将遇到麻烦。我不能百分之百地记住这一点。
#5
You can also do late binding and then invoke methods through reflection (myObject.InvokeMember("NameOfTheMethod", options, params, etc.)
).
您也可以进行后期绑定,然后通过反射调用方法(myObject.InvokeMember(“NameOfTheMethod”,options,params等))。
A wrapper should, however, offer better performance and faster marshalling.
但是,包装器应该提供更好的性能和更快的编组。
#6
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.
我怀疑动态关键字(C#4.0)会实现这一点。如果是这样,它将给出的结果大致相当于调用方法,即Groo如何建议。
#7
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人员并要求他们告诉你IResSrvDll接口的IID是什么。
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更改指定的枚举成员。应该有IDispatch和“dual” - 虽然你的对象不支持IDispatch,但这些都不是正确的选择。 IUnknown设置(显示在示例代码中)应该有效 - 表明IID是错误的。