Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

An outgoing call cannot be made since the application is dispatching an input-synchronous call

I got this(the error in the title above) from the System.Thread.Timer threadpool so then I have my TimerWrapper that wraps the System.Thread.Timer to move the actual execution to System.Thread.ThreadPool and I still get it so I move it a new Thread(callback).Start() and I still get it. How is it dispatching an input-synhcronous call when I put it on a brand new thread???

This is a very very small prototype app in which all I am doing is firing a timer that is doing this...

    IEnumerable swc = SHDocVw.ShellWindows() 
    HashSet<WindowInfo> windows = new HashSet<WindowInfo>();
    foreach (SHDocVw.InternetExplorer ie in swc)
    {
        if (!ie.FullName.ToLower().Contains("iexplore.exe"))
            continue;

        IntPtr hwnd;
        IEPlugin.IOleWindow window = ie.Document as IEPlugin.IOleWindow;
        window.GetWindow(out hwnd);   

        WindowInfo info = new WindowInfo();
        info.handle = hwnd;
        info.extraInfo = ie;
        windows.Add(info);
    }
like image 638
Dean Hiller Avatar asked Jan 12 '12 17:01

Dean Hiller


1 Answers

Congratulations; you've managed to stumble on one of my favourite COM quirks, in this case, a delightfully obscure restriction with IOleWindow's GetWindow method - and an error message that gives you little clue as to what's going on. The underlying problem here is that the GetWindow() method is marked as [input_sync] - from the include\oleidl.idl file in the SDK:

interface IOleWindow : IUnknown
{
...
    [input_sync]
    HRESULT GetWindow
    (
        [out] HWND *phwnd
    );

Unfortunately, the docs for IOleWindow don't mention this attribute, but the docs for some others, such as IOleDocumentView::SetRect() do:

This method is defined with the [input_sync] attribute, which means that the view object cannot yield or make another, non input_sync RPC call while executing this method.

The idea behind this attribute is to guarantee to the caller (which could be an app like Word or some other OLE control host) that it can safely call these methods without having to worry about reentrancy.

Where things get tricky is that COM decides to enforce this: it will reject cross-apartment calls to an [input_sync] method if it thinks it could violate these constraints. So, IIRC, you can't do a cross-apartment [input_sync] call if you are within a SendMessage() - that's the case the error message is somewhat alluding do. And - this is the one that's getting you here - you can't call a cross-apartment [input_sync] method from an MTA thread. Perhaps COM is being a bit over-zealous in its enforcement here, but that's what you have to deal with anyhow.

(Brief comment on MTA vs STA threads: in COM, threads and objects are either STA or MTA. STA, Single-Threaded-Aparment, is the way Windows UI works; a single thread owns the UI and all objects associated with it, and those objects expect to get called by that thread alone. MTA, or Multi-Threaded-Aparment, is more of a free-for-all; objects can expect to be called from any thread at any time, so need to do their own synchronization to be thread-safe. MTA threads are usually used for worker and background tasks. So you may manage UI on a single STA thread, but download a bunch of files in the background on using one or more MTA threads. COM does a bunch of work to allow the two to interop with each other and attempts to hide some of the complexity. Part of the issue here is you're mixing these metaphors: ThreadPools are associated with background work so are MTA, but IOleWindow is UI-centric, so is STA - and GetWindow happens to be the one method that is really really strict about enforcing this.)

Long story short, you can't call this method from a ThreadPool thead because they are MTA threads. Also, new threads are MTA by default, so just creating a new thread to do the work on is insufficent.

Instead, create the new thread, but use tempThread.SetApartmentState(ApartmentState.STA); before starting it, this will give you an STA thread. You may need to actually put all of the code that deals with the shell COM object in that STA thread, not just the single call to GetWindow() - I don't remember the exact details offhand, but if you end up acquiring the original COM object (seems to be the ShellWindows one here) while on the MTA ThreadPool thread, it will stay associated with that MTA even if you attempt to call it from a STA.

If you could instead do all the work from an STA thread rather than a MTA one from the ThreadPool, so much so the better, that would avoid this in the first place. Rather than using System.Threading.Timer, which is designed for background/non-UI code, try using the UI-centric System.Windows.Forms.Timer instead. This does require a message loop - if you've already got windows and forms in your app, you've already got one, but if not, the simplest way to do this in test code is to do a MessageBox() in the same place where your main line code is waiting to exit (usually with a Sleep or Console.ReadLine or similar).

like image 99
BrendanMcK Avatar answered Oct 14 '22 11:10

BrendanMcK