Note that there are some explanatory texts on larger screens.

plurals
  1. POWhy does marshalling a struct of callback delegates cause an AccessViolationException
    primarykey
    data
    text
    <h1>Introduction</h1> <p>I am trying to use P/Invoke to register a struct of callbacks with a native dll. When calling a function that makes the native dll invoke the callbacks an AccessViolationException occurs. I have constructed a "small" test case that demonstrates the behavior comprised of 2 files, native.cpp that compiles into native.dll and clr.cs that compiles into the executable.</p> <h1>native.cpp</h1> <pre><code> extern "C" { typedef void (*returncb)(int i); typedef struct _Callback { int (*cb1)(); int (*cb2)(const char *str); void (*cb3)(returncb cb, int i); } Callback; static Callback *cbStruct; __declspec(dllexport) void set_callback(Callback *cb) { cbStruct = cb; std::cout &lt&lt "Got callbacks: " &lt&lt std::endl &lt&lt "cb1: " &lt&lt std::hex &lt&lt cb->cb1 &lt&lt std::endl &lt&lt "cb2: " &lt&lt std::hex &lt&lt cb->cb2 &lt&lt std::endl &lt&lt "cb3: " &lt&lt std::hex &lt&lt cb->cb3 &lt&lt std::endl; } void return_callback(int i) { std::cout &lt&lt "[Native] Callback from callback 3 with input: " &lt&lt i &lt&lt std::endl; } __declspec(dllexport) void exec_callbacks() { std::cout &lt&lt "[Native] Executing callback 1 at " &lt&lt std::hex &lt&lt cbStruct->cb1 &lt&lt std::endl; std::cout &lt&lt "[Native] Result: " &lt&lt cbStruct->cb1() &lt&lt std::endl; std::cout &lt&lt "[Native] Executing callback 2 at " &lt&lt std::hex &lt&lt cbStruct->cb2 &lt&lt std::endl; std::cout &lt&lt "[Native] Result: " &lt&lt cbStruct->cb2("2") &lt&lt std::endl; std::cout &lt&lt "[Native] Executing callback 3 with input 3 at " &lt&lt std::hex &lt&lt cbStruct->cb3 &lt&lt std::endl; cbStruct->cb3(return_callback, 3); std::cout &lt&lt "[Native] Executing callback 3 with input 4 at " &lt&lt std::hex &lt&lt cbStruct->cb3 &lt&lt std::endl; cbStruct->cb3(return_callback, 4); } } </code></pre> <h1>clr.cs</h1> <pre><code> using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; namespace clr { public delegate void returncb(Int32 i); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int cb1(); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int cb2(string str); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void cb3(returncb cb, Int32 i); [StructLayout(LayoutKind.Sequential)] struct Callback { [MarshalAs(UnmanagedType.FunctionPtr)] public cb1 c_cb1; [MarshalAs(UnmanagedType.FunctionPtr)] public cb2 c_cb2; [MarshalAs(UnmanagedType.FunctionPtr)] public cb3 c_cb3; } class Program { static int cb1Impl() { Console.WriteLine("[Managed] callback 1"); return 1; } static int cb2Impl(string c) { Console.WriteLine("[Managed] callback 2"); return int.Parse(c); } static void cb3Impl(returncb cb, Int32 i) { Console.WriteLine("[Managed] callback 3"); Console.WriteLine("[Managed] Executing callback to native."); cb(i); } [DllImport("native.dll", CallingConvention = CallingConvention.Cdecl)] static extern void set_callback(ref Callback cb); [DllImport("native.dll", CallingConvention = CallingConvention.Cdecl)] static extern void exec_callbacks(); static void Main(string[] args) { Callback cb; cb.c_cb1 = new cb1(cb1Impl); cb.c_cb2 = new cb2(cb2Impl); cb.c_cb3 = new cb3(cb3Impl); Console.WriteLine("Beginning test."); Console.WriteLine("Sending callbacks: "); Console.WriteLine("cb1: " + Marshal.GetFunctionPointerForDelegate(cb.c_cb1)); Console.WriteLine("cb2: " + Marshal.GetFunctionPointerForDelegate(cb.c_cb1)); Console.WriteLine("cb3: " + Marshal.GetFunctionPointerForDelegate(cb.c_cb1)); set_callback(ref cb); exec_callbacks(); Console.ReadLine(); } } } </code></pre> <h1>Result</h1> <p>Invoking this results in exec_callbacks() throwing an AccessViolationException. cb1 is successfully invoked, but cb2 is not. Furthermore, the native code shows that before cb2 is called, its address has changed. Why does this occur? To the best of my knowledge, none of the delegates should have been gc'ed. As additionally information, marshalling a struct of IntPtr's and using Marshal.GetFunctionPtrForDelegate works correctly (even for cb3 which gets a native ptr to invoke), however, being able to marshal the delegates directly makes more sense/is more readable.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload