问题
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;
}
回答1:
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:
- 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. - 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:
- WaitForInputIdle should really be called WaitForProcessStartupComplete
- WaitForInputIdle waits for any thread, which might not be the thread you care about
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.
来源:https://stackoverflow.com/questions/33405201/waitforinputidle-doesnt-work-for-starting-mspaint-programmatically