user31157
user31157

Reputation:

Prevent delegate from being garbage collected

I'm currently trying to marshal a C# delegate to a C++ function pointer and I looked at the 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();
}

The call to GCHandle::Alloc() is supposed to prevent the garbage collector from collecting the delegate. But my understanding is that the variable GetTheAnswerDelegate^ fp already keeps the delegate alive, because it is a root object and sure enough even when I remove the calls to GCHandle the example still works. Only when I inline the delegate instantiation like this:

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

then I'm seeing a crash.

So is the example from Microsoft wrong or did I miss something?

Upvotes: 4

Views: 4620

Answers (2)

Hans Passant
Hans Passant

Reputation: 941635

You are missing the effect that using a debugger has on the lifetime of local variables. With a debugger attached, the jitter marks the variables in use until the end of the method. Important to make debugging reliable. This however also prevents the GC.Collect() call from collecting the delegate object.

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

An in-depth answer on the affect of Debug build behavior on the garbage collector is available in this post

Upvotes: 7

edtheprogrammerguy
edtheprogrammerguy

Reputation: 6039

The 'Alloc' call adds a reference to the delegate, which prevents the GC from collecting it. You have to keep 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 the call to Alloc. If you don't call Free() on the GCHandle, the program will leak. The memory environment is a bit different when running in the debugger. Make sense?

Upvotes: 2

Related Questions