if we start child process and want way when it and all the children processes terminate we can use job object. general steps
of course we need create I/O completion port too by CreateIoCompletionPort
and one or more (if only for this task - single thread more than enough) threads which will be call GetQueuedCompletionStatus
on port until end signal.
we can for example use lpCompletionKey as pointer to object with virtual functions, and every object know how handle action event. demo code:
struct __declspec(novtable) PortTask
{
virtual bool OnIoCompletion(OVERLAPPED* lpOverlapped, ULONG NumberOfBytesTransferred) = 0;
};
struct EndTask : public PortTask
{
virtual bool OnIoCompletion(OVERLAPPED* /*lpOverlapped*/, ULONG /*NumberOfBytesTransferred*/)
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
delete this;
return false;
}
};
struct IOPort
{
HANDLE CompletionPort;
LONG dwRefCount;
IOPort() : dwRefCount(1), CompletionPort(0) {
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
~IOPort(){
if (CompletionPort) CloseHandle(CompletionPort);
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
void AddRef(){
InterlockedIncrementNoFence(&dwRefCount);
}
void Release(){
if (!InterlockedDecrement(&dwRefCount)) {
delete this;
}
}
static ULONG WINAPI PortThread(PVOID This)
{
union {
ULONG_PTR CompletionKey;
PortTask* pTask;
};
ULONG NumberOfBytesTransferred;
OVERLAPPED* lpOverlapped;
HANDLE CompletionPort = reinterpret_cast<IOPort*>(This)->CompletionPort;
while (GetQueuedCompletionStatus(CompletionPort, &NumberOfBytesTransferred, &CompletionKey, &lpOverlapped, INFINITE) &&
pTask->OnIoCompletion(lpOverlapped, NumberOfBytesTransferred)) continue;
reinterpret_cast<IOPort*>(This)->Release();
return 0;
}
ULONG Create()
{
if (CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1))
{
AddRef();
if (HANDLE hThread = CreateThread(0, 0, PortThread, this, 0, 0))
{
CloseHandle(hThread);
return NOERROR;
}
Release();
}
return GetLastError();
}
ULONG Stop()
{
if (EndTask* pTask = new EndTask)
{
if (!PostQueuedCompletionStatus(CompletionPort, 0, (ULONG_PTR)pTask, 0))
{
ULONG dwError = GetLastError();
delete pTask;
return dwError;
}
return NOERROR;
}
return ERROR_NO_SYSTEM_RESOURCES;
}
};
struct ActiveProcessZeroTask : public PortTask
{
//HWND hwnd; // in real code you send some message to hwnd instead thread
HANDLE _hJob;
ULONG _dwThreadId;
ActiveProcessZeroTask() : _hJob(0), _dwThreadId(GetCurrentThreadId()) { }
~ActiveProcessZeroTask() {
CloseHandle(_hJob);
PostThreadMessageW(_dwThreadId, WM_QUIT, 0, 0);
}
virtual bool OnIoCompletion(OVERLAPPED* dwProcessId, ULONG MessageId)
{
DbgPrint("%s<%p>(%x %p)\n", __FUNCTION__, this, MessageId, dwProcessId);
switch (MessageId)
{
case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
DbgPrint("%p - ACTIVE_PROCESS_ZERO\n", dwProcessId);
delete this;
break;
case JOB_OBJECT_MSG_NEW_PROCESS:
DbgPrint("%p - NEW_PROCESS\n", dwProcessId);
break;
case JOB_OBJECT_MSG_EXIT_PROCESS:
DbgPrint("%p - EXIT_PROCESS\n", dwProcessId);
break;
case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:
DbgPrint("%p - ABNORMAL_EXIT_PROCESS\n", dwProcessId);
break;
}
return true;
}
ULONG Create(HANDLE CompletionPort, PCWSTR ApplicationName)
{
if (HANDLE hJob = CreateJobObjectW(0, 0))
{
_hJob = hJob;
JOBOBJECT_ASSOCIATE_COMPLETION_PORT jacp = { this, CompletionPort };
if (SetInformationJobObject(hJob, JobObjectAssociateCompletionPortInformation, &jacp, sizeof(jacp)))
{
STARTUPINFO si = { sizeof(si)};
PROCESS_INFORMATION pi;
if (CreateProcessW(ApplicationName, 0, 0, 0, 0, CREATE_SUSPENDED, 0, 0, &si, &pi))
{
ULONG dwError = NOERROR;
if (!AssignProcessToJobObject(hJob, pi.hProcess) ||
!ResumeThread(pi.hThread))
{
dwError = GetLastError();
TerminateProcess(pi.hProcess, 0);
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return dwError;
}
}
}
return GetLastError();
}
};
void demo()
{
if (IOPort* port = new IOPort)
{
if (port->Create() == NOERROR)
{
MessageBoxW(0, 0, L"just for demo #1", MB_ICONINFORMATION);
// exec cmd for demo
WCHAR ApplicationName[MAX_PATH];
if (GetEnvironmentVariableW(L"ComSpec", ApplicationName, RTL_NUMBER_OF(ApplicationName)))
{
if (ActiveProcessZeroTask* pTask = new ActiveProcessZeroTask)
{
if (pTask->Create(port->CompletionPort, ApplicationName) != NOERROR)
{
delete pTask;
}
}
}
// wait all childs exit
MessageBoxW(0, 0, L"Wait for MSG_ACTIVE_PROCESS_ZERO", MB_ICONINFORMATION);
// stop track thread
if (port->Stop() != NOERROR) __debugbreak();
}
port->Release();
}
{
MSG msg;
// remove Wm_QUIT
while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) continue;
MessageBoxW(0, 0, L"just for demo #2", MB_ICONINFORMATION);
}
}