WaitForInputIdle doesn't work for starting mspaint programmatically
Asked Answered
T

1

2

I'm trying to open "mspaint" and find handle of it right after it has been initialized. But FindWindow returns NULL if I call WaitForInputIdle. If I try to use the function Sleep(1000) it works. But I don't think it's a right way to wait for the program to be ready. Is there a solution for this code?

    CString strWindowDirectory;
    GetSystemDirectory(strWindowDirectory.GetBuffer(MAX_PATH), MAX_PATH);
    SHELLEXECUTEINFO sei = { 0 };
    sei.cbSize = sizeof(SHELLEXECUTEINFO);
    sei.fMask =  SEE_MASK_NOCLOSEPROCESS;
    sei.lpVerb = L"open";
    sei.lpFile = L"mspaint";
    sei.lpDirectory = strWindowDirectory;
    sei.nShow = SW_SHOWNORMAL;

    HWND    hPaint = NULL;
    if(ShellExecuteEx(&sei))
    {
        int r = ::WaitForInputIdle(sei.hProcess, INFINITE);
        ATLTRACE(L"WaitForInputIdle %d\n", r);

        if (sei.hProcess == NULL)       return;

        hPaint = ::FindWindow(L"MSPaintApp", NULL); 

        ATLTRACE(L"Handle %d\n", hPaint);
        if (!hPaint) return;
    }
    else
    {
        MessageBox(L"Couldn't find mspaint program");
        return;
    }
Transitive answered 29/10, 2015 at 2:55 Comment(16)
Well you could loop until ::FindWindow doesn't return null and::Sleep(100); on each iteration.Nightjar
I am already aware of that. But I am looking for a better way to solve. Thanks.~Transitive
Sorry, I'm surprised that ::WaitForInputIdle is not working. It's what I've always used without failure.Nightjar
have you tried using the mask values SEE_MASK_NOASYNC and/or SEE_MASK_WAITFORINPUTIDLENightjar
I m really kind of surprised that ::WaitForInputIdle doesn't work if I open "mspaint". But works in case of "notepad" and some other programs. :) thank you very much . Yes I've tried.Transitive
It may be because you need to initialize COM. See Remarks in docs of ShellExecuteEx msdn.microsoft.com/en-us/library/windows/desktop/…Nightjar
Even If I use CreateProcess, it still doesn't work. I'll try to put codes for init.Transitive
WaitForInputIdle does work. It is your program that does not work.Ingham
@Unpopular My comment is addressed at the title of the question.Ingham
@DavidHeffernan Well, I tried to make it more specific.Unpopular
@Great.And.Powerful.Oz: "I'm surprised that ::WaitForInputIdle is not working. It's what I've always used without failure." - I would strongly suggest that you revisit your code then. WaitForInputIdle works, as designed. But that is in the majority of the cases not how you'd expect it to work. Unless you are using it for DDE, you are doing it wrong. And using DDE is wrong, too. Details in the answer.Tact
@Great.And.Powerful.Oz: Are you sure?Tact
@IInspectable, You got me. Well, I guess I must've always used it as intended or at least as documented. But that was back in 32bit days. However, if a process has no point at which "input" is expected, generally a message wait loop or reading from console, then WaitForInputIdle probably won't work as desired.Nightjar
@Great.And.Powerful.Oz: WaitForInputIdle works as documented. It's just hard to read the contract right. It waits for at most one thread for every process, and since you don't control threads the system launches in your process, there's really no way to for you to correlate it's return with a visible UI. If you need to wait for UI being up and running, use the right tool (e.g. WinEvents).Tact
@IInspectable, I think all of the processes I waited on back in the day were single threaded, so it worked as intended. Your point about multithreaded apps is well taken. In that case, if the child process is code owned by the caller, then I think I'd use interprocess signalling before I'd use WinEvents or WaitForInputIdle. But where one doesn't own the code, then, as you suggest, WinEvents or Enumerating Desktop Windows, or some such is probably the way to go.Nightjar
@Great.And.Powerful.Oz: Even for an application that is implemented to strictly run on a single thread, you have no guarantees, that this is the only thread that runs in your process. There are numerous ways for applications to inject code into your process, and the OS will create threads as well. There is no guarantee for any process to be single-threaded (I haven't seen one in decades).Tact
T
9

WaitForInputIdle works, but not the way you assume it does. This is largely, because the documentation is misleading (or at least not as explicit as it should be):

Waits until the specified process has finished processing its initial input and is waiting for user input with no input pending, or until the time-out interval has elapsed.

This is almost criminally inaccurate. While the Remarks section notes, that WaitForInputIdle waits at most once per process, it never mentions significant details. Specifically:

  • WaitForInputIdle returns, as soon as the initial startup has come to a point, where any thread in the process is ready to process messages. Those messages need not be user input.
  • WaitForInputIdle was invented to allow a process to communicate with a child process using a message-based protocol. The specific scenario addressed was DDE, which no one1) uses anymore.

WaitForInputIdle cannot be used as a reliable solution to your problem: Waiting for a child process' UI to show up. You really need to wait for the UI show up.

The system offers two solutions you can use:

  1. A global CBT hook, and wait for the HCBT_CREATEWND callback. You can inspect the CREATESTRUCT's lpszClass and/or lpszName members to filter out the window you are interested in.
  2. Use WinEvents and respond to the EVENT_OBJECT_CREATE event.

The global CBT hook is called, whenever a window is about to be created. The kernel structures that the HWND references have been fully populated, but the client calling CreateWindow[Ex] may still terminate window creation. In contrast, the WinEvent is issued, after the window has been fully constructed, and is ready for interaction.

In general, a solution based on a CBT hook is used, when an application needs to update certain aspects of a window before the caller of CreateWindowEx gets to see the HWND for the first time. WinEvents, instead, are usually the tool of choice when implementing accessibility or UI automation solutions.


Additional resources:


1) Yes, I know, some applications might still use DDE. But if Raymond Chen suggested in 2007, that we should feel free to stop using DDE, I'll just pass that on as authoritative guidance.
Tact answered 29/10, 2015 at 9:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.