I have a TestNet
wrapper for a native component. The native component exposes a blocking TestNative::Foo()
that communicates with managed part through calling managed callbacks and a weak GCHandle
that is used to retrieve the reference to the .NET wrapper and provides a context. The GCHandle
is weak since the .NET wrapper is meant to hide the fact that is handling unmanaged resources to user and deliberately doesn't implement the IDisposable
interface: being non weak it would prevent TestNet
instances from being collected at all, creating a memory leak. What's happening is that in Release build only the garbage collector will collect reference to .NET wrapper while executing the managed callback, even before both TestNative::Foo()
and surprisingly TestNet::Foo()
unblocks. I understood the problem my self and I can fix it by issuing a GC.KeepAlive(this)
after the P/Invoke call but since the knowledge of this is not very widespread, it seems a lot of people are doing it wrong. I have few questions:
- Is
GC.KeepAlive(this)
always needed in a managed method if last instruction is a P/Invoke call on unmanaged resources or it's just needed in this special case, namely the switch to managed execution context while marshaling the managed callback from native code? The question could be: should I putGC.KeepAlive(this)
everywhere? This old microsoft blog (original link is 404, here is cached) seems to suggest so! But this would be game changer and basically it would mean that most people never did P/Invoke correctly, because this would require reviewing most P/Invoke calls in wrappers. Is there for example a rule that say that garbage collector (EDIT: or better the finalizer) can't run for objects that belong to the current thread while execution context is unamanaged (native)? - Where I can find proper documentation? I could find CodeAnalysis policy CA2115 pointing to generically use
GC.KeepAlive(this)
any time a unmanaged resource is accessed with P/Invoke. In generalGC.KeepAlive(this)
seems to be very rarely needed when dealing with finalizers. - Why is this happening only in Release build? It looks like an optimization but not being needed at all in Debug build hides an important behavior of the garbage collector.
NOTE: I have no problem with delegates being collected, that is a different issue which I know how to handle properly. The issue here is with objects holding unmanaged resources being collected when P/Invoke calls are not finished yet.
It follows code that clearly manifest the problem. Creates a C# console application and a C++ Dll1 project and build them in Release mode:
Program.cs:
using System;
using System.Runtime.InteropServices;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var test = new TestNet();
try
{
test.Foo();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
class TestNet
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void Callback(IntPtr data);
static Callback _callback;
IntPtr _nativeHandle;
GCHandle _thisHandle;
static TestNet()
{
// NOTE: Keep delegates references so they can be
// stored persistently in unmanaged resources
_callback = callback;
}
public TestNet()
{
_nativeHandle = CreateTestNative();
// Keep a weak handle to self. Weak is necessary
// to not prevent garbage collection of TestNet instances
_thisHandle = GCHandle.Alloc(this, GCHandleType.Weak);
TestNativeSetCallback(_nativeHandle, _callback, GCHandle.ToIntPtr(_thisHandle));
}
~TestNet()
{
Console.WriteLine("this.~TestNet()");
FreeTestNative(_nativeHandle);
_thisHandle.Free();
}
public void Foo()
{
Console.WriteLine("this.Foo() begins");
TestNativeFoo(_nativeHandle);
// This is never printed when the object is collected!
Console.WriteLine("this.Foo() ends");
// Without the following GC.KeepAlive(this) call
// in Release build the program will consistently collect
// the object in callback() and crash on next iteration
//GC.KeepAlive(this);
}
static void callback(IntPtr data)
{
Console.WriteLine("TestNet.callback() begins");
// Retrieve the weak reference to self. As soon as the istance
// of TestNet exists.
var self = (TestNet)GCHandle.FromIntPtr(data).Target;
self.callback();
// Enforce garbage collection. On release build
self = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("TestNet.callback() ends");
}
void callback()
{
Console.WriteLine("this.callback()");
}
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr CreateTestNative();
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern void FreeTestNative(IntPtr obj);
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern void TestNativeSetCallback(IntPtr obj, Callback callback, IntPtr data);
[DllImport("Dll1", CallingConvention = CallingConvention.Cdecl)]
static extern void TestNativeFoo(IntPtr obj);
}
}
Dll1.cpp:
#include <iostream>
extern "C" typedef void (*Callback)(void *data);
class TestNative
{
public:
void SetCallback(Callback callback1, void *data);
void Foo();
private:
Callback m_callback;
void *m_data;
};
void TestNative::SetCallback(Callback callback, void * data)
{
m_callback = callback;
m_data = data;
}
void TestNative::Foo()
{
// Foo() will never end
while (true)
{
m_callback(m_data);
}
}
extern "C"
{
__declspec(dllexport) TestNative * CreateTestNative()
{
return new TestNative();
}
__declspec(dllexport) void FreeTestNative(TestNative *obj)
{
delete obj;
}
__declspec(dllexport) void TestNativeSetCallback(TestNative *obj, Callback callback1, void * data)
{
obj->SetCallback(callback1, data);
}
__declspec(dllexport) void TestNativeFoo(TestNative *obj)
{
obj->Foo();
}
}
The output is consistently:
this.Foo() begins
TestNet.callback() begins
this.callback()
this.~TestNet()
TestNet.callback() ends
TestNet.callback() begins
System.NullReferenceException: Object reference not set to an instance of an object.
If one uncomment the GC.KeepAlive(this)
call in TestNet.Foo()
the program correctly never ends.
_purecall
followed by_abort
, which actually meant that the wrapper holding unmanaged resources was finalized externally. – DairymaidIDisposable
pattern because I don't hold file handles, sockets or other resources for which it's recommended to useIDisposable
: it's just a C++ native API and C# users shouldn't be forced to rely onusing
. Anyway: I just realized thatGC.KeepAlive()
API documents exactly my use case with callbacks, basically answering my second question. Your answer perfectly covers question 3). Not yet answered question is wheneverGC.KeepAlive(this)
is always needed or not – DairymaidGCHandle
anymore. I didn't know it could also work this way. It doesn't seems to prevent possible garbage collection of the managed instance during the blocking native call, though (that's why you still suggested meIDisposable
, I guess). – DairymaidIDisposable
is generically for unmanaged resources of any kind. In my case these unmanaged resources are just C++ objects for which deletion can happen non-deterministically, and don't have any resource that works with open/close logic (for example registry handles, as you say, that would be unsafe to close non deterministically in the general case).IDisposable
could be added anyway, but it would be hard to maintain in a way that API users can arbitrarily dispose objects without corrupting others. I tried that way, but people were just misusing the pattern. – DairymaidIDisposable
pattern is really not the solution to early collection of objects as observed in the provided example. – DairymaidSafeHandle
part, which I discovered in last hour, you could have pointed it out ;) . But really:thank you for suggestions, It's just that I am coming to conclusions. I found very interesting theHandleRef
, which is less used nowadays but it's not deprecated and present in .NET standard 2.0 as well. They are both techniques to solve managed objects lifecycle when doing Interop. – Dairymaid