dotnet/runtime · warning
mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n
Error message
mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n
What it means
Companion to the MANAGED_CONV_IN case in emit_marshal_safehandle_ilgen: the MARSHAL_ACTION_MANAGED_CONV_OUT action (copying a SafeHandle's state back out after a reverse P/Invoke call returns) is unimplemented. The marshaller prints this to stderr and emits no IL, so outbound state propagation for the SafeHandle is silently skipped.
Source
Thrown at src/mono/mono/component/marshal-ilgen.c:2042
/* Create return value */
cb_to_mono->methodBuilder.emit_op (mb, CEE_NEWOBJ, ctor);
cb_to_mono->methodBuilder.emit_stloc (mb, 3);
/* Set the return.handle to the value, am using ldflda, not sure if thats a good idea */
cb_to_mono->methodBuilder.emit_ldloc (mb, 3);
cb_to_mono->methodBuilder.emit_ldflda (mb, MONO_STRUCT_OFFSET (MonoSafeHandle, handle));
cb_to_mono->methodBuilder.emit_ldloc (mb, intptr_handle_slot);
cb_to_mono->methodBuilder.emit_byte (mb, CEE_STIND_I);
break;
}
case MARSHAL_ACTION_MANAGED_CONV_IN:
fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_IN\n");
break;
case MARSHAL_ACTION_MANAGED_CONV_OUT:
fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_OUT\n");
break;
case MARSHAL_ACTION_MANAGED_CONV_RESULT:
fprintf (stderr, "mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n");
break;
default:
printf ("Unhandled case for MarshalAction: %d\n", action);
}
return conv_arg;
}
static int
emit_marshal_handleref_ilgen (EmitMarshalContext *m, int argnum, MonoType *t,
MonoMarshalSpec *spec, int conv_arg,
MonoType **conv_arg_type, MarshalAction action)
{
MonoMethodBuilder *mb = m->mb;
View on GitHub (pinned to 60108ba66e)
Solutions
- Do not pass SafeHandle by ref/out across the native->managed boundary on Mono; use IntPtr and re-wrap.
- Perform handle write-back manually via a second callback or a shared native slot.
- Move the SafeHandle ownership fully to one side of the boundary to avoid bidirectional marshalling.
- Use a runtime that implements the MANAGED_CONV_OUT path for SafeHandle (CoreCLR/NativeAOT).
Defensive patterns
Strategy: validation
Validate before calling
// Flag ref/out SafeHandle on reverse-P/Invoke delegates (Mono MANAGED_CONV_OUT unsupported).
static IEnumerable<ParameterInfo> CheckByRefSafeHandles(Assembly asm) {
foreach (var t in asm.GetTypes())
foreach (var m in t.GetMethods(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance)) {
if (m.GetCustomAttribute<UnmanagedFunctionPointerAttribute>() == null) continue;
foreach (var p in m.GetParameters())
if ((p.ParameterType.IsByRef) &&
typeof(SafeHandle).IsAssignableFrom(p.ParameterType.GetElementType()))
yield return p;
}
} Prevention
- Avoid ref/out SafeHandle across the native->managed boundary on Mono.
- Marshal a ref IntPtr and reconstruct the SafeHandle manually in managed code.
- Keep SafeHandle ownership on one side of the boundary.
- Build-time scan interop signatures for by-ref SafeHandles.
When it happens
Trigger: A reverse P/Invoke (native->managed) call has a SafeHandle passed by-reference or with out semantics, so the marshaller must write the handle back after the managed call; reaching this case means that write-back IL was not emitted.
Common situations: Mono reverse P/Invoke with a 'ref SafeHandle' or 'out SafeHandle' parameter; COM interop where the managed method mutates a SafeHandle that must flow back to native; code ported from CoreCLR that relies on full SafeHandle two-way marshalling.
Related errors
- mono/marshal: SafeHandles missing MANAGED_CONV_IN\n
- mono/marshal: SafeHandles missing MANAGED_CONV_RESULT\n
- Unhandled case for MarshalAction: %d\n
- Locale or culture name is null or empty. localeName=${locale
- ${part} not found while looking up ${function_name}
AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10).
Data as JSON: /api/errors/fdd90096ace45863.
Report an issue: GitHub.