我有一个MFC应用程序,它使用CreateProcess(...)
启动另一个进程。我想在创建的进程终止时执行UI更新。通常,我会在返回的进程WaitForSingleObject
上使用WaitForMutlipleObject
或HANDLE
,但这会阻止GUI线程(错误)。
我能想到的唯一解决方案是生成一个新线程,该线程可以等待句柄并在进程终止时发布消息。这不太理想。
是否可以在Windows Manager中注册句柄并在进程终止时收到Windows消息?
答案 0 :(得分:2)
当流程结束时,您可以使用RegisterWaitForSingleObject()
通过回调获得通知。 RegisterWaitForSingleObject
函数指示thread pool中的等待线程等待进程,因此这应该是资源的最佳使用。正如Raymond Chen评论的那样:
线程池可以将多个Wait请求批处理为单个调用 WaitForMultipleObjects因此摊销成本是线程的1/63。
下面是Win32 GUI应用程序的最小示例。代码创建一个窗口,然后它创建自己的另一个实例作为子进程,由“/ child”参数指示。它注册等待回调函数并运行常规消息循环。您可以调整窗口大小并移动窗口以查看GUI未被阻止。当子进程结束时,系统异步调用等待回调,该回调将应用程序定义的消息(WM_APP
)发布到窗口。当窗口收到消息时,它立即调用UnregisterWait()
取消等待。正如参考所述,在等待完成时必须取消使用WT_EXECUTEONLYONCE
的等待操作(但不是在回调中!)。然后窗口显示一个消息框,以证明它已收到消息。
为简洁起见,省略了错误处理。您应该检查每个API函数的返回值,并在返回GetLastError()
的情况下调用FALSE
。
#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阅读:Why bother with RegisterWaitForSingleObject when you have MsgWaitForMultipleObjects?
答案 1 :(得分:1)
Tricker,是为了让它进入MFC的消息泵,但我发现this link建议执行以下操作(代码未经测试,已修复(!),并且适合仅在一个句柄上等待):
// 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;
}
我不得不说这段代码对我来说仍然不合适,但它可能已经足够接近了。我不太了解MFC进一步评论。
请注意:此代码在MFC通过消息泵之前不会看到手柄已发出信号。例如,MessageBox()
具有控制权时可能会发生这种情况。如果这让您感到困扰,请考虑使用RegisterWaitForSingleObject,而不是传说Raymond Chen的推荐。
答案 2 :(得分:0)
如果您可以修改子进程的代码,您可以添加一个反向通道,该通道将在SendMessage
即将离开时通知父进程。如果您不能这样做,您可以创建一个只传递原始子进程数据(如果有)的中继进程,但是当子进程离开时将执行信息工作。当然,这至少要比仅仅使用专用线程和例如线程要少得多。 WaitForSingleObject
。
答案 3 :(得分:0)
我会做这两个中的一个:
在某个地方的消息循环中调用WaitForSingleObject()
或超时为零的任何内容(可能必须将循环更改为PeekMessage()
或添加WM_TIMER
消息以确保您经常检查,)
或者更好的是,生成一个具有非常小的堆栈的线程(您可以在CreateThread()
调用中自定义)仅等待此子进程,然后将消息发布到您的消息循环。< / p>
我更喜欢选项2,因为一个小堆栈的线程除了等待东西之外什么也不做什么都不是资源消耗。
答案 4 :(得分:0)
解决方案是在创建应用时创建一个线程。然后等待一个应该在需要时发出脉冲的事件。例如:
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);
触发线程:
mEvevnt.PulseEvent();
你的应用程序结束时销毁线程:
bStatus = FALSE;
mEvevnt.PulseEvent();