Reputation: 28872
I have an MFC application that launches another process using CreateProcess(...)
. I would like to perform a UI update when the created process terminates. Normally, I would use WaitForSingleObject
or WaitForMutlipleObject
on the returned process HANDLE
but this will block the GUI thread (bad).
The only solution I can think of is to spawn a new thread that can wait on the handle and post a message when the process terminates. This is not ideal.
So is it possible to register the handle with the Windows Manager and receive a Windows message when the process terminates?
Upvotes: 2
Views: 2995
Reputation: 27756
You can use RegisterWaitForSingleObject()
to get notified via callback, when the process has ended. The RegisterWaitForSingleObject
function directs a wait thread in the thread pool to wait on the process, so this should be optimal usage of resources. As Raymond Chen commented:
The thread pool can batch multiple Wait requests into a single call to WaitForMultipleObjects so the amortized cost is 1/63 of a thread.
A minimal example of a Win32 GUI application follows. The code creates a window, then it creates another instance of itself as a child process, which is indicated by the "/child" parameter. It registers the wait callback function and runs a regular message loop. You can resize and move the window to see that the GUI is not blocked. When the child process has ended, the system asynchronously calls the wait callback which posts an application-defined message (WM_APP
) to the window. When the window receives the message, it immediately calls UnregisterWait()
to cancel the wait. As the reference states, even wait operations that use WT_EXECUTEONLYONCE
must be canceled when the wait is completed (but not from within the callback!). Then the window shows a message box to demonstrate that it has received the message.
Error handling is omitted for brevity. You should check the return value of each API function and call GetLastError()
in case FALSE
is returned.
#pragma comment(linker, "/SubSystem:Windows")
#include <windows.h>
#include <string>
int APIENTRY wWinMain( HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPWSTR lpCmdLine, int /*nCmdShow*/ )
{
if ( wcsstr( lpCmdLine, L"/child" ) )
{
MessageBoxW( nullptr, L"Hello from child process!", L"Child", MB_OK );
return 0;
}
// Create window
struct WindowData
{
HANDLE hWait = nullptr;
}
wndData;
WNDCLASSW wc{};
wc.hInstance = hInstance;
wc.hCursor = LoadCursor( nullptr, IDC_ARROW );
wc.hbrBackground = reinterpret_cast<HBRUSH>(GetStockObject( WHITE_BRUSH ));
wc.lpszClassName = L"MyClass";
wc.cbWndExtra = sizeof(LONG_PTR);
wc.lpfnWndProc = []( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
switch ( message )
{
case WM_APP:
{
// When the wait is completed, you must call the UnregisterWait or UnregisterWaitEx function to cancel
// the wait operation. (Even wait operations that use WT_EXECUTEONLYONCE must be canceled.)
WindowData* pWndData = reinterpret_cast<WindowData*>(GetWindowLongPtr( hWnd, 0 ));
UnregisterWait( pWndData->hWait );
pWndData->hWait = nullptr;
MessageBoxW( hWnd, L"Child process has ended!", L"Main", MB_OK );
}
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
}
return DefWindowProc( hWnd, message, wParam, lParam );
};
RegisterClassW( &wc );
HWND hWnd = CreateWindowExW( 0, wc.lpszClassName, L"Main", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr );
SetWindowLongPtr( hWnd, 0, reinterpret_cast<LONG_PTR>( &wndData) );
// Create child process
std::wstring cmd( MAX_PATH, L'\0' );
cmd.resize( GetModuleFileNameW( nullptr, &cmd[0], cmd.size() ) );
cmd = L"\"" + cmd + L"\" /child";
STARTUPINFOW si{ sizeof( si ) };
PROCESS_INFORMATION pi{};
CreateProcessW( nullptr, &cmd[0], nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi );
// Get notified when child process ends
RegisterWaitForSingleObject( &wndData.hWait, pi.hProcess,
[]( PVOID lpParameter, BOOLEAN /*TimerOrWaitFired*/ )
{
PostMessage( reinterpret_cast<HWND>(lpParameter), WM_APP, 0, 0 );
},
reinterpret_cast<PVOID>(hWnd), INFINITE, WT_EXECUTEONLYONCE );
// Run message loop
MSG msg;
while ( GetMessage( &msg, nullptr, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// Cleanup
if( wndData.hWait )
UnregisterWait( wndData.hWait );
if( pi.hProcess )
CloseHandle( pi.hProcess );
if( pi.hThread )
CloseHandle( pi.hThread );
return 0;
}
Bonus OldNewThing read: Why bother with RegisterWaitForSingleObject when you have MsgWaitForMultipleObjects?
Upvotes: 3
Reputation: 2556
The solution is creating a thread when your app is created. You then wait on an event that should be pulsed when needed. Example:
BOOL bStatus = TRUE;
CEvent mEvevnt;
// thread function
UINT LaunchThread( LPVOID p )
{
while(bStatus && ::WaitForSingleObject(HANDLE(mEvevnt), INFINITE) == WAIT_OBJECT_0) {
// create procees here
}
}
// thread creation
AfxBeginThread(LaunchThread, NULL);
Trigger thread into action:
mEvevnt.PulseEvent();
You destroy the thread when your app is ending:
bStatus = FALSE;
mEvevnt.PulseEvent();
Upvotes: 0
Reputation: 25388
Good news! Windows has exactly the API you're looking for: MsgWaitForMultipleObjects ().
Tricker, is to get this into MFC's message pump, but I found this link which recommends doing the following (code untested, fixed (!), and adapted to wait on just one handle):
// virtual
BOOL CMyApp::PumpMessage()
{
DWORD const res = ::MsgWaitForMultipleObjects
(1, &handle_I_am_interested in, TRUE, INFINITE, QS_ALLINPUT);
switch (res)
{
case WAIT_OBJECT_0 + 0:
// the handle was signalled, strut your stuff here
return TRUE;
case WAIT_OBJECT_0 + 1:
// there is a message in the queue, let MFC handle it
return __super::PumpMessage();
}
// Shouldn't happen
return TRUE;
}
I have to say that this code still doesn't look ideal to me, but it's probably close enough. I don't know enough about MFC to comment further.
Please note: This code won't see that the handle has been signalled until MFC passes through the message pump. That might happen while MessageBox()
has control, for example. If that bothers you, consider using RegisterWaitForSingleObject instead, as recommended above by the legendary Raymond Chen.
Upvotes: 1
Reputation: 9103
I would do one of these two:
Either call WaitForSingleObject()
or whatever with a timeout of zero in your message loop somewhere (might have to change your loop to PeekMessage()
or add WM_TIMER
messages to make sure you check every so often,)
Or better still, spawn a thread with a very small stack (you can customize that in the CreateThread()
call) that only waits for this child process and then posts a message to your message loop.
I like option 2 better, since a thread with a small stack that does nothing but wait for stuff is hardly a resource drain.
Upvotes: 0
Reputation: 7581
If you can modify the code of the child process, you may just add a back-channel that will inform the parent process by SendMessage
when it is about to leave. If you cannot do that, you may create a relay process that will just pass-through the original child process data (if any) but will do the information job when the child left. This is, of course, least to say far less elegent than just using a dedicated thread and e.g. WaitForSingleObject
.
Upvotes: 0