Calling an STA COM Object from a WCF Operation


One of the things that many people are still doing is making use of old COM objects that run in STA (single threaded apartment) threads. Back in October, 2006, Jeff Prosise wrote how to do this from ASMX. Not too long after that, I had a chance to teach for Wintellect and Jeff asked me to show him how to do the same in WCF. That information went up on a post for a consulting company that didn’t make it through the latest recession. For better or worse, that post was referenced a fair number of times and now, folks are writing to me, asking for the code again.

In general, any time you receive a message via WCF, the message itself will be processed on a MTA (multi-threaded apartment) thread. Normally, this is just fine. Sometimes, you might be calling out to a COM object. COM objects will not run if the threading model the COM object needs differs from the threading model of the calling thread. MTA COM objects work just fine. But, if you have a bunch of STA COM objects (typically produced by Visual Basic but sometimes coming from C++ applications or other utilities) that you use in your WCF service, you have a problem. To allow things to work, the method needs to be invoked on an STA thread.

In .NET, one creates an STA thread by setting the apartment state of the thread prior to Start()-ing the thread.

thread.SetApartmentState(ApartmentState.STA);

thread.Start();

Now, how do we make this happen in WCF? We need to wrap the invocation of the actual method! For this, we resort to an IOperationBehavior. One typically applies a behavior (IServiceBehavior, IContractBehavior, IOperationBehavior) via an attribute. The only exception to this is an IEndpointBehavior, which is applied via manipulation of the EndpointDescription or in the app|web.config file. The IOperationBehavior exposes four methods:

  1. AddBindingParameters(OperationDescription, BindingParameterCollection): In our case, we will just do nothing with this info.
  2. ApplyClientBehavior(OperationDescription, ClientOperation): Only called when the contract is used on a client. We will  do nothing here since we only care about the service implementation.
  3. ApplyDispatchBehavior(OperationDescription, DispatchOperation): Only called when the contract is used on the server. We will do our work here and override the IOperationInvoker on the DispatchOperation so that our operation is invoked on an STA thread.
  4. Validate(OperationDescription): Used to make sure that everything is OK before applying the behavior. Normally, one throws an exception from this method if the environment isn’t right in some way. Our implementation will throw if the method we are calling is being executed asynchronously. If you need an async version of the attribute, that work is left as an exercise for you, dear reader.

Our IOperationBehavior then looks like this:

public class STAOperationBehaviorAttribute: Attribute, IOperationBehavior

{

    public void AddBindingParameters(OperationDescription operationDescription,

      System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

    {

    }

 

    public void ApplyClientBehavior(OperationDescription operationDescription,

      System.ServiceModel.Dispatcher.ClientOperation clientOperation)

    {

        // If this is applied on the client, well, it just doesn’t make sense.

        // Don’t throw in case this attribute was applied on the contract

        // instead of the implementation.

    }

 

    public void ApplyDispatchBehavior(OperationDescription operationDescription,

      System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)

    {

        // Change the IOperationInvoker for this operation.

        dispatchOperation.Invoker = new STAOperationInvoker(dispatchOperation.Invoker);

    }

 

    public void Validate(OperationDescription operationDescription)

    {

        if (operationDescription.SyncMethod == null)

        {

            throw new InvalidOperationException("The STAOperationBehaviorAttribute " +

                "only works for synchronous method invocations.");

        }

    }

}

So far, so good. Now, we need to write that IOperationInvoker. You’ll note from the ApplyDispatchBehavior above that our STAOperationInvoker takes an IOperationInvoker in the constructor. This is done because, in general, the IOperationInvoker WCF gives us does everything right. It just needs to do its thing on an STA thread. Our implementation will delegate as much work as possible to the provided IOperationInvoker. This is a pattern you will follow in most WCF extensions as most extensions require a slight tweak to existing behavior. We do just that, except for our implementation of Invoke. In that, we will setup an STA thread and then call the contained IOperationInvoker’s Invoke method from the STA thread.

public class STAOperationInvoker : IOperationInvoker

{

    IOperationInvoker _innerInvoker;

    public STAOperationInvoker(IOperationInvoker invoker){

        _innerInvoker = invoker;

    }

 

    public object[] AllocateInputs()

    {

        return _innerInvoker.AllocateInputs();

    }

 

    public object Invoke(object instance, object[] inputs, out object[] outputs)

    {

        // Create a new, STA thread

        object[] staOutputs = null;

        object retval = null;

        Thread thread = new Thread(

            delegate(){

                retval = _innerInvoker.Invoke(instance, inputs, out staOutputs);

            });

        thread.SetApartmentState(ApartmentState.STA);

        thread.Start();

        thread.Join();

        outputs = staOutputs;

        return retval;

    }

 

    public IAsyncResult InvokeBegin(object instance, object[] inputs,

      AsyncCallback callback, object state)

    {

        // We don’t handle async…

        throw new NotImplementedException();

    }

 

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)

    {

        // We don’t handle async…

        throw new NotImplementedException();

    }

 

    public bool IsSynchronous

    {

        get { return true; }

    }

}

To test, I wrote the following Service Contract:

[ServiceContract]

public interface ITestService

{

    [OperationContract]

    string GetApartmentTypeMTA();

 

    [OperationContract]

    string GetApartmentTypeSTA();

}

and applied the attribute to this implementation:

class TestService : ITestService

{

    public string GetApartmentTypeMTA()

    {

        return Thread.CurrentThread.GetApartmentState().ToString();

    }

 

    [STAOperationBehavior]

    public string GetApartmentTypeSTA()

    {

        return Thread.CurrentThread.GetApartmentState().ToString();

    }

}

When calling the service, GetApartmentTypeMTA always returns MTA and GetApartmentTypeSTA always returns STA.

If you want to save some typing time or see the sample application, go here.

  1. #1 by Jeremias Nuñez on August 31, 2012 - 3:49 pm

    Hey Scott

    Great article. I was wondering if this would work on the new WCF Data Services? I have a Service Operation in which I need to instantiate a STA COM object (specifically, a Canvas object), so I did some googling around and came up with this.

    I haven’t tested it, though. Do you think it will work without any major refactoring?

    Thanks a million.

  2. #2 by Ji on November 20, 2012 - 9:56 pm

    In this way ,are you sure the thread will be kill when it works on an wcf request?
    Looks like this ,if i creat a thread by the sta(thread.SetApartmentState(ApartmentState.STA);) ,when the request is over , but the activex is not be relese ,so ,i can not make it work good in the Multi thread, some times it will say ” Memory can not read or write ”
    By the way you say ,that error is not appear ?(i Recursive call webbrowser )
    activex can not release it by oueselves?
    in the last ,i make the Connection number in wcf is 1 ,i hope the error is will not appear

    • #3 by Scott Seely on January 28, 2013 - 12:10 pm

      the thread “dies” when the processing ends and the code rejoins execution. If we send a response back, that thread is a goner.

  3. #4 by Floran Stuijt on November 22, 2013 - 9:56 am

    Hello Scott,

    Thank you for this great article.

    I tried your solution but unfortunately it does not allow one to access the WebOperationContext.Current property from the operation marked with [STAOperationBehavior] as this property is thread static. I had to resort to the (in my opinion) less elegant spawning of a STA thread from a vanilla (MTA) service operation in order to gain access to this property.

    Any suggestions?

    Kind regards,