c - 启动应用程序并等待其完成,而不会阻止重绘

标签 c multithreading winapi

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

我目前的方法是这样的:

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

线程T:

  1. 使用CreateProcess启动应用程序
  2. 使用 WaitForSingleObject 等待应用程序终止
  3. 使用 PostMessage 将特殊的 WM_APP 消息发布到主窗口。
  4. 线程在此终止

这工作正常,但我不确定这是否是正确的方法。

有更好的方法还是我应该继续这样?

最佳答案

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

send a special WM_APP message to the main window

如果主线程恰好等待线程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;
}

强制性的旧新事物链接:

关于c - 启动应用程序并等待其完成,而不会阻止重绘,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44027206/

相关文章:

C 编译程序崩溃

c - 不是 c 中结构的预期结果循环

python停止多线程回显服务器

c++ - 将视频流附加到现有应用程序

c# - 如何在没有物理硬件的情况下利用多显示器设置的功能?

c++ - WinAPI - 获取用于绘图的可滚动框架

c - 如果 posix 关闭调用失败怎么办?

c - 在 ARM cpu 上使用 uart

c# - 等待所有异步 Web 服务调用返回

java - 编写 Java Server 以并发(同时)处理多个客户端