【问题标题】:How to display a modal dialog window from another process?如何显示来自另一个进程的模式对话框窗口?
【发布时间】:2017-05-11 07:32:23
【问题描述】:

我有一个 32 位 MFC 应用程序,它使用自定义库,重新编译成 x64 将是一场噩梦。一般来说,应用程序实际上并不需要以 64 位运行,除非有一种情况——即渲染内容以显示在对话窗口中,这可以受益于更大的寻址空间。

所以我的目标是“模仿”CDialog::DoModal 方法,但用于另一个进程中的对话。

我将该对话框窗口构建为一个独立的基于 x64 MFC 对话框的应用程序。它将文件路径作为输入参数,在内部完成所有工作,并返回简单的用户选择:OKCancel

所以我从我的主要父进程执行以下操作:

//Error checks omitted for brevity
CString strCmd = L"D:\\C++\\MyDialogBasedApp.exe";

HWND hParWnd = this->GetSafeHwnd();

SHELLEXECUTEINFO sei = {0};
sei.cbSize = sizeof(sei);
sei.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS;
sei.nShow = SW_SHOW;
sei.lpVerb = _T("open");
sei.lpFile = strCmd.GetBuffer();
sei.hwnd = hParWnd;

BOOL bInitted = SUCCEEDED(::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE));

ShellExecuteEx(&sei);

DWORD dwProcID = ::GetProcessId(sei.hProcess);

//Try to get main Wnd handle for the child process
HWND hMainChildWnd = NULL;
for(;; ::Sleep(100))
{
    hMainChildWnd = getHwndFromProcID(dwProcID);
    if(hMainChildWnd)
        break;
}

HWND hPrevParWnd = ::SetParent(hMainChildWnd, hParWnd);
if(hPrevParWnd)
{
    //Wait for child process to close
    ::WaitForSingleObject(sei.hProcess, INFINITE);

    //Reset parent back
    ::SetParent(hMainChildWnd, hPrevParWnd);
}

::CloseHandle(sei.hProcess);

if(bInitted)
    ::CoUninitialize();

getHwndFromProcID 的位置是 from here

这有点工作,除了以下内容:

(1) 任务栏上有两个图标:一个用于我的主应用程序,一个用于子应用程序。有没有办法不显示子图标?

(2) 我可以将焦点从子窗口切换到父窗口,反之亦然。在实际的模态对话框窗口中,当子进程打开时,无法切换回父进程。有没有办法做到这一点?

(3) 如果我开始与父级交互,它似乎被“挂断”了,操作系统甚至会在其标题栏上显示它。

所以我很好奇是否有办法解决所有这些问题?

