-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Description
I have a specific interop scenario where a C# function calls a Native (C++) function via a function pointer. Inside the Native function, it calls back into Managed code using mono_runtime_invoke to retrieve a MonoObject*, and then returns this raw pointer back to the C# caller as an IntPtr.
My question is about the GC safety of the MonoObject* during this transition phase. Specifically, is the object vulnerable to garbage collection after mono_runtime_invoke returns but before the C# caller has successfully converted the returned IntPtr back into a managed reference (e.g., using Unsafe.As)?
I am concerned that there might be a window where the object is considered unreachable by the GC (no strong references on stack or roots) even though the raw pointer is being passed around.
Reproduction Logic
1. Managed Side (Caller)
// Definition
private unsafe static delegate* unmanaged<IntPtr> NativeFuncToCall_GetObject;
// Invocation
// 'res' receives the raw address. At this exact point, does GC see this as a live reference?
var res = NativeFuncToCall_GetObject();
// Convert back to managed reference
var myObject = Unsafe.As<IntPtr, MyClass>(ref res);
// Access logic...
if (myObject.Name != "MyClass")
{
Console.WriteLine($"Error: Name mismatch. Expected 'MyClass', got '{myObject.Name}'");
}2. Native Side (C++)
MonoObject* NativeFuncToCall_GetObject()
{
// 1. Invoke a managed method to get a new object (e.g., "new MyClass()")
MonoMethod* method = mono_class_get_method_from_name(myClass, "GetObject", 0);
// 'obj' is holding the reference. Does mono_runtime_invoke register this result with any handle/root?
MonoObject* obj = mono_runtime_invoke(method, nullptr, nullptr, nullptr);
// 2. Simulate a GC Trigger here or immediately after return
// If 'obj' is not protected, does it get collected here?
// Note: In my test, I force GC here to test robustness.
mono_gc_collect(mono_gc_max_generation());
mono_gc_pending_finalizers();
// 3. Return raw pointer
return obj;
}Specific Questions
- Lifetime of
mono_runtime_invokeresult: Doesmono_runtime_invokeinternally protect the returned object (e.g., in a temporary handle or thread-local storage) that persists until the native stack frame unwinds? - Safety Gap: Is there a "danger zone" between the C++
return obj;and the C#var res = ...assignment where specific GC timings could collect the object? - Best Practice: Is passing
MonoObject*asIntPtrinherently unsafe in this pattern? Should I always wrap it inmono_gchandle_newbefore returning to C#?
I would appreciate insights on how the exact stack scanning mechanism (precise vs conservative) behaves with this return value on the stack.
Environment:
- dotnet runtime: 9.0.11
- Platform: Windows x64