启动一个应用程序并等待它完成而不阻止重绘

时间:2017-05-17 14:07:05

标签: c multithreading winapi

我有一个交互式Win32应用程序,在某些时候我需要启动另一个应用程序并等待其他应用程序完成。在其他应用程序运行期间,交互式应用程序不应该响应调整大小和移动窗口(当然这意味着交互式应用程序仍应继续重绘)。

我目前的做法是:

  1. 创建主题T
  2. 禁用主窗口(使用EnableWindow(handle, FALSE)
  3. 继续留言循环
  4. 从线程T发送的特殊WM_APP消息将再次启用主窗口(EnableWindow(handle, TRUE));
  5. 线程T:

    1. 使用CreateProcess
    2. 启动应用程序
    3. 使用WaitForSingleObject
    4. 等待应用程序终止
    5. 使用WM_APP向主窗口发布特殊PostMessage消息。
    6. 线程终止于此
    7. 这很好用,但我不确定这是不是正确的方法。

      是否有更好的方法或者我应该继续这样做?

1 个答案:

答案 0 :(得分:2)

您的方法很好,只需确保使用PostMessage()

  

向主窗口发送特殊的WM_APP消息

如果主线程正好等待线程T,则避免死锁。

正如评论者指出的,创建线程的替代方法是使用带有MsgWaitForMultipleObjectsEx的消息循环。

我看到的优势:

  • 不需要额外的线程,因此不会担心与线程相关的问题,例如竞争条件和死锁。这些问题通常很难找到和调试(通常它们只发生在客户机器上),因此我尽量避免使用线程。
  • 创建流程并等待它的程序流程更简单。它可以是顺序的,而不是像线程解决方案那样基于事件。

如果这是针对您的方案的更好方法,您必须自己判断。

可用于等待进程(或任何其他可等待句柄)处理消息的功能如下所示。它的实现非常复杂(有关背景信息,请参阅我的答案末尾的链接),但使用非常简单(参见后续示例)。

// Function to process messages until the state of any of the given objects is signaled, 
// the timeout is reached or a WM_QUIT message is received.
// Parameter hDialog can be nullptr, if there is no dialog.
//
// Returns ERROR_SUCCESS if any of the given handles is signaled.
// Returns ERROR_TIMEOUT in case of timeout.
// Returns ERROR_CANCELLED if the WM_QUIT message has been received.
// Returns the value of GetLastError() if MsgWaitForMultipleObjectsEx() fails.

DWORD WaitForObjectsWithMsgLoop( 
    HWND hDialog, const std::vector<HANDLE>& handles, DWORD timeOutMillis = INFINITE )
{
    if( handles.empty() )
        return ERROR_INVALID_PARAMETER;

    DWORD handleCount = static_cast<DWORD>( handles.size() );
    DWORD startTime = GetTickCount();
    DWORD duration = 0;
    do
    {
        DWORD status = MsgWaitForMultipleObjectsEx(
            handleCount, handles.data(), 
            timeOutMillis - duration, 
            QS_ALLINPUT, 
            MWMO_INPUTAVAILABLE );

        if( status == WAIT_FAILED )
        {
            // MsgWaitForMultipleObjectsEx() has failed.
            return GetLastError();
        }
        else if( status >= WAIT_OBJECT_0 && status < WAIT_OBJECT_0 + handleCount )
        {
            // Any of the handles is signaled.
            return ERROR_SUCCESS;
        }
        else if( status == WAIT_OBJECT_0 + handleCount )
        {
            // New input is available, process it.
            MSG msg;
            while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
            {
                if( msg.message == WM_QUIT )
                {
                    // End the message loop because of quit message.
                    PostQuitMessage( static_cast<int>( msg.wParam ) );
                    return ERROR_CANCELLED;
                }
                // Enable message filter hooks (that's what the system does in it's message loops).
                // You may use a custom code >= MSGF_USER.
                // https://blogs.msdn.microsoft.com/oldnewthing/20050428-00/?p=35753
                if( ! CallMsgFilter( &msg, MSGF_USER ) )
                {
                    // Optionally process dialog messages.
                    if( ! hDialog || ! IsDialogMessage( hDialog, &msg ) )
                    {
                        // Standard message processing.
                        TranslateMessage( &msg );
                        DispatchMessage( &msg );
                    }
                }
            }
        }

        duration = GetTickCount() - startTime;
    }
    while( duration < timeOutMillis );

    // Timeout reached.
    return ERROR_TIMEOUT;
}

该功能可用于对话框程序,如下所示。为简洁省略了错误处理。

INT_PTR CALLBACK YourDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
    static bool s_childProcessRunning = false;

    switch( message )
    {
        case YourMessageToLaunchProcess:
        {
            // prevent reentrancy in case the process is already running
            if( s_childProcessRunning )
            {
                MessageBoxW( hDlg, L"Process already running", L"Error", MB_ICONERROR );
                return TRUE;
            }
            // Prepare CreateProcess() arguments
            STARTUPINFO si{ sizeof(si) };
            PROCESS_INFORMATION pi{};
            wchar_t command[] = L"notepad.exe"; // string must be writable!

            // Launch the process
            if( CreateProcessW( NULL, command, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi ) )
            {
                // Set flag to prevent reentrancy. 
                s_childProcessRunning = true;  

                // Wait until the child process exits while processing messages
                // to keep the window responsive.  
                DWORD waitRes = WaitForObjectsWithMsgLoop( hDlg, { pi.hProcess } );
                // TODO: Check waitRes for error

                s_childProcessRunning = false;

                // Cleanup
                CloseHandle( pi.hThread );
                CloseHandle( pi.hProcess );
            }
            return TRUE;
        }

        // more message handlers...
    }
    return FALSE;
}

强制旧的新事物链接: