17

.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 or not, and regardless of their ThreadingModel registry value. I suspect, they aggregate the Free Threaded Marshaler (more details about COM threading could be found here).

I want to make my .NET COM object use the standard COM marshaller proxy when marshaled to another thread. The problem:

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;

namespace ConsoleApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            var apt1 = new WpfApartment();
            var apt2 = new WpfApartment();

            apt1.Invoke(() =>
            {
                var comObj = new ComObject();
                comObj.Test();

                IntPtr pStm;
                NativeMethods.CoMarshalInterThreadInterfaceInStream(NativeMethods.IID_IUnknown, comObj, out pStm);

                apt2.Invoke(() =>
                {
                    object unk;
                    NativeMethods.CoGetInterfaceAndReleaseStream(pStm, NativeMethods.IID_IUnknown, out unk);

                    Console.WriteLine(new { equal = Object.ReferenceEquals(comObj, unk) });

                    var marshaledComObj = (IComObject)unk;
                    marshaledComObj.Test();
                });
            });

            Console.ReadLine();
        }
    }

    // ComObject
    [ComVisible(true)]
    [Guid("00020400-0000-0000-C000-000000000046")] // IID_IDispatch
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface IComObject
    {
        void Test();
    }

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(IComObject))]
    public class ComObject : IComObject
    {
        // IComObject methods
        public void Test()
        {
            Console.WriteLine(new { Environment.CurrentManagedThreadId });
        }
    }


    // WpfApartment - a WPF Dispatcher Thread 
    internal class WpfApartment : IDisposable
    {
        Thread _thread; // the STA thread
        public System.Threading.Tasks.TaskScheduler TaskScheduler { get; private set; }

        public WpfApartment()
        {
            var tcs = new TaskCompletionSource<System.Threading.Tasks.TaskScheduler>();

            // start the STA thread with WPF Dispatcher
            _thread = new Thread(_ =>
            {
                NativeMethods.OleInitialize(IntPtr.Zero);
                try
                {
                    // post a callback to get the TaskScheduler
                    Dispatcher.CurrentDispatcher.InvokeAsync(
                        () => tcs.SetResult(System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext()),
                        DispatcherPriority.ApplicationIdle);

                    // run the WPF Dispatcher message loop
                    Dispatcher.Run();
                }
                finally
                {
                    NativeMethods.OleUninitialize();
                }
            });

            _thread.SetApartmentState(ApartmentState.STA);
            _thread.IsBackground = true;
            _thread.Start();
            this.TaskScheduler = tcs.Task.Result;
        }

        // shutdown the STA thread
        public void Dispose()
        {
            if (_thread != null && _thread.IsAlive)
            {
                InvokeAsync(() => System.Windows.Threading.Dispatcher.ExitAllFrames());
                _thread.Join();
                _thread = null;
            }
        }

        // Task.Factory.StartNew wrappers
        public Task InvokeAsync(Action action)
        {
            return Task.Factory.StartNew(action,
                CancellationToken.None, TaskCreationOptions.None, this.TaskScheduler);
        }

        public void Invoke(Action action)
        {
            InvokeAsync(action).Wait();
        }
    }

    public static class NativeMethods
    {
        public static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
        public static readonly Guid IID_IDispatch = new Guid("00020400-0000-0000-C000-000000000046");

        [DllImport("ole32.dll", PreserveSig = false)]
        public static extern void CoMarshalInterThreadInterfaceInStream(
            [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
            [MarshalAs(UnmanagedType.IUnknown)] object pUnk,
            out IntPtr ppStm);

        [DllImport("ole32.dll", PreserveSig = false)]
        public static extern void CoGetInterfaceAndReleaseStream(
            IntPtr pStm,
            [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
            [MarshalAs(UnmanagedType.IUnknown)] out object ppv);

        [DllImport("ole32.dll", PreserveSig = false)]
        public static extern void OleInitialize(IntPtr pvReserved);

        [DllImport("ole32.dll", PreserveSig = true)]
        public static extern void OleUninitialize();
    }
}

Output:

{ CurrentManagedThreadId = 11 }
{ equal = True }
{ CurrentManagedThreadId = 12 }

Note I use CoMarshalInterThreadInterfaceInStream/CoGetInterfaceAndReleaseStream to marshal ComObject from one STA thread to another. I want both Test() calls to be invoked on the same original thread, e.g. 11, as it would have been the case with a typical STA COM object implemented in C++.

One possible solution is to disable IMarshal interface on the .NET COM object:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IComObject))]
public class ComObject : IComObject, ICustomQueryInterface
{
    // IComObject methods
    public void Test()
    {
        Console.WriteLine(new { Environment.CurrentManagedThreadId });
    }

