How to make make a .NET COM object apartment-threaded?

后端 未结 2 767
渐次进展
渐次进展 2020-12-01 17:06

.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

相关标签:
2条回答
  • 2020-12-01 17:10

    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);
        }
    }
    
    0 讨论(0)
  • 2020-12-01 17:14

    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.

    0 讨论(0)
提交回复
热议问题