[Mono] [Question] Safety of returning MonoObject* from Native function pointer to C# IntPtr regarding GC collection
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
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)
```csharp
// Definition
private unsafe static delegate* unmanaged 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(ref res);
// Access logic...
if (myObject.Name != "MyClass")
{
Console.WriteLine($"Error: Name mismatch. Expected 'MyClass', got '{myObject.Name}'");
}
```
### 2. Native Side (C++)
```cpp
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
1. **Lifetime of `mono_runtime_invoke` result**: Does `mono_runtime_invoke` internally protect the returned object (e.g., in a temporary handle or thread-local storage) that persists until the native stack frame unwinds?
2. **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?
3. **Best Practice**: Is passing `MonoObject*` as `IntPtr` inherently unsafe in this pattern? Should I always wrap it in `mono_gchandle_new` before 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
***
Contributor guide
Assessment
This issue has not been assessed yet.