1
votes

I have a Visual Studio add-in that uses the System.Timers.Timer myTimer.
Every N seconds myTimer fires and executes this code:

foreach(Window window in DTE2.Windows)
{
    TextDocument td = window.Document.Object("TextDocument") as TextDocument;
    // do stuff with td...  
}

Because this gets called from another thread I sometimes get one of these errors:

  • QI for IEnumVARIANT failed on the unmanaged server.
    at EnvDTE.Windows.GetEnumerator()
    on line foreach(Window window in DTE2.Windows)

  • The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))
    at EnvDTE.Window.get_Document()
    on line TextDocument td = window.Document.Object("TextDocument") as TextDocument;

What's the proper way to access this enumerator in another thread since COM objects are involved?
Some kind of COM thread marshalling?
Something else?

1
Are you saying that the code snippet gets called from a different thread each time the timer fires?Szymon Rozga
yes. it gets called on the System.Timers.Timer background thread as it should.Mladen Prajdic

1 Answers

2
votes

You are running afoul of COM trying to protect an object model that isn't thread-safe. Such complicated object models, like the Visual Studio automation interface never are. COM tries to do so by automatically marshaling a call made on a background thread to the STA thread. This is done through a proxy, a replica of the original COM interface that has all the same methods but marshal any calls made on them to an interface that runs the methods on the STA thread.

This proxy has thread affinity, it can only be used on the thread that created it. DTE2 is your problem here. If any extensibility code ran before and created the DTE2 interface instance then DTE2 will be the 'real' interface pointer, not a proxy. If you then use it on a worker thread, like the one that Timer creates for its Elapsed event then you get the bomb. It works the other way around too, if DTE2 is first created by your code then you'll bomb any extensibility code that runs the normal way.

Maybe DTE2.DTE will fix your problem, not sure. Ultimately it doesn't fix anything, the code is always going to run on the Visual Studio STA thread anyway. Just don't use a System.Timer.Timer, use a synchronous timer like System.Windows.Forms.Timer