Skip to content

[Mono] [Question] Safety of returning MonoObject* from Native function pointer to C# IntPtr regarding GC collection #122969

@Liangjia0411

Description

@Liangjia0411

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

  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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-VM-meta-monoquestionAnswer questions and provide assistance, not an issue with source code or documentation.untriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions