C# Marshalling COM objects between threads

2019-04-10 20:37发布

I'm getting very confused about whether C# marshal's COM objects between threads. To this end I have an application which is loading a set of files in a task parallel fashion. I'm using the StaTaskScehduler to load the files using the COM object. Once the COM object is loaded I am storing the object in a central list.

I then later try to perform some processing on this data, again using the STATaskScheduler. However at this point I hit a problem. I receive an exception as follows:

An unhandled exception of type 'System.Runtime.InteropServices.InvalidComObjectException' occurred in MadCat.exe

Additional information: COM object that has been separated from its underlying RCW cannot be used

Now my understanding is that I receive this error because the object hasn't been marshalled into the new thread. I thought this was something C# does for you?

How can I create an apartment threaded COM object in one thread and then use it from another thread?

Am I barking up the wrong tree here? Should I not even be using the Sta apartment for my threads? I can guarantee that the object is never access from multiple threads simultaneously. Any thoughts much appreciated.

Edit: The COM object is defined as follows:

[
    coclass,
    threading( apartment ),
    vi_progid( [Namespace.Class] ),
    progid( [Namespace.Class].6 ),
    version( 6.0 ),
    uuid( GUID_C[Class] ),
    helpstring( [Class]" Class" )
]

So by my understanding this is an apartment threaded object, right? I've just tried using a modified task scheduler that doesn't set the apartment state (MTA by default?). This object then does seem to work when I create it in one thread and use it from another. Is this safe or will it come back to bite me some other way?

COM's threading model has always confused the hell out of me :/

1条回答
成全新的幸福
2楼-- · 2019-04-10 21:26

It appears you're using Stephen Toub's StaTaskScheduler as a part of some "stateful" logic, where your COM objects live across StartNew boundaries. If that's the case, make sure you create and use these objects on the same StaTaskScheduler STA thread and nowhere outside it. Then you wouldn't have to worry about COM marshaling at all. Needless to say, you should create StaTaskScheduler with only one thread, i.e., numberOfThreads:1.

Here's what I mean:

var sta = new StaTaskScheduler(numberOfThreads:1);

var comObjects = new { Obj = (ComObject)null };

Task.Factory.StartNew(() =>
{
    // create COM object
    comObjects.Obj = (ComObject)Activator.CreateInstance(
        Type.GetTypeFromProgID("Client.ProgID"));
}, CancellationToken.None, TaskCreationOptions.None, sta);

//...

for(int i=0; i<10; i++)
{
    var result = await Task.Factory.StartNew(() =>
    {
        // use COM object
        return comObjects.Obj.Method();    
    }, CancellationToken.None, TaskCreationOptions.None, sta);
}

If Obj.Method() returns another COM objects, you should keep the result in the same StaTaskScheduler's "apartment" and access it from there, too:

var comObjects = new { Obj = (ComObject)null, Obj2 = (AnotherComObject)null };
//...
await Task.Factory.StartNew(() =>
{
    // use COM object
    comObjects.Obj2 = comObjects.Obj.Method();    
}, CancellationToken.None, TaskCreationOptions.None, sta);

If you also need to handle events sourced by Obj, check this:

查看更多
登录 后发表回答