Error 'Unable to cast COM object of type' when crossing thread apartments in C#

3

I've been working with a third party SDK as a referenced dll from within C# using .NET 4.5.2 on Windows 10. The IDE creates an Interop around the dll and I can see the appropriate namespaces, interfaces, enum, etc. The SDK I am working with is for the Blackmagic ATEM Television Studio, but I don't think that is directly relevant.

The SDK provides an object I can use to locate/discover the attached hardware and returns an instance of an object, which appears to be a wrapper (RCW) around the underlying COM object.

The discovery code looks like this:

string address = "192.168.1.240";
_BMDSwitcherConnectToFailure failureReason = 0;
IBMDSwitcher switcher = null;
var discovery = new CBMDSwitcherDiscovery();
discovery.ConnectTo(address, out switcher, out failureReason);

If I perform this code on my WPF UI thread, which is an STA apartment, it will work without any problem. I am able to use the object without error. However, if I try to access the resulting switcher object from any new thread (STA or MTA) I will receive an error similar to the following:

Unable to cast COM object of type 'System.__ComObject' to interface type 'BMDSwitcherAPI.IBMDSwitcherMixEffectBlock'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{11974D55-45E0-49D8-AE06-EEF4D5F81DF6}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

Quoting from MikeJ and his answer to a similar question here:

This nasty, nasty exception arises because of a concept known as COM marshalling. The essence of the problem lies in the fact that in order to consume COM objects from any thread, the thread must have access to the type information that describes the COM object.

At this point I decided to adapt my strategy slightly and try calling the discovery logic from within an MTA thread. Like this:

private IBMDSwitcher _switcher = null;
public void Connect()
{
    Task.Run(() =>
    {
        string address = "192.168.1.240";
        IBMDSwitcherDiscovery discovery = new CBMDSwitcherDiscovery();
        _BMDSwitcherConnectToFailure failureReason = 0;
        discovery.ConnectTo(address, out _switcher, out failureReason);
    });
}

This seems to work but at a cost.

The tradeoff is that I can now work with my switcher object from any background (MTA) thread without problem, but my entire application will simply crash after running for approximately 15 minutes. Before someone says it, I know this isn't thread-safe. I recognize the need for some synchronization logic before multiple MTA threads try to use my switcher object. But that's down the road, right now if I run the above code and simply let the application idle for 15 minutes, it will crash, every time.

So on one hand I have a stable app but I can only use the library from the UI thread and on the other hand I have an unstable app but I can use the library from any (MTA) background thread.

Due to the nature of my app, it would be preferable to be able to use the library from multiple background threads. For example, one thread might be generating and uploading graphics to the device, while another thread is managing switching video inputs, and a third thread is managing audio inputs, and the UI thread is handling ID-10T bonk dialogs... All of this could be done from the UI thread, but I'd really rather avoid that if possible.

And so: Is there some way I can discover and create my switcher object on the UI (STA) thread (and theoretically avoid blowing up in 15 minutes) but make the type information accessible to my background (MTA) threads so they can also use the switcher? Or alternately, is it safe to discover/create my switcher object on a background (MTA) thread but I need to do something to 'mark' it in some special way to avoid that 15 minute blow-up?

EDIT:

After reading the comment by Hans Passant and investigating the link he provided, I adapted the referenced STAThread class for my own use in WPF. But I'm a bit fuzzy on SynchronizationContext's and Dispatcher's. Would my code listed below be safe to use as a wrapper 'around' my COM object without blocking the UI during any 'long running' operations? My use case would be to have a Task() running that would prep some data, then Invoke some code to interact with my COM object and when the operation is complete code flow resumes in the running Task to perform the next series of operations...

public class STAThread
{
    private Thread thread;
    private SynchronizationContext ctx;
    private ManualResetEvent mre;

    public STAThread()
    {
        using (mre = new ManualResetEvent(false))
        {
            thread = new Thread(() =>
            {
                ctx = new SynchronizationContext();
                mre.Set();
                Dispatcher.Run(); 
            });
            thread.IsBackground = true;
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
            mre.WaitOne();
        }
    }
    public void BeginInvoke(Delegate dlg, params Object[] args)
    {
        if (ctx == null) throw new ObjectDisposedException("STAThread");
        ctx.Post((_) => dlg.DynamicInvoke(args), null);
    }
    public object Invoke(Delegate dlg, params Object[] args)
    {
        if (ctx == null) throw new ObjectDisposedException("STAThread");
        object result = null;
        ctx.Send((_) => result = dlg.DynamicInvoke(args), null);
        return result;
    }
}
c#
multithreading
com
marshalling
apartments
asked on Stack Overflow Dec 6, 2016 by Geo... • edited Sep 10, 2018 by Flimzy

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0