.NET objects are free-threaded by default. If marshaled to another thread via COM, they always get marshaled to themselves, regardless of whether the creator thread was STA
Paulo Madeira's excellent answer provides a great solution for when the managed class being exposed to COM can be derived from StandardOleMarshalObject
.
It got me thinking though, how to deal with the cases when there is already a base class, like say System.Windows.Forms.Control
, which doesn't have StandardOleMarshalObject
in its inheritance chain?
It turns out, it's possible to aggregate the Standard COM Marshaler. Similar to the Free Threaded Marshaler's CoCreateFreeThreadedMarshaler
, there is an API for that: CoGetStdMarshalEx. Here's how it can be done:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IComObject))]
public class ComObject : IComObject, ICustomQueryInterface
{
IntPtr _unkMarshal;
public ComObject()
{
NativeMethods.CoGetStdMarshalEx(this, NativeMethods.SMEXF_SERVER, out _unkMarshal);
}
~ComObject()
{
if (_unkMarshal != IntPtr.Zero)
{
Marshal.Release(_unkMarshal);
_unkMarshal = IntPtr.Zero;
}
}
// IComObject methods
public void Test()
{
Console.WriteLine(new { Environment.CurrentManagedThreadId });
}
// ICustomQueryInterface
public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
{
ppv = IntPtr.Zero;
if (iid == NativeMethods.IID_IMarshal)
{
if (Marshal.QueryInterface(_unkMarshal, ref NativeMethods.IID_IMarshal, out ppv) != 0)
return CustomQueryInterfaceResult.Failed;
return CustomQueryInterfaceResult.Handled;
}
return CustomQueryInterfaceResult.NotHandled;
}
static class NativeMethods
{
public static Guid IID_IMarshal = new Guid("00000003-0000-0000-C000-000000000046");
public const UInt32 SMEXF_SERVER = 1;
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoGetStdMarshalEx(
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
UInt32 smexflags,
out IntPtr ppUnkInner);
}
}
You can inherit from StandardOleMarshalObject or ServicedComponent for that effect:
Managed objects that are exposed to COM behave as if they had aggregated the free-threaded marshaler. In other words, they can be called from any COM apartment in a free-threaded manner. The only managed objects that do not exhibit this free-threaded behavior are those objects that derive from ServicedComponent or StandardOleMarshalObject.