在 C# 中找不到 C++ COM DLL 中的导出函数

Posted

技术标签:

【中文标题】在 C# 中找不到 C++ COM DLL 中的导出函数【英文标题】:Exported function in C++ COM DLL not found from C# 【发布时间】: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");


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

【问题讨论】:

这不是您的入口点错误的原因,但请注意,只要非托管代码可能调用,您就需要保留对存储在 f1cf2c 中的委托的引用他们。否则,它们可能会在非托管代码调用它们之前被垃圾回收。如果在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 的参数应该是指针,而不是长。 【参考方案1】:

我看到的问题:

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

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

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

【讨论】:

谢谢,必须用完,但明天我会根据您的建议进行修改,并更新进展情况。感谢您的意见。 const 实际上是签名的一部分。它是***常量,即wchar_t* const,被排除在外。 我指的是编组器,而不是语言。编组器不能编组“常量”。 其实我忘了默认的 CharSet 是 Ansi(真的吗?是 2013 年的人!)。完全删除了该段落。 很奇怪。依赖说导出的函数名称是“InitDotNet”。不是您建议的“_InitDotNet”。关于您的第 1 点,我按照您的建议更改了代码以反映功能点。仍然有同样的问题。我还删除了入口点的规范——也反映在上面的更新代码中。我的系统上似乎没有 dumpbin,有 VS 2008 并没有附带它。【参考方案2】:

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的Solution Explorer窗格中右键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# 中使用。

【讨论】:

我想知道您是否假设我的 DLL 是 C# dll。它是用 c++ 编写的较旧的 DLL。通过四处搜索,这种方法是我在一般方法中所能找到的。是的,VB 类只是一个包装器,因为假设没有其他方法可以访问 C++ DLL 中的 COM 方法。您可以在 C++ 代码上方看到 - 您能否建议一种架构,允许我按照您上面建议的方式调用“mainCall”方法传递回调?也许是一个样本? 不,我知道 DLL 是 C++ COM DLL。但是你是说它是第三方DLL,不能修改吗?因为要按照我的建议进行操作,您肯定需要对其进行修改。 我可以修改它。我需要一些关于 c++ 代码如何解释接口指针的解释。 好的,我添加了更多内容来解释如何操作。

以上是关于在 C# 中找不到 C++ COM DLL 中的导出函数的主要内容,如果未能解决你的问题,请参考以下文章

如何处理 C++ 加载的 C# DLL 中的异常

如何在c#中使用C++ dll导出类[重复]

使用来自 C# 的托管 C++ dll

项目在 Visual Studio 2015 PrivateAssemblies 中找不到 dll

从 C# 调用 C++ dll 中的方法,没有任何信息,只有头文件

在 C++ 中找不到 AoS 和 SoA 之间的性能差异