    public static readonly Guid IID_IMarshal = new Guid("00000003-0000-0000-C000-000000000046");

    public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
    {
        ppv = IntPtr.Zero;
        if (iid == IID_IMarshal)
        {
            return CustomQueryInterfaceResult.Failed;
        }
        return CustomQueryInterfaceResult.NotHandled;
    }
}

Output (as desired):

{ CurrentManagedThreadId = 11 }
{ equal = False }
{ CurrentManagedThreadId = 11 }

This works, but it feels like an implementation-specific hack. Is there a more decent way to get this done, like some special interop attribute I might have overlooked? Note that in real life ComObject is used (and gets marshaled) by a legacy unmanaged application.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    You always have the most interesting threading questions. I don't know the answer to this but I definitely will be watching this question to see what the answer will be. – Scott Chamberlain Feb 20 '15 at 14:33
  • @ScottChamberlain, thanks :) Where COM is not involved, I'm happy with using techniques like `await WpfApartment.InvokeAsync(()=>DoSomething())` for marshaling calls between STA threads. – noseratio Feb 20 '15 at 14:43
  • It is a *very* artificial test, real COM servers of course don't behave this way. They have a proper ThreadModel, the CLR makes an effort to find it in your test program but of course is doomed to fail. Without any shot at finding a proxy, it punts and doesn't marshal the interface at all. There is little point in pursuing this, just don't bother using COM at all if you are not going to use a real COM server. – Hans Passant Feb 20 '15 at 15:22
  • 1
    @HansPassant, the actual server does use `ComRegisterFunctionAttribute` to register the object as `ThreadingModel=Apartment`. Yet, as I've mentioned above, that doesn't change the marshaling behavior I described. The unmanaged client calls `CoMarshalInterThreadInterfaceInStream` on one STA thread, then `CoGetInterfaceAndReleaseStream` on another STA thread, and gets the *same* object, *not a proxy*. That's because any .NET CCW implements `IMarshal` and *is free-threaded*, regardless of its `ThreadingModel` in the registry. Don't take my word for that, try it for yourself. – noseratio Feb 20 '15 at 15:43
  • Out of curiosity: why did you define `IComObject`s GUID as `IDispatch`'s one? – acelent Feb 23 '15 at 01:43
  • 1
    @PauloMadeira, in this particular case, it's a trick which allows me to use OLE Automation `IDispatch`-style marshaling without doing `RegAsm`. COM uses `IDispatch::Invoke`, and .NET has just enough metadata to make those `IDispatch::Invoke` calls hard-typed. – noseratio Feb 23 '15 at 01:52

2 Answers2

7

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.

