Windows: Getting callback on child process exit in C++

In my current capacity I sometimes descend into the virtually forgotten depths of C++ desktop programming. One interesting problem I needed to solve is how to get notified about a process exit. My program can create potentially unlimited number of child processes, and I need to know when they die. In the .NET world I would use Process.Exited event, but it has no equivalent in Win32.

Short answer to this puzzle is: use RegisterWaitForSingleObject() function.

Download sample application (47K zip file)

Here are more details.

When a Win32 process exits, its handle becomes signaled. We could just wait on it using WaitForSingleObject(), but this would block the executing thread. This is not what I need: I need an asynchronous callback.

A naive approach would be to create a dedicated waiting thread and call WaitForSingleObject() there. This works, but it is adequate only when we deal with just a few processes. Creating threads is expensive and slow.

The next optimization would be to wait for multiple processes in a single thread using WaitForMultipleObjects(). This works too, but WaitForMultipleObjects() can wait only on up to 64 handles at a time. If the program creates more than 64 processes, this approach will break.

The next logical step is to create a pool of waiting threads, each of which waits for up to 64 processes. Fortunately, Win32 API already has implementation of such a pool of wait threads, and it can be accessed via RegisterWaitForSingleObject() function. You give at a handle to wait and a callback pointer, and the rest is taken care of by Win32 API.

When you no longer need the callback, you remove it by calling UnregisterWait() or UnregisterWaitEx().

However, as usual in C++, some things should be handled with care. First, the documentation says that if the object you wait on stays signaled forever, you need to specify WT_EXECUTEONLYONCE flag. They are not kidding. Without this flag I received two calls of the callback for each process exit.

Also, one must be careful not to create deadlocks and race conditions. If your wait callback uses some shared resources such as pointers to class instances, etc., you must ensure that they are not destroyed before the callback is called. This is harder than it sounds, because the callback may be called on random thread at any time. But we are in luck: function UnregisterWaitEx has a mode in which it waits for any pending callbacks:

UnregisterWaitEx(hCallback, INVALID_HANDLE_VALUE);

In my view passing INVALID_HANDLE_VALUE is totally confusing; replacing it with some flags like WT_WAITFORCALLBACKS (which does not really exist) would be more elegant. Anyway, confusing or not, it does do the job of ensuring that all callbacks are finished before you clean up the resources.

Sample Application

Sample application ProcessManager.exe creates a number of child processes (100 by default) in suspended state, registers wait callbacks on each one, and then lets them run. You can stop it at any time by pressing ENTER.

I encapsulate child processes in a class creatively called Process that takes care of starting a process, registering a wait callback, and closing all handles in the destructor.

Each child process executes is a very simple application (TestApp.exe) that sleeps for a random number of milliseconds and then exits. Exiting of a child process triggers a callback in the process manager, that prints a message to the standard output. Writing to the standard output is not thread safe, so I use a critical section named coutAccess to make sure the messages are not garbled.

The code line that installs the exit callback is:

RegisterWaitForSingleObject(&hWait, hProcess, OnExited, this, INFINITE, WT_EXECUTEONLYONCE);

The code line that removes the callback is:

::UnregisterWaitEx(hWait, INVALID_HANDLE_VALUE);

Note, that hWait is not a “real” handle: you cannot pass it to CloseHandle() and the like. The only thing you can do with it is UnregisterWait() or UnregisterWaitEx().

That’s all, folks!

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *