3

I have a 32-bit MFC application that uses a custom library that would be a nightmare to re-compile into x64. In general the app doesn't really need to run as 64-bit, except in one case -- and that is to render contents to display in a dialog window, which can benefit from a larger addressing space.

So my goal is to "imitate" CDialog::DoModal method but for a dialog in another process.

I built that dialog window as a standalone x64 MFC dialog-based application. It takes a file path as it's input parameter, does all the work internally, and returns simple user selection: OK, Cancel.

So I do the following from my main parent process:

//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();

where getHwndFromProcID is taken from here.

This kinda works, except of the following:

(1) There are two icons on the taskbar: one for my main app, and one for the child app. Is there a way not to show a child icon?

(2) I can switch focus from child window to parent and vice versa. In actual modal dialog window one cannot switch back to parent while child is open. Is there a way to do that?

(3) If I start interacting with the parent, it appears to be "hung up" and the OS will even show it on its title bar.

So I was curious if there's a way to resolve all these?

Community
  • 1
  • 1
c00000fd
  • 20,994
  • 29
  • 177
  • 400

3 Answers3

3
  1. you need pass pointer of own window to child process
  2. you need process windows messages, while you wait on child process exit. WaitForSingleObject unacceptably here - need use MsgWaitForMultipleObjectsEx
  3. child process must set your window as self owner window at create time - you not need call SetParent

with this all will be worked perfect. in your 32-bit MFC application you need use next code :

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;
}

in MyDialogBasedApp.exe let use MessageBox as demo dialog. we will be use your MFC window as first argument to it.

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);
}

with this code:

(1) There are only one icon on the taskbar for your main app

(2) You can not switch focus from child window to parent and vice versa. all work as actual modal dialog window

(3) parent not "hung up" because it processing windows messages (MsgWaitForMultipleObjectsEx) - your code is "hung up" because you not do this, but wait in WaitForSingleObject

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Wow, dude. It totally works! Thanks. Two corrections to your code though. (1) Why didn't you use MFC's [theApp.PumpMessage();](https://msdn.microsoft.com/en-us/library/3dy7kd92.aspx) to pump messages instead of the `PeekMessage` while loop? or do you stick strictly to Win32. And (2) Your method does not remove an icon of a child process from the taskbar. To do that in the `MyDialogBasedApp.exe` process you need to remove `WS_EX_APPWINDOW` extended style from the self window within `WM_INITDIALOG` handler. – c00000fd Dec 27 '16 at 05:10
  • @c00000fd - 1) yes - you can use `while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) App->PumpMessage();` instead `while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){..}` 2)in own test i use MessageBoxW dialog, which not create icon of the taskbar. i and not use WS_EX_APPWINDOW. i of course can not know what you use in `MyDialogBasedApp.exe` – RbMm Dec 27 '16 at 08:58
  • @c00000fd - no special difference how you implement message loop here because modal dialog have own message loop. if say you direct call `MessageBox` from your app - while it running - internal message loop will be executed in `MessageBox` but not your MFC loop. main - UI thread **must** permanent call `PeekMessage` or `GetMessage` for process windows messages, otherwise UI hung up – RbMm Dec 27 '16 at 09:11
  • [Is it legal to have a cross-process parent/child or owner/owned window relationship?](https://blogs.msdn.microsoft.com/oldnewthing/20130412-00/?p=4683) This will break, at some point, and you cannot fix this, since MFC is outside your control. You cannot reliably implement a cross-process owner/owned window relationship. Sorry, -1 for not even mentioning this. – IInspectable Dec 27 '16 at 18:00
  • Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackoverflow.com/rooms/131701/discussion-on-answer-by-rbmm-how-to-display-a-modal-dialog-window-from-another-p). – Bhargav Rao Dec 28 '16 at 12:13
0

A modal dialog box does two things that make it "modal":

  • The dialog's "owner" is set to the parent window.
  • The parent window is disabled.

I played around with this a little bit and while you can do these manually, the easiest way is to just pass the parent window handle to the DialogBox function (or the CDialog constructor in MFC).

Instead of doing all the work after ShellExecuteEx in the parent process, your child process can use FindWindow (or a similar mechanism) to get the parent window handle and use that to display the dialog.

casablanca
  • 69,683
  • 7
  • 133
  • 150
  • [Is it legal to have a cross-process parent/child or owner/owned window relationship?](https://blogs.msdn.microsoft.com/oldnewthing/20130412-00/?p=4683) – IInspectable Dec 27 '16 at 18:01
0

What you are trying to do cannot safely be done. The blog entry Is it legal to have a cross-process parent/child or owner/owned window relationship? explains, that installing a cross-process owner/owned window relationship causes the system to call AttachThreadInput, and - as we all know - 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. This creates a very real potential for a dead lock. You can only safely prevent this from happening, if you control both participating threads. Since at least one thread uses a 3rd party application framework (MFC in this case), this is off limits.

Since we have established, that your proposed solution cannot safely be implemented, you need to look into alternatives. One solution might be to delegate the work to a 64-bit process for computation, and have the results passed back to your 32-bit process for display.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • 1
    I appreciate your input. So before we go any further, let me ask this. Chen's articles deal with a child process being a "stranger" or some other process that we cannot control. In my case I control both processes & can adjust them as needed. Also MFC framework, is not a stranger. We have its source code, we know what's going on behind the scenes. There's no secret there. I will link to it statically so it won't change with a Windows update either. On top of that I will not code those windows to send each other messages. So can you use this particular example and say why it wouldn't work? – c00000fd Dec 27 '16 at 22:03
  • I tested it on OS's from XP to Win10, and the only one that I seem to have issues with is XP. For some reason two windows there behave like different processes (and not like a modal child & parent.) The rest, Vista and up work just fine. No problems, no hang-ups, memory corruptions, etc. Again, I'm not criticizing your critique, I'm just asking for a specific reasons why not..... – c00000fd Dec 27 '16 at 22:05
  • The failure mode is not, because you don't **know** what MFC does. The problem is, that MFC is not prepared to share an input queue across threads, and doesn't do anything special to cater to that use case. (Besides, MFC isn't part of the OS, and won't receive any updates anyway. You should definitely link dynamically if you want to life a happy life.) Plus, you **are** sending messages between windows. You just don't know, because that code is baked into the dialog manager (which you are using). – IInspectable Dec 27 '16 at 22:12
  • Just curious, what makes you think that the method described in [this answer](http://stackoverflow.com/a/41338347/843732) will `share an input queue across threads` or call `AttachThreadInput`? – c00000fd Dec 28 '16 at 03:06
  • @c00000fd: There's a `HWND` passed to `MessageBoxW`, that is owned by a different thread. And from the [blog post](https://blogs.msdn.microsoft.com/oldnewthing/20130412-00/?p=4683) I linked to, we have learned, that *"creating a cross-thread [...] owner/owned window relationship implicitly attaches the input queues of the threads which those windows belong to"*. I'm sure if you ask the author of that proposed answer to step into the code, they will come to the conclusion, that `AttachThreadInput` is called in the process of creating a cross-thread window hierarchy. – IInspectable Dec 28 '16 at 11:10
  • The reason I'm asking is because I did check both parent and child processes with `WinDbg` and neither of them seem to call `AttachThreadInput` or `bp USER32!NtUserAttachThreadInput` counterpart. – c00000fd Dec 28 '16 at 16:28