Correct way to call a C DLL method from C#

2019-07-22 07:57发布

I'm trying to execute some methods (in this particular case, rdOnAllDone) from a third party DLL, written in C, and looking trough the headers files, I found this:

#ifndef TDECLSDONE
#ifdef STDCALL
#define     CCON        __stdcall
#else
#define     CCON        __cdecl
#endif
#define TDECLSDONE
#endif
#define     DLLIMP      __declspec (dllimport)
DLLIMP int CCON rdOnAllDone (void(CCON *)(int));

After goggling for a way to call this method, I made this:

[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(Delegate d);
public delegate void rdOnAllDoneCallbackDelegate();

private static void rdOnAllDoneCallback()
{
    Console.WriteLine("rdOnAllDoneCallback invoked");
}

The method was called correctly except that I couldn't get the int parameter. So I tried adding the input parameter int like this

[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(Delegate d);
public delegate void rdOnAllDoneCallbackDelegate(int number);

private static void rdOnAllDoneCallback(int number)
{
    Console.WriteLine("rdOnAllDoneCallback invoked " + number);
}

But now delegate is called twice and and it crashes the program with the following error " vshosts32.exe has stopped working"

What's the correct way to call this DLL method?

EDIT: Forgot to add the Main method:

public static void Main()
{
    rdOnAllDoneCallbackDelegate del3 = new rdOnAllDoneCallbackDelegate(rdOnAllDoneCallback);
rdOnAllDone(del3);

    while (true)
    {
        Thread.Sleep(1000);
    }
}

标签: c# .net c dll
2条回答
何必那么认真
2楼-- · 2019-07-22 08:31

Your delegates signature has to match that of the native callback, also it has to have the UnmanagedFunctionPointerAttribute set appropriately.

In your case like so:

[UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
public delegate void rdOnAllDoneCallbackDelegate(int parameter);

[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(rdOnAllDoneCallbackDelegate callback);

Usage:

{
    rdOnAllDone(rdOnAllDoneCallback);
}

private static void rdOnAllDoneCallback(int parameter)
{
    Console.WriteLine("rdOnAllDoneCallback invoked, parameter={0}", parameter);
}
查看更多
神经病院院长
3楼-- · 2019-07-22 08:41

Three things you need to do to make this work right:

  • you need to tell the pinvoke marshaller about the actual delegate type, using Delegate isn't good enough. That will create the wrong thunk that won't properly marshal the argument. Which is what you saw happening.
  • you need to tell the marshaller about the calling convention if it isn't __stdcall with the [UnmanagedFunctionPointer] attribute. Getting this wrong imbalances the stack with good odds for a hard crash.
  • you need to store a reference to the delegate object so that the garbage collector won't collect it. It cannot see references held by native code. Getting this wrong makes the native code fail with a hard crash after the next garbage collection.

So this ought to work better, tweak as necessary:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void rdOnAllDoneCallbackDelegate(int parameter);

[DllImport("sb6lib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int rdOnAllDone(rdOnAllDoneCallbackDelegate d);

class Foo {
    private static rdOnAllDoneCallbackDelegate callback;   // Keeps it referenced

    public static void SetupCallback() {
       callback = new rdOnAllDoneCallbackDelegate(rdOnAllDoneCallback);
       rdOnAllDone(callback);
    }

    private static void rdOnAllDoneCallback(int parameter) {
       Console.WriteLine("rdOnAllDoneCallback invoked, parameter={0}", parameter);
    }
}
查看更多
登录 后发表回答