2

I need to pass an array of delegates from C# to C++.
I can pass a single delegate like this:

In C#:

[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Auto)] 
public delegate IntPtr MyDelegate(string inStringParam, out int outIntParam, out string outStringParam);

In C++:

typedef void* (*MyCallback)(w_char_t inStringParam, int* outIntParam, wchar_t* outStringParam);

I’m passing a C# struct with multiple parameters, including this one:

[MarshalAs(UnmanagedType.FunctionPtr)] internal MyDelegate MyHandler;

In C++, I receive the parameters, including this one:

MyCallback* MyHandler;

And reference it, like this:

(MyCallback)initParams->MyHandler;

Works like a charm.

Now, instead of passing just one delegate, I need to pass X delegates. I’ve tried a LOT of things, a couple of man-days worth of things, but no joy. The most straightforward approach seems to be a fixed size, single dimension array. From what I can gather, this should work in my C# struct:

[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.FunctionPtr, SizeConst = 32)] 
internal MyDelegate[] MyHandlers;

I initialize the array when I ‘new up’ the struct:

private NativeParameters _startupParameters = new NativeParameters { MyHandlers = new MyDelegate[32] };

Then, I add my delegate implementation to the first spot in the array:

_startupParameters.MyHandlers[0] = OnMyEvent;

On the C++ side, I change my struct to have a fixed size array:

MyCallback* MyHandlers[32];

And reference the first item like this:

(MyCallback)initParams->MyHandlers[0];

Everything compiles, I do get a pointer when I reference the callback, all of the other values on the struct are still working correctly, but when I invoke the callback, I get:

System.AccessViolationException
  HResult=0x80004003
  Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
  Source=<Cannot evaluate the exception source>
  StackTrace:
<Cannot evaluate the exception stack trace>

It appears to do with .NET Interop (though I can’t prove it). I can successfully pass an array of simpler types like ints or even strings using the same approach:

[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.LPWStr, SizeConst = 32)] 
internal string[] MyStrings;

Those all work. The exception only comes up when I try to pass an array of delegates (technically, pointers to delegates) and try to invoke the callback.

Any ideas are appreciated.

Mike Yeager
  • 131
  • 6
  • 3
    Are you sure it works like a charm? `w_char_t` is not `string`, and `out string` is [not right](https://stackoverflow.com/a/20752021/11683) for `wchar_t*` either. – GSerg Jun 16 '21 at 23:31
  • I would expect something like `[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] public delegate IntPtr MyDelegate(char inStringParam, [Out] out int outIntParam, [Out, MarshalAs(UnmanagedType.LPWStr)] out string outStringParam);` – Charlieface Jun 16 '21 at 23:45
  • Thanks for the reply. There is actually a custom type that equates to wchar_t*, but I replaced it to make the sample less complex and forgot to add the star. Sorry about that! Yes, it does work to pass a pointer to a Unicode string from C# (the only kind of string .NET uses) to a pointer to a Unicode (wide) string in C++ (wchar_t*). – Mike Yeager Jun 17 '21 at 18:00
  • @MikeYeager No, it doesn't work. With `out string`, you cannot pass the initial buffer from C#, and `wchar_t*` would expect a buffer to write to, so an AccessViolationException would then be likely. – GSerg Jun 17 '21 at 18:02
  • Thank you for your reply. When returning strings from C++ to C+, we now specify a return type of IntPtr in the DllImport declaration. We then call Marshal.PtrToStringAuto() to read the string. – Mike Yeager Jun 24 '21 at 16:33
  • Never could get the array of delegates to work. There must be some marshalling magic in .NET for delegates that doesn't get applied when marshalling an array. We worked around it by using a single delegate from C++ to C#, inspecting the values and calling the proper C# to C# delegate to expose the event to the user's C# code. Plenty good enough! Thanks everyone who took a look! – Mike Yeager Jun 24 '21 at 16:35

0 Answers0