简体   繁体   English

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

[英]Exported function in C++ COM DLL not found from C#

I have an existing COM DLL that is currently accessed (just one function) through a VB wrapper class and called from a C# class. 我有一个现有的COM DLL,当前可以通过VB包装器类访问它(只是一个函数),并可以从C#类中调用。

I'm trying to add callbacks into my C# code (4 separate callbacks). 我试图将回调添加到我的C#代码中(4个单独的回调)。 My chosen approach is the only one that I've found but I'm having issues. 我选择的方法是我找到的唯一方法,但是遇到了问题。

It says "Unable to find an entry point named 'InitDotNet' in DLL 'xxxx'. 它说:“无法在DLL'xxxx'中找到名为'InitDotNet'的入口点。

My DLL header file: 我的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);
...

My DLL C++ file: 我的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;
}

My VB wrapper looks like this: 我的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

My C# code looks like this: 我的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");

}

Anybody have any thoughts on what I'm doing wrong? 有人对我在做什么错有任何想法吗?

Issues I see: 我看到的问题:

  1. InitDotNet takes long instead of CB_func1 and CB_func2 . InitDotNet花费的时间比CB_func1CB_func2 This is a two-fold problem for 64-bit versions of your program: it leads to an exported name mismatch for stdcall functions and, worse, it can cause pointer truncation if InitDotNet somehow manages to get called. 对于您的程序的64位版本,这有两个问题:导致stdcall函数的导出名称不匹配,更糟糕的是,如果InitDotNet以某种方式设法被调用,则可能导致指针截断。

  2. InitDotNet is not marked __stdcall . InitDotNet没有标记为__stdcall The default calling convention is cdecl. 默认的调用约定为cdecl。 The cdecl naming convention is "prefix with underscore", so the exported name is "_InitDotNet". cdecl的命名约定为“带下划线的前缀”,因此导出的名称为“ _InitDotNet”。 However, the stdcall naming convention is "prefix with underscore, postfix with @ followed by the size of the arguments, in bytes", so the expected exported name would be "_InitDotNet@8" (with the current signature of taking two longs). 但是,stdcall的命名约定是“带下划线的前缀,带@的后缀,后跟参数的大小(以字节为单位)”,因此,预期的导出名称将为“ _InitDotNet @ 8”(当前签名为两个长整数)。 You should use a program like dumpbin or depends.exe to view the names of the functions exported by your DLL. 您应该使用像dumpbin或depends.exe这样的程序来查看DLL导出的函数的名称。 This mismatch is likely the reason the runtime can't find InitDotNet , assuming 32-bit Windows . 假设是32位Windows,这种不匹配可能是运行时找不到InitDotNet的原因 You should not be specifying EntryPoint to the DllImport attribute if this is corrected (the runtime will figure out the appropriate name automatically). 如果已纠正,则不应在DllImport属性中指定EntryPoint (运行时将自动找出合适的名称)。

  3. As pointed out by cdhowie in the comments, you need to keep the two delegates you're passing to the native code "alive". 正如cdhowie在注释中指出的那样,您需要保留传递给本地代码“ alive”的两个委托。 The .NET garbage collector is incapable of knowing that the function pointers are being stored by the native code. .NET垃圾回收器无法知道功能指针是由本机代码存储的。 To prevent the garbage collector from collecting them, keep a reference to the delegates around (such as in a field for an object that is guaranteed to outlive the native code's use for them) or use a GCHandle . 为了防止垃圾收集器收集它们,请在周围保留对委托的引用(例如,在某个对象的字段中,保证该对象的使用期限超过本机代码对它们的使用)或使用GCHandle Note if you use a GCHandle : you do not need to use a pinned handle; 注意,如果使用GCHandle :不需要使用固定的句柄; the function pointer that is actually passed to your code is a stub and the stub remains in the same place even if the delegate is moved by the garbage collector. 实际上传递给您的代码的函数指针是一个存根,并且即使委托由垃圾收集器移动,该存根仍位于同一位置。 However, the stub is deleted when the delegate is collected, so it is vital to ensure the delegate does not get collected until the native code no longer needs the callbacks. 但是,在收集委托时会删除存根,因此确保在本机代码不再需要回调之前不收集委托至关重要。

COM's way of passing callbacks is interfaces. COM传递回调的方法是接口。 Trying to match unmanaged function pointers with .NET delegates is complicated and error-prone, as you've experienced. 正如您所经历的那样,尝试将非托管函数指针与.NET委托进行匹配既复杂又容易出错。 Interfaces are not as practical as delegates, but still better than function pointers. 接口不像委托那样实用,但是仍然比函数指针更好。

So if I were you I'd put the callbacks in a COM interface exported by the COM DLL: 因此,如果您是我,我会将回调放在由COM DLL导出的COM接口中:

(The following is IDL code, which must go in the .idl file associated with the C++ project.) (以下是IDL代码,它必须放在与C ++项目关联的.idl文件中。)

interface ISomeObject : IUnknown
{
    HRESULT DoTask1([in] int i);
    HRESULT DoTask2([in] BSTR s);
}

Then build the C++ project, and add the type library as a reference from the C# project. 然后构建C ++项目,并添加类型库作为C#项目的引用。 If the type library is registered, you can add it by right-clicking the C# project name in the Solution Explorer pane of Visual Studio, select Add Reference , go to the COM tab, look for the name of the type library and add it as a reference. 如果类型库已注册,则可以通过在Visual Studio的“ 解决方案资源管理器”窗格中右键单击C#项目名称来添加它,选择“ 添加引用” ,转到“ COM”选项卡,查找类型库的名称并将其添加为参考。

Once you have added a reference to the type library, you can use the COM interface as if it were a C# interface: 添加对类型库的引用后,就可以像使用C#接口一样使用COM接口:

class MyForm : AnotherForm, ISomeObject
{
      // ISomeObject methods:
    public void DoTask1(int i) { ... }
    public void DoTask2(string s) { ... }

    ...
}

Then InitDotNet would take a ISomeObject pointer, and the C# code would simply call it by passing this : 然后,InitDotNet将使用ISomeObject指针,而C#代码将通过传递以下代码来简单地调用它:

C++: C ++:

ISomeObject* g_pSomeObject;

extern "C" __declspec(dllexport) void __stdcall InitDotNet(ISomeObject* o)
{
    g_pSomeObject = o;
}

C#: C#:

[DllImport("mycppdll.dll")]
private static extern void InitDotNet(ISomeObject o);

private void DoInitDotNet()
{
    // The following works because MyForm implements ISomeObject
    InitDotNet(this);
}

But I would also make InitDotNet a method of a COM interface and not a global function. 但是我还要使InitDotNet成为COM接口的方法,而不是全局函数。

Last but not least, what's the purpose of the VB class? 最后但并非最不重要的一点是,VB类的目的是什么? If it's only purpose is only to wrap the COM class, you don't need it: COM classes/interfaces are directly consumable from C#. 如果仅用于包装COM类,则不需要它:COM类/接口可直接从C#中使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM