Search code examples
c#c++.netpinvokeunmanaged

C# delegate callback with params cause AccessViolation when calling from C++ DLL


I have an unmanaged C++ DLL and a .net application which communicate using P/Invoke . I need to update a dictionary within the .net app from a C++ DLL.

I tried to do that in the following way:

public delegate void MyDelegate(string address, string username);

[DllImport("MyDLL.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void SaveMyDelegate(MyDelegate callback);

private static SortedDictionary<string, string> dict = new SortedDictionary<string, string>();

public void save_delegate() {

    SaveMyDelegate(delegate(string address, string username) {
        if (username != "") {
            dict.Add(address, username);
        }
     });
 }

On C++ side I have:

typedef void (*MANAGED_CALLBACK)(string user_address, string username);

    extern "C" __declspec(dllexport) void SaveMyDelegate(MANAGED_CALLBACK callback);

    MANAGED_CALLBACK my_callback;
    void SaveMyDelegate(MANAGED_CALLBACK callback) {
        my_callback = callback;
    }

extern "C" __declspec(dllexport) VOID start_collection();
VOID start_collection() {
    my_callback("test", "User");
}

On C# I do the following calls:

[DllImport("MyDLL.dll")]
public static extern void start_collection();

private void Button1_Click(object sender, EventArgs e) {
    save_delegate();

    start_collection();
}

When I call start_collection() I get an AccessViolation exception. I tried to declare the delegate in the following way:

public delegate void MyDelegate(
[MarshalAs(UnmanagedType.LPStr)]string address, 
[MarshalAs(UnmanagedType.LPStr)]string username);

I have also added this statement:

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void MyDelegate(...)

Solution

  • I've not looked at every detail of your code, but some commonly seen p/invoke issues just out immediately:

    1. You cannot use C++ strings as interop types. You should use null terminated character arrays on the C++ side. Obtain these using the c_str() method of std::string.
    2. The delegate likely has the wrong calling convention. The unmanaged code (probably) uses cdecl and so you need to use the [UnmanagedFunctionPointer(CallingConvention.Cdecl)] attribute when declaring the delegate type in C#.
    3. Your C# delegate is susceptible to being collected early (while your unmanaged code still holds a reference to it) because there are no managed references to it. Typically that would be solved by holding a reference to the delegate in a static variable in your C# code.