How to make make a .NET COM object apartment-threaded?
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.
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); }}