В ожидании процессов внука в окнах
Можно ли ждать всех процессов, запущенных дочерним процессом в Windows? Я не могу изменить процессы ребенка или внука.
В частности, вот что я хочу сделать. Мой процесс запускает uninstallA.exe. Процесс uninistallA.exe запускает uninstallB.exe и сразу же завершается, а uninstallB.exe запускается некоторое время. Я хотел бы дождаться завершения удаления uninstallB.exe, чтобы я мог знать, когда удаление будет завершено.
5 ответов
Создать объект задания с CreateJobObject
, использование CreateProcess
начать UninstallA.exe
в подвешенном состоянии. Назначьте этот новый процесс для вашего объекта работы с AssignProcessToJobObject
, Запустите UninstallA.exe, выполнив вызов ResumeThread
на ручке нити, с которой вы вернулись CreateProcess
,
Затем сложная часть: дождаться, пока объект задания завершит свое выполнение. К сожалению, это немного сложнее, чем можно было бы ожидать. Основная идея состоит в том, что вы создаете порт завершения ввода-вывода, затем создаете объектный объект, связываете его с портом завершения ввода-вывода и, наконец, ждете порт завершения ввода-вывода (получая его состояние с помощью GetQueuedCompletionStatus
). У Раймонда Чена есть демонстрация (и объяснение того, как это произошло) в его блоге.
Вот методика, которая, хотя и не является непогрешимой, может быть полезна, если по какой-то причине вы не можете использовать объект задания. Идея состоит в том, чтобы создать анонимный канал и позволить дочернему процессу наследовать дескриптор конца записи канала.
Как правило, процессы внука также наследуют конец записи канала. В частности, процессы, запущенные cmd.exe
(например, из командного файла) будет наследовать дескрипторы.
Как только дочерний процесс вышел, родительский процесс закрывает свой дескриптор конца записи канала и затем пытается прочитать из канала. Поскольку никто не пишет в канал, операция чтения будет блокироваться бесконечно. (Конечно, вы можете использовать потоки или асинхронный ввод-вывод, если хотите продолжать делать что-то, ожидая внуков.)
Когда (и только когда) последний дескриптор конца записи канала закрыт, конец записи канала автоматически уничтожается. Это разрывает канал, и операция чтения завершается и сообщает об ошибке ERROR_BROKEN_PIPE.
Я использую этот код (и более ранние версии того же самого кода) в производстве в течение ряда лет.
// pwatch.c
//
// Written in 2011 by Harry Johnston, University of Waikato, New Zealand.
// This code has been placed in the public domain. It may be freely
// used, modified, and distributed. However it is provided with no
// warranty, either express or implied.
//
// Launches a process with an inherited pipe handle,
// and doesn't exit until (a) the process has exited
// and (b) all instances of the pipe handle have been closed.
//
// This effectively waits for any child processes to exit,
// PROVIDED the child processes were created with handle
// inheritance enabled. This is usually but not always
// true.
//
// In particular if you launch a command shell (cmd.exe)
// any commands launched from that command shell will be
// waited on.
#include <windows.h>
#include <stdio.h>
void error(const wchar_t * message, DWORD err) {
wchar_t msg[512];
swprintf_s(msg, sizeof(msg)/sizeof(*msg), message, err);
printf("pwatch: %ws\n", msg);
MessageBox(NULL, msg, L"Error in pwatch utility", MB_OK | MB_ICONEXCLAMATION | MB_SYSTEMMODAL);
ExitProcess(err);
}
int main(int argc, char ** argv) {
LPWSTR lpCmdLine = GetCommandLine();
wchar_t ch;
DWORD dw, returncode;
HANDLE piperead, pipewrite;
STARTUPINFO si;
PROCESS_INFORMATION pi;
SECURITY_ATTRIBUTES sa;
char buffer[1];
while (ch = *(lpCmdLine++)) {
if (ch == '"') while (ch = *(lpCmdLine++)) if (ch == '"') break;
if (ch == ' ') break;
}
while (*lpCmdLine == ' ') lpCmdLine++;
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (!CreatePipe(&piperead, &pipewrite, &sa, 1)) error(L"Unable to create pipes: %u", GetLastError());
GetStartupInfo(&si);
if (!CreateProcess(NULL, lpCmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
error(L"Error %u creating process.", GetLastError());
if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED) error(L"Error %u waiting for process.", GetLastError());
if (!GetExitCodeProcess(pi.hProcess, &returncode)) error(L"Error %u getting exit code.", GetLastError());
CloseHandle(pipewrite);
if (ReadFile(piperead, buffer, 1, &dw, NULL)) {
error(L"Unexpected data received from pipe; bug in application being watched?", ERROR_INVALID_HANDLE);
}
dw = GetLastError();
if (dw != ERROR_BROKEN_PIPE) error(L"Unexpected error %u reading from pipe.", dw);
return returncode;
}
Не существует общего способа ждать всех внуков, но в вашем конкретном случае вы можете взломать что-то вместе. Вы знаете, что ищете конкретный экземпляр процесса. Я бы сначала дождался выхода uninstallA.exe (используя WaitForSingleObject), потому что в этот момент вы знаете, что uninstallB.exe запущен. Затем используйте EnumProcesses и GetProcessImageFileName из PSAPI, чтобы найти работающий экземпляр uninstallB.exe. Если вы не нашли его, вы знаете, что он уже закончен, иначе вы можете подождать.
Дополнительным осложнением является то, что если вам требуется поддержка версий Windows, более старых, чем XP, вы не можете использовать GetProcessImageFileName, а для Windows NT вы вообще не можете использовать PSAPI. Для Windows 2000 вы можете использовать GetModuleFileNameEx, но у него есть некоторые предостережения, которые означают, что иногда он может потерпеть неудачу (см. Документацию). Если вам нужно поддерживать NT, тогда ищите Toolhelp32.
Да, это супер уродливо.
Одна из возможностей - установить Cygwin, а затем использовать команду ps, чтобы увидеть выход внука.