2

我有一个现有的 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");

}

有人对我做错了什么有任何想法吗?

4

2 回答 2

3

我看到的问题:

  1. InitDotNet需要很长时间而不是CB_func1and CB_func2。对于 64 位版本的程序来说,这是一个双重问题:它会导致 stdcall 函数的导出名称不匹配,更糟糕的是,如果InitDotNet设法以某种方式被调用,它可能会导致指针截断。

  2. InitDotNet 未标记__stdcall。默认调用约定是 cdecl。cdecl 命名约定是“带下划线的前缀”,因此导出的名称是“_InitDotNet”。但是,stdcall 命名约定是“带有下划线的前缀,带有 @ 的后缀,后跟参数的大小,以字节为单位”,因此预期的导出名称将是“_InitDotNet@8”(当前签名采用两个长整数)。你应该使用像dumpbin 或depends.exe这样的程序来查看你的DLL 导出的函数的名称。这种不匹配可能是运行时找不到的原因InitDotNet,假设是 32 位 WindowsEntryPointDllImport

  3. 正如 cdhowie 在评论中指出的那样,您需要保持您传递给本机代码的两个委托“活动”。.NET 垃圾收集器无法知道函数指针是由本机代码存储的。为了防止垃圾收集器收集它们,请保留对委托的引用(例如在保证比本机代码对它们的使用长的对象的字段中)或使用GCHandle. 请注意,如果您使用GCHandle:您不需要使用固定手柄;实际传递给您的代码的函数指针是一个存根,即使垃圾收集器移动了委托,该存根仍保留在同一位置。但是,在收集委托时会删除存根,因此确保在本机代码不再需要回调之前不会收集委托至关重要。

于 2013-01-17T19:42:42.523 回答
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# 项目名称来添加它,选择Add Reference,转到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# 中使用。

于 2013-01-18T08:50:58.027 回答