【发布时间】:2013-01-17 18:17:29
【问题描述】:
我有一个现有的 COM DLL,当前通过 VB 包装类访问(只有一个函数)并从 C# 类调用。
我正在尝试将回调添加到我的 C# 代码中(4 个单独的回调)。我选择的方法是我发现的唯一方法,但我遇到了问题。
它显示“无法在 DLL 'xxxx' 中找到名为 'InitDotNet' 的入口点。
我的 DLL 头文件:
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);
...
我的 DLL C++ 文件:
...
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;
}
我的 VB 包装器如下所示:
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
我的 C# 代码如下所示:
...
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");
}
有人对我做错了什么有任何想法吗?
【问题讨论】:
-
这不是您的入口点错误的原因,但请注意,只要非托管代码可能调用,您就需要保留对存储在
f1c和f2c中的委托的引用他们。否则,它们可能会在非托管代码调用它们之前被垃圾回收。如果在InitDotNet()返回后非托管代码不会调用它们,那么您需要在InitDotNet()调用之后对两个委托对象调用GC.KeepAlive()。否则,请确保将它们存储在某个字段中。 -
看起来您在 Dllimport 语句中定义的入口点不正确,或者运行时出于某种原因找不到它。你熟悉垃圾箱吗?如果您在有问题的 .dll 上运行 dumpbin /exports,您可以看到入口点名称实际上是什么。编辑:或 cdhowie 所说的。你仍然可以检查入口点,只是为了完整性:)
-
你能确认一下在 InitDotNet 的声明中定义了什么 DLL 吗?
-
为什么 InitDotNet 需要很长时间而不是 CB_func1 和 CB_func2?为什么在互操作签名中声明 InitDotNot 时未标记 __stdcall(默认为 cdecl)(注意:这可能是运行时找不到导出的原因;调用约定会更改导出的名称)?您需要告诉运行时将 func2Callback 的参数编组为 char*,例如[MarshalAs(UnmangedType.LPStr)]。您需要让代表保持活动状态,这样 GC 就不会收集它们(它对本机代码一无所知;请参阅 cdhowie 的评论)。
-
附注鉴于 64 位 Windows 是 LLP64,C++ 中的长数据类型在该平台上的大小为 4 字节。如果您使用两个 long,stdcall 命名约定会将 InitDotNet 导出为“_InitDotNet@8”。 64 位运行时需要导出“_InitDotNet@16”,因为它需要指针;因此,InitDotNet 的参数应该是指针,而不是长。