acelent
  • 7,965
  • 21
  • 39
  • `StandardOleMarshalObject` seems to be exactly what I'm looking for! a great answer, tks. – noseratio Feb 20 '15 at 23:58
  • 1
    I added the quote from the linked page. Actually, your question is great, code and all, I just answered with a reference. – acelent Feb 21 '15 at 10:46
  • Thanks again. You answer has led me to [yet another solution](http://stackoverflow.com/a/28654583/1768303), suitable for when the class cannot be derived from `StandardOleMarshalObject`. – noseratio Feb 22 '15 at 05:19
6

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);
    }
}
Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • You should take a look at [StandardOleMarshalObject](http://referencesource.microsoft.com/#System/compmod/System/Runtime/InteropServices/StandardOleMarshalObject.cs,c00f99eb10426274)'s reference implementation. It implements `IMarshal` and delegates to `CoGetStandardMarshal`, much closer in purpose of what you want, although it seems `CoGetStdMarshalEx` should work, even though it was designed for client-side handlers. – acelent Feb 22 '15 at 12:39
  • @PauloMadeira, I did look at it, it requires all `IMarshal` methods to be explicitly implemented where I can't derive from `StandardOleMarshalObject`. Not quite reusable, IMO. `CoGetStdMarshalEx ` offers `SMEXF_SERVER` specifically for use by COM servers. – noseratio Feb 22 '15 at 22:15
  • Yes, but note that the way you're using it, .NET will `QueryInterface` the returned pointer (an RCW) for some interfaces, possibly `AddRef`ing it and creating an unbreakable cycle. Assuming the standard marshaler doesn't implement any of .NET's queried interfaces **is** an implementation detail. However, you can turn `StandardOleMarshalObject`'s implementation to work on an object other than `this` by passing some other object to `Marshal.GetIUnknownForObject` in `GetStdMarshaler`. Or use `IntPtr` instead of plain interfaces with this answer's approach. – acelent Feb 23 '15 at 00:24
  • @PauloMadeira, nope this is not an issue. `CoGetStdMarshalEx` is slated for COM aggregation. It actually obeys [the aggregation rules](https://msdn.microsoft.com/en-us/library/windows/desktop/ms686558%28v=vs.85%29.aspx), and it doesn't `AddRef` or `Release` the passed `pUnkOuter`. It also correctly delegates `AddRef`/`Release`/`QueryInterface` to `pUnkOuter` on all interfaces other than its own `IUnknown` (including `IMarshal`, too). – noseratio Feb 23 '15 at 00:34
  • You may be right, but the simpler option you gave in your question (failing on `QueryInterface` for `IMarshal`) will make COM do its thing, bypassing a FTM (from `CoCreateFreeThreadedMarshaler` or a .NET custom one). Anyway, having an RCW to an object that points to us... I think .NET uses an interface (`IManagedObject`, I think) to be able to unwrap a RCW to a CCW, but relying on that feels dangerous. For me, an adaptation of `StandardOleMarshalObject` for arbitrary .NET objects would be the way to go, at the not-so-great cost of delegating `IMarshal`'s methods (put it in a `#region`). – acelent Feb 23 '15 at 00:56
  • But remember, I was talking about .NET creating an RCW for and querying **the non-delegated inner IUnknown** for the newly created standard marshaler. – acelent Feb 23 '15 at 01:00
  • @PauloMadeira, *".NET creating an RCW for and querying the non-delegated inner IUnknown for the newly created standard marshaler."* - not sure I follow (not arguing, I really want to understand your point). AFAIU, there's no separate RCW for the aggregated marshaler returned by `CoGetStdMarshalEx`. There's one single CCW for the outer (aggregating) object `ComObject`, which is queried for `IMarshal`. You can then call that `IMarshal`'s `QueryInterface` for `IManagedObject` (or any other iface), and you'll get the same correct outer object's CCW, from either managed or unmanaged code. – noseratio Feb 23 '15 at 01:11
  • In COM aggregation, you usually pass `this`'s `IUnknown` as the controlling aggregator object, and you get the inner aggregatee's `IUnknown`. The latter is the narrow path only the aggregator has to ask the aggregatee for one of its interfaces. Right after this, you get an interface pointer whose `IUnknown` methods delegate to the aggregator. `IUnknown`s are used to make sure you get the right identities. – acelent Feb 23 '15 at 01:20
  • 1
    So, .NET will create an RCW to the aggregatee's inner `IUnknown` and query it for some interfaces (unless you use something more opaque, such as `IntPtr`). I think the standard marshaler doesn't implement any of them, but that surely is an implementation detail. Hence, *you may be right* right now. Your question is really great, in that it answers itself (with rationale) in what is probably the best way to achieve what you want. – acelent Feb 23 '15 at 01:21
  • And actually, now that I think of it, using `CoGetStdMarshalEx`, you'll get an object which is tied to an apartment. If you use an interface pointer allowing .NET to create an RCW, you risk having a cycle (I admit the risk of this is **really** low), but if you don't, you'll have to marshal the pointer yourself (much like FTM objects in C++). Using `CoGetStandardMarshal` each time you're queried for marshaling may work better. Even so, you're not protecting .NET code from invoking your object's methods from other threads. – acelent Feb 23 '15 at 01:31
  • You're probably better off just using WPF's `Threading.Dispatcher`, capturing the current one in the constructor and using `Invoke` or `BeginInvoke` on the object's methods. This works for both COM and .NET, and doesn't require in-process COM marshaling. You can use PostSharp for this, and I think this particular aspect isn't too complicated to implement in the free version. I think there's no need to make a .NET object actually perform work on the MTA, but I guess that's possible too, by checking the current apartment and delegating to the thread-pool if it's STA. – acelent Feb 23 '15 at 01:51
  • @PauloMadeira, right, the goal is indeed to have the `ComObject` object tied to a specific apartment (that allows to safely use other apartment-threaded COM objects inside it). If used from other threads, managed or unmanaged, `CoMarshalInterThreadInterfaceInStream`/`CoGetInterfaceAndReleaseStream` (or Global Interface Table) marshaling is always used, and only `IComObject` methods are called. – noseratio Feb 23 '15 at 01:55
  • @PauloMadeira, the WPF stuff is used here purely for testing, to create STA apartments. The actual `ComObject` is a WPF control, but it's solely used from a legacy unmanaged app (and that app does the COM-style marshaling). – noseratio Feb 23 '15 at 01:58
  • 1
    It seems deriving from `ServicedComponent` [has a lot more implications](https://msdn.microsoft.com/en-us/library/ms973847.aspx) (from 2002). It's almost *just* a side effect that it delegates work to the object's apartment, but it seems to do it even for .NET calls, so if you don't have to derive from some other class or if you can delegate it, it's an option to consider. – acelent Feb 23 '15 at 12:06