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

  1. Do not pass SafeHandle by ref/out across the native->managed boundary on Mono; use IntPtr and re-wrap.
  2. Perform handle write-back manually via a second callback or a shared native slot.
  3. Move the SafeHandle ownership fully to one side of the boundary to avoid bidirectional marshalling.
  4. 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

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


AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10). Data as JSON: /api/errors/fdd90096ace45863. Report an issue: GitHub.