【问题讨论】:

    标签: c++ windows winapi mfc modal-dialog


    【解决方案1】:

    模态对话框做了两件事使其成为“模态”:

    • 对话框的“所有者”设置为父窗口。
    • 父窗口被禁用。

    我玩了一下,虽然您可以手动执行这些操作,但最简单的方法是将父窗口句柄传递给 DialogBox 函数(或 MFC 中的 CDialog 构造函数)。

    您的子进程可以使用FindWindow(或类似机制)获取父窗口句柄并使用它来显示对话框,而不是在父进程中完成ShellExecuteEx 之后的所有工作。

    【解决方案2】:
    1. 您需要将自己的窗口指针传递给子进程
    2. 您需要处理 Windows 消息,同时等待子进程 出口。 WaitForSingleObject 在这里无法接受 - 需要使用 MsgWaitForMultipleObjectsEx
    3. 子进程必须在创建时将您的窗口设置为自己的所有者窗口 时间 - 你不需要打电话SetParent

    有了这些,一切都会完美无缺。在您的 32 位 MFC 应用程序中,您需要使用下一个代码:

    BOOL DoExternalModal(HWND hwnd, PCWSTR ApplicationName)
    {
        STARTUPINFO si = { sizeof(si) };
        PROCESS_INFORMATION pi;
        WCHAR CommandLine[32];
        swprintf(CommandLine, L"*%p", hwnd);
    
        if (CreateProcessW(ApplicationName, CommandLine, 0, 0, 0, 0, 0, 0, &si, &pi))
        {
            CloseHandle(pi.hThread);
    
            MSG msg;
    
            for (;;)
            {
                switch (MsgWaitForMultipleObjectsEx(1, &pi.hProcess, INFINITE, QS_ALLINPUT, 0))
                {
                case WAIT_OBJECT_0:
                    CloseHandle(pi.hProcess);
                    return TRUE;
                case WAIT_OBJECT_0 + 1:
                    while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
                    {
                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                    }
                    continue;
                default: __debugbreak();
                }
            }
        }
    
        return FALSE;
    }
    

    MyDialogBasedApp.exe 中使用MessageBox 作为演示对话框。我们将使用您的 MFC 窗口作为它的第一个参数。

    void ExeEntry()
    {
        int ec = -1;
    
        if (PWSTR CommandLine = GetCommandLine())
        {
            if (CommandLine = wcschr(CommandLine, '*'))
            {
                HWND hwnd = (HWND)(ULONG_PTR)_wcstoi64(CommandLine + 1, &CommandLine, 16);
    
                if (hwnd && !*CommandLine && IsWindow(hwnd))
                {
                    ec = MessageBoxW(hwnd, L"aaa", L"bbb", MB_OK);
                }
            }
        }
    
        ExitProcess(ec);
    }
    

    使用此代码:

    (1) 主应用的任务栏上只有一个图标

    (2) 您不能将焦点从子窗口切换到父窗口,反之亦然。都作为实际的模态对话框窗口工作

    (3) 父级未“挂起”,因为它处理 Windows 消息 (MsgWaitForMultipleObjectsEx) - 您的代码已“挂起”,因为您没有这样做,而是等待 WaitForSingleObject

    【讨论】:

    • 哇,伙计。它完全有效!谢谢。不过,对您的代码进行了两次更正。 (1) 为什么不使用 MFC 的 theApp.PumpMessage(); 而不是 PeekMessage while 循环来泵送消息?还是您严格遵守 Win32。并且(2)您的方法不会从任务栏中删除子进程的图标。要在MyDialogBasedApp.exe 进程中执行此操作,您需要从WM_INITDIALOG 处理程序中的self 窗口中删除WS_EX_APPWINDOW 扩展样式。
    • @c00000fd - 1) 是的 - 您可以使用while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) App->PumpMessage(); 而不是while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){..} 2) 在自己的测试中,我使用 MessageBoxW 对话框,它不会创建任务栏的图标。我不使用 WS_EX_APPWINDOW。我当然不知道你在MyDialogBasedApp.exe中使用了什么@
    • @c00000fd - 在这里实现消息循环的方式没有特别的区别,因为模态对话框有自己的消息循环。如果说您直接从您的应用程序调用 MessageBox - 在它运行时 - 内部消息循环将在 MessageBox 中执行,但不是您的 MFC 循环。 main - UI线程必须永久调用PeekMessageGetMessage处理windows消息,否则UI挂起
    • Is it legal to have a cross-process parent/child or owner/owned window relationship? 这在某些时候会中断,并且您无法修复此问题,因为 MFC 不在您的控制范围内。您不能可靠地实现跨进程所有者/拥有的窗口关系。抱歉,-1 甚至没有提到这一点。
    • 评论不用于扩展讨论;这个对话是moved to chat
    【解决方案3】:

    您正在尝试做的事情无法安全地完成。博客条目Is it legal to have a cross-process parent/child or owner/owned window relationship? 解释说,安装跨进程所有者/拥有的窗口关系会导致系统调用AttachThreadInput,并且——众所周知——AttachThreadInput is like taking two threads and pooling their money into a joint bank account, where both parties need to be present in order to withdraw any money。这为死锁创造了非常真实的可能性。如果您控制两个参与线程,您只能安全地防止这种情况发生。由于至少有一个线程使用第 3 方应用程序框架(在本例中为 MFC),因此这是不受限制的。

    由于我们已经确定您提出的解决方案无法安全实施,因此您需要寻找替代方案。一种解决方案可能是将工作委托给 64 位进程进行计算,并将结果传递回 32 位进程进行显示。

    【讨论】:

    • 感谢您的意见。因此,在我们进一步讨论之前,让我问这个问题。 Chen 的文章讨论了作为“陌生人”的子进程或其他我们无法控制的进程。在我的情况下,我控制这两个过程并可以根据需要调整它们。同样是MFC框架,并不陌生。我们有它的源代码,我们知道幕后发生了什么。那里没有秘密。我将静态链接到它,因此它也不会随着 Windows 更新而改变。最重要的是,我不会对这些窗口进行编码以相互发送消息。那么你能用这个特定的例子说明为什么它不起作用吗?
    • 我在从 XP 到 Win10 的操作系统上对其进行了测试,我似乎唯一遇到问题的是 XP。由于某种原因,那里的两个窗口表现得像不同的进程(而不像模态的孩子和父母。)其余的,Vista 和 up 工作得很好。没有问题,没有挂断,内存损坏等。再说一次,我不是在批评你的批评,我只是在问一个具体的原因为什么不......
    • 故障模式不是,因为你不知道 MFC是做什么的。问题是,MFC 不准备跨线程共享输入队列,也没有做任何特别的事情来迎合该用例。 (此外,MFC 不是操作系统的一部分,无论如何也不会收到任何更新。如果您想过上幸福的生活,您绝对应该动态链接。)另外,您正在之间发送消息视窗。您只是不知道,因为该代码已被烘焙到对话框管理器(您正在使用)中。
    • 只是好奇,是什么让你觉得this answer中描述的方法会share an input queue across threads或调用AttachThreadInput
    • @c00000fd:有一个HWND 传递给MessageBoxW,它属于不同的线程。从我链接到的blog post 中,我们了解到,“创建跨线程 [...] 所有者/拥有的窗口关系会隐式附加这些窗口所属线程的输入队列”我>。我敢肯定,如果您要求该建议答案的作者进入代码,他们会得出结论,在创建跨线程窗口层次结构的过程中调用了AttachThreadInput
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-29
    • 2012-06-03
    • 1970-01-01
    • 2013-01-11
    • 1970-01-01
    • 2020-09-05
    • 2014-01-04
    相关资源
    最近更新 更多