11
votes

I'm working in a large multithreaded C# application handling bunches of COM interop. The other developers and I have ample opportunity to accidentally call Single-Threaded Apartment (STA) COM objects from MTA threads, and from STA threads they weren't created on. Performance is sluggish, and cross-thread marshalling is a prime suspect.

Is there a good method to test for cross-apartment marshaling? Even better, is there a defensive programming technique to test that a given COM object belongs to this thread's apartment?

The closest I've come is an assert statement placed defensively around suspicious code:

Debug.Assert(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA);
suspiciousComInterface.SomeMethod();

While this will warn us if our BackgroundWorker threads are calling STA objects, I am specifically worried that STA threads are using COM Runtime Callable Wrapper (RCW) objects that were created in another STA thread.

One online source suggested that this isn't possible (http://www.pcreview.co.uk/forums/detecting-cross-apartment-com-calls-t2450589.html), that the CLR obscures too much of the COM Proxy objects to make them accessible at a high-level.

I can't believe this is the only answer. Thanks!

1
Do you have access to the COM component code? - sharptooth
Nope, the COM components are a lovely little black box. - Scott B
Remove the proxy from the HKCR\Interface registry keys with regedit. That will cause the code to bomb with E_NOINTERFACE when trying to marshal between apartments. - Hans Passant

1 Answers

12
votes

You should be able to accomplish this by testing whether you can get to the IMarshal interface, which should be aggregated into the proxy if the call is a cross-apartment call. First, you will need to declare IMarshal somewhere in your project:

  [System.Runtime.InteropServices.InterfaceTypeAttribute(1)]
  [System.Runtime.InteropServices.Guid("00000003-0000-0000-C000-000000000046")]
  public interface IMarshal
  {
     // no methods needed, just querying for the interface
  }

Then, you can test for the interface like so.

  if (suspiciousComInterface is IMarshal)
     // cross-apartment call
  else
     // direct call