Prevent delegate collection from garbage

I'm currently trying to combine a C # delegate with a pointer to a C ++ function, and I reviewed an example from Microsoft :

// MarshalDelegate1.cpp // compile with: /clr #include <iostream> using namespace System; using namespace System::Runtime::InteropServices; #pragma unmanaged // Declare an unmanaged function type that takes two int arguments // Note the use of __stdcall for compatibility with managed code typedef int (__stdcall *ANSWERCB)(int, int); int TakesCallback(ANSWERCB fp, int n, int m) { printf_s("[unmanaged] got callback address, calling it...\n"); return fp(n, m); } #pragma managed public delegate int GetTheAnswerDelegate(int, int); int GetNumber(int n, int m) { Console::WriteLine("[managed] callback!"); return n + m; } int main() { GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber); GCHandle gch = GCHandle::Alloc(fp); IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp); ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer()); Console::WriteLine("[managed] sending delegate as callback..."); // force garbage collection cycle to prove // that the delegate doesn't get disposed GC::Collect(); int answer = TakesCallback(cb, 243, 257); // release reference to delegate gch.Free(); } 

A call to GCHandle :: Alloc () should prevent the garbage collector from collecting a delegate. But I understand that the variable GetTheAnswerDelegate ^ fp already keeps the delegate alive because it is the root object and is pretty sure even when I delete the calls in GCHandle, the example works. Only when I embed the delegate instance as follows:

 IntPtr ip = Marshal::GetFunctionPointerForDelegate(gcnew GetTheAnswerDelegate(GetNumber)); 

then I see a crash.

So does the example from Microsoft look like, or did I miss something?

+4
source share
2 answers

You are missing the effect that the debugger uses for the lifetime of local variables. When a debugger is connected, jitter marks the variables used until the end of the method. It is important to do reliable debugging. It also prevents GC.Collect () from being called to collect the delegate object.

This code will crash when you run the Release build of your program without a debugger.

A detailed answer about the impact of Debug assembly behavior on the garbage collector can be found in this post.

+7
source

A call to the delegate is added to the Alloc call, which prevents the GC from being collected. You must save the handle returned from Alloc and call Free () on it when you are done using the function pointer. The delegate will be GC'ed without calling Alloc. If you do not call Free () in GCHandle, the program leaks. When working in a debugger, the memory environment is slightly different. It makes sense?

+1
source

Source: https://habr.com/ru/post/1480720/


All Articles