简体   繁体   中英

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.

I'm trying to add callbacks into my C# code (4 separate callbacks). 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'.

My DLL header file:

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:

...
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:

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:

...
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 . 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.

  2. InitDotNet is not marked __stdcall . The default calling convention is cdecl. The cdecl naming convention is "prefix with underscore", so the exported name is "_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). You should use a program like dumpbin or depends.exe to view the names of the functions exported by your DLL. This mismatch is likely the reason the runtime can't find InitDotNet , assuming 32-bit Windows . You should not be specifying EntryPoint to the DllImport attribute if this is corrected (the runtime will figure out the appropriate name automatically).

  3. As pointed out by cdhowie in the comments, you need to keep the two delegates you're passing to the native code "alive". The .NET garbage collector is incapable of knowing that the function pointers are being stored by the native code. 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 . Note if you use a GCHandle : you do not need to use a pinned handle; 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. Trying to match unmanaged function pointers with .NET delegates is complicated and error-prone, as you've experienced. 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:

(The following is IDL code, which must go in the .idl file associated with the C++ project.)

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. 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.

Once you have added a reference to the type library, you can use the COM interface as if it were a C# interface:

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 :

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);
}

But I would also make InitDotNet a method of a COM interface and not a global function.

Last but not least, what's the purpose of the VB class? 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#.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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