我有一个现有的COM DLL,当前通过VB包装类访问(只有一个函数)并从C#类调用。
我正在尝试将回调添加到我的C#代码中(4个单独的回调)。我选择的方法是我发现的唯一一个,但我遇到了问题。
它说“无法在DLL'xxxx'中找到名为'InitDotNet'的入口点。
extern "C"
{
#define DLL __declspec(dllexport)
typedef void (__stdcall * CB_func1)(int);
typedef void (__stdcall * CB_func2)(char *);
DLL void InitDotNet(CB_func1 func1, CB_func2 func2);
}
...
class CComInterface : public CCmdTarget
...
afx_msg void mainCall(short parm1, LPCTSTR parm2);
...
...
CB_func1 func1Function;
CB_func2 func2Function;
...
IMPLEMENT_DYNCREATE(CComInterface, CCmdTarget)
...
BEGIN_DISPATCH_MAP(CComInterface, CCmdTarget)
DISP_FUNCTION(CComInterface, "mainCall", mainCall, VT_EMPTY, VTS_I2 VTS_BSTR)
END_DISPATCH_MAP()
...
IMPLEMENT_OLECREATE(CComInterface, "MyDll.Interface", ...)
...
void CComInterface::mainCall(short parm1, LPCTSTR parm2)
{
...
// at various times call func1Functoin and func2Function
...
}
DLL void InitDotNet(CB_func1 func1, CB_func2 func2)
{
func1Function = func1;
func2Function = func2;
}
Public Class MyWrapperClass
Private Shared Protocol As Object = CreateObject("MyDll.Interface")
Public Shared Sub mainCall(ByVal parm1 As Short, ByVal parm2 As String)
Protocol.mainCall(parm1, parm2)
End Sub
End Class
...
using System.Runtime.InteropServices
namespace MyNamespace
{
public partial class MyForm : AnotherForm
{
...
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void func1Callback(int value);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void func2Callback(string value);
[DllImport("mycppdll.dll")]
public static extern void InitDotNet([MarshalAs(UnmanagedType.FunctionPtr)] func1Callback f1c,
[MarshalAs(UnmanagedType.FunctionPtr)] func2Callback f2c);
...
private void MyFunc()
{
func1Callback f1c =
(value) =>
{
// work here
};
func2Callback f2c =
(value) =>
{
// work here
};
InitDotNet(f1c, f2c);
MyWrapperDll.MyWrapperClass.mainCall(1, "One");
}
有人对我做错了什么有任何想法吗?
答案 0 :(得分:3)
我看到的问题:
InitDotNet
需要很长时间,而不是CB_func1
和CB_func2
。对于64位版本的程序,这是一个双重问题:它导致stdcall函数的导出名称不匹配,更糟糕的是,如果InitDotNet
以某种方式设法被调用,它可能导致指针截断。
InitDotNet未标记为__stdcall
。默认调用约定是cdecl。 cdecl命名约定是“带下划线的前缀”,因此导出的名称为“_InitDotNet”。但是,stdcall命名约定是“带下划线的前缀,后缀带@后跟参数的大小,以字节为单位”,因此预期的导出名称将为“_InitDotNet @ 8”(当前签名为两个长整数)。您应该使用dumpbin或depends.exe之类的程序来查看DLL导出的函数的名称。 这种不匹配可能是运行时无法找到InitDotNet
的原因,假设32位Windows 。如果更正了(EntryPoint
属性,则不应指定DllImport
(运行时将自动找出相应的名称)。
正如cdhowie在评论中指出的那样,你需要让你传递给本机代码的两个代表“活着”。 .NET垃圾收集器无法知道本机代码正在存储函数指针。要防止垃圾收集器收集它们,请保持对周围代理的引用(例如,在保证比本机代码使用的对象更长的对象的字段中)或使用GCHandle
。请注意,如果您使用GCHandle
:您不需要使用固定手柄;实际传递给您的代码的函数指针是一个存根,即使代理由垃圾收集器移动,存根仍保留在同一位置。但是,在收集委托时会删除存根,因此确保在本机代码不再需要回调之前不会收集委托是至关重要的。
答案 1 :(得分:1)
COM传递回调的方式是接口。正如您所经历的那样,尝试将非托管函数指针与.NET代理进行匹配是复杂且容易出错的。接口不像代理那样实用,但仍然比函数指针更好。
所以,如果我是你,我会把回调放在COM DLL导出的COM接口中:
(以下是IDL代码,必须放在与C ++项目关联的.idl文件中。)
interface ISomeObject : IUnknown
{
HRESULT DoTask1([in] int i);
HRESULT DoTask2([in] BSTR s);
}
然后构建C ++项目,并将类型库添加为C#项目的引用。如果已注册类型库,则可以通过右键单击Visual Studio的解决方案资源管理器窗格中的C#项目名称来添加它,选择添加引用,转到< strong> COM 标签,查找类型库的名称并将其添加为参考。
添加对类型库的引用后,可以像使用C#接口一样使用COM接口:
class MyForm : AnotherForm, ISomeObject
{
// ISomeObject methods:
public void DoTask1(int i) { ... }
public void DoTask2(string s) { ... }
...
}
然后InitDotNet将采用ISomeObject指针,C#代码只需通过传递 this 来调用它:
C ++:
ISomeObject* g_pSomeObject;
extern "C" __declspec(dllexport) void __stdcall InitDotNet(ISomeObject* o)
{
g_pSomeObject = o;
}
C#:
[DllImport("mycppdll.dll")]
private static extern void InitDotNet(ISomeObject o);
private void DoInitDotNet()
{
// The following works because MyForm implements ISomeObject
InitDotNet(this);
}
但我也会将InitDotNet作为COM接口的方法,而不是全局函数。
最后但并非最不重要的是,VB类的目的是什么?如果它的唯一目的只是包装COM类,则不需要它:COM类/接口可直接从C#中使用。