Esta es una técnica que, aunque no es infalible, puede ser útil si por algún motivo no puede usar un objeto de trabajo. La idea es crear un conducto anónimo y dejar que el proceso secundario herede el controlador hasta el final de escritura del conducto.
Normalmente, los procesos de nieto también heredarán el final de escritura de la tubería. En particular, los procesos iniciados por cmd.exe
(por ejemplo, desde un archivo por lotes) heredarán identificadores.
Una vez que el proceso secundario ha salido, el proceso padre cierra su manejador hasta el final de escritura de la tubería, y luego intenta leer desde la tubería. Como nadie está escribiendo en la tubería, la operación de lectura se bloqueará indefinidamente. (Por supuesto, puede usar subprocesos o E/S asíncronas si desea seguir haciendo cosas mientras espera a los nietos.)
Cuando (y solo cuando) se cierra el último identificador del extremo de escritura de la tubería, el el final de escritura de la tubería se destruye automáticamente. Esto rompe el conducto y la operación de lectura finaliza e informa un error ERROR_BROKEN_PIPE.
He estado usando este código (y versiones anteriores del mismo código) en producción durante varios años.
// 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;
}
Gracias. Parece que un simple WaitForSingleObject no me dirá cuándo se han cerrado todos los procesos, pero parece que puedo crear un puerto de finalización de IO y esperar a JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO. – thudbang
Raymond Chen lo cubre en su blog: [¿Cómo espero hasta que se hayan cerrado todos los procesos en un trabajo?] (Https://blogs.msdn.microsoft.com/oldnewthing/20130405-00/?p=4743) –