dotnet/runtime · warning

mono/marshal: SafeHandles missing MANAGED_CONV_IN\n

Error message

mono/marshal: SafeHandles missing MANAGED_CONV_IN\n

What it means

In Mono's marshal-ilgen, emit_marshal_safehandle_ilgen handles the SafeHandle marshalling IL emission for P/Invoke stubs. The MARSHAL_ACTION_MANAGED_CONV_IN case (reverse P/Invoke: native calls a managed delegate that takes a SafeHandle) is unimplemented: it only prints this message to stderr and emits no IL. This indicates that SafeHandle handling for native->managed 'in' direction is incomplete in this runtime build.

Source

Thrown at src/mono/mono/component/marshal-ilgen.c:2038

		}
		/* Store the IntPtr results into a local */
		intptr_handle_slot = cb_to_mono->methodBuilder.add_local (mb, int_type);
		cb_to_mono->methodBuilder.emit_stloc (mb, intptr_handle_slot);

		/* 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,

View on GitHub (pinned to 60108ba66e)

Solutions

  1. Avoid SafeHandle parameters on delegates called back from native code; marshal the underlying IntPtr handle and wrap it manually on the managed side.
  2. Use a plain IntPtr or HandleRef for the reverse-P/Invoke boundary, then construct/Release the SafeHandle in managed code.
  3. If you control the native ABI, pass raw handle integers and do lifetime management explicitly.
  4. Upgrade to a .NET runtime (CoreCLR/NativeAOT) that implements the SafeHandle managed-conv path for reverse invocations.

Example fix

// before
public delegate int NativeCallback(SafeFileHandle h);
[DllImport("n")] static extern void register(NativeCallback cb);

// after
public delegate int NativeCallback(IntPtr h);
[DllImport("n")] static extern void register(NativeCallback cb);
// in the callback, wrap h in a SafeFileHandle manually and own its lifetime
Defensive patterns

Strategy: validation

Validate before calling

// Validate at startup that no reverse-P/Invoke delegate carries a SafeHandle param,
// since Mono's MANAGED_CONV_IN for SafeHandle is unimplemented.
static IEnumerable<MethodInfo> CheckReversePInvokes(Assembly asm) {
    foreach (var t in asm.GetTypes())
      foreach (var m in t.GetMethods(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance)) {
        var dlg = m.GetCustomAttribute<UnmanagedFunctionPointerAttribute>();
        if (dlg == null) continue;
        foreach (var p in m.GetParameters())
          if (typeof(SafeHandle).IsAssignableFrom(p.ParameterType))
            yield return m; // will hit the unimplemented Mono path
      }
}

Prevention

When it happens

Trigger: A managed delegate with a SafeHandle parameter is passed to native code and invoked from native (reverse P/Invoke), or a COM/WinRT interop path triggers the MANAGED_CONV_IN action for a SafeHandle-typed parameter. The marshaller reaches this case and emits a no-op instead of conversion IL.

Common situations: Using [DllImport] reverse pinvokes or Marshal.GetDelegateForFunctionPointer with SafeHandle arguments on Mono; COM interop scenarios where a SafeHandle crosses the managed boundary inbound; running code that works on CoreCLR but exercises an unimplemented Mono marshaller path.

Related errors


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