A: 

First of all, kudos for posting clear repro code. Now a couple of issues with your code:

  • Most importantly, the cb pointer you're receiving in set_callback (from the C# ref parameter) and storing in cbStruct isn't safe to store. There's no guarantee that the struct it's pointing to will stay around after set_callback returns. If you change your code so a copy of the struct is passed by value instead, I think your errors will go away.

  • All three Marshal.GetFunctionPointerForDelegate calls are passed the first delegate.

  • If you want to be really sure that the delegates remain valid, insert calls to GC.KeepAlive(cb.c_cb1) etc after the call to exec_callbacks.

Mattias S
If possible I would like to not have to change the native code. Is there anything that will translate to a pointer to struct?
Ryan
Yes, if you change the signature of set\_callback to take an IntPtr instead of a ref Callback. Then you can use the GCHandle type to get a pointer to a boxed copy of the struct and pass in that. Free the GCHandle after exec\_callbacks has returned.
Mattias S
Something isn't translating correctly, now none of the callbacks appear to be in the correct location, i.e. with some cosmetic changes, my code shows that both the managed/unmanaged agree as to where the struct is in memory, but seem to disagree on the location of the callbacksSending callbacks:Struct: 291194cb1: 340A1Acb2: 340B62cb3: 340BDAGot callbacks:Struct: 00291194cb1: 02838F34cb2: 02838DD0cb3: 00000000[Native] Executing callback 1 at 02838F34
Ryan
A: 

I struggled with a similar problem a couple of days ago. Although the pointer to the struct remained valid, the function pointers withhin the struct changed after returning from the first callback. My approach was exactly as shown in your sample code. Instead of passing a copy of the struct by val, i decided to pass a pointer and copy the struct it pointed to in set_callback, it works.

__declspec(dllexport) void __stdcall set_callback(Callback *cb) {
    cbStruct = new Callback(*cb);
    // TODO: Clean up memory e.g. in release_callback()
}
Wolf M
A: 

The problem is that cb1, cb2 and cb3 are heap allocated, even though their storage (the struct) is not. Thus they are each subject to GC (compaction/relocation, thus invalidating the pointers originally passed in).

Prior to passing in the struct, each of cb1, cb2 and cb3 should be pinned, ideally immediately after they are new'd. Otherwise they will likely relocate in memory.

Was the decision to use a struct to contruct a classic function map done to avoid this relocation? If so, it's ultimately non-helpful.

Shaun Wilson