We help IT Professionals succeed at work.

Fire up app "modal" using CreateProcess

aphillips
aphillips asked
on
I want an app to fire up another app in a modal fashion.  Ie suspend itself until the 2nd app has returned.

Calling CreateProcess and waiting for it to finish works fine but no WM_PAINT messages get through so if you move the front window around the background app behind does not get redrawn.  This is ugly if not confusing to the user.

So I created a message loop to handle messages while waiting for the 2nd app to terminate.  If I process all messages the user can accidentally click the background app while the 2nd app is still running which is confusing and causes other problems.  (See code below.)

If I process WM_PAINT and ignore all others the user can still click on the "disabled" app but can't do anything.  This also is confusing to the user.

I really want it so that the background app handles WM_PAINT and other messages (eg automation) but if the user tries to interact with the background app then control returns to the 2nd app.


--------------------------------------------------
    STARTUPINFO startupInfo;
    PROCESS_INFORMATION processInfo;
   
    ZeroMemory(&startupInfo,sizeof(startupInfo));  
    startupInfo.cb = sizeof(startupInfo);

    if (!::CreateProcess(NULL,
                command_line,
                NULL,
                NULL,
                FALSE,
                0,
                NULL,
                NULL,
                &startupInfo,
                &processInfo
                ))
    {
        // error
    }
    else
    {
        BOOL fin = FALSE;

        while (!fin)
        {
            MSG msg;

            switch (MsgWaitForMultipleObjects(1,
                           &processInfo.hProcess,
                           FALSE, INFINITE, QS_ALLINPUT))
            {
            case WAIT_OBJECT_0 + 0:
                fin = TRUE;
                break;

            case WAIT_OBJECT_0 + 1:
                 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                 {
                    if (msg.message == WM_QUIT)
                        fin = TRUE;
                    else
                    {
                        // We need better message handling?
                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                    }
                }
                break;
            }
        }
        CloseHandle(processInfo.hProcess);
        CloseHandle(processInfo.hThread);
    }
Comment
Watch Question

Commented:
You should disable your own window with EnableWindow(windowHandle, false). Furthermore you should handle WM_ACTIVATE(APP). If your window gets activated, reactivate the other window.

Regards, Madshi.

Author

Commented:
> ... disable your own window with EnableWindow ...
Thanks for the info.  I have used EnableWindow on controls but never thought to use it on the main window.

> ... reactivate the other window ...
But how do you activate another app.  You can't call SetActiveWindow() for another process (or even thread).

Commented:
Use SetForegroundWindow.

Author

Commented:
> ... disable your own window with EnableWindow ...
Thanks for the info.  I have used EnableWindow on controls but never thought to use it on the main window.

> ... reactivate the other window ...
But how do you activate another app.  You can't call SetActiveWindow() for another process (or even thread).

Author

Commented:
> Use SetForegroundWindow.

I saw SetForeGroundWindow but I wasn't sure it worked across processes.  I also need to get the window handle of the fired up app in order to use it.

Thanks again.
[Sorry about the double post, I'm sure I didn't do it.]

Author

Commented:
Also I have tried intercepting WM_ACTIVATE and WMACTIVATEAPP but my app never seems to get these messages.  This is true whether I click on it, click the task bar, or use Alt-Tab.

Author

Commented:
If anyone has done this (someone must have) can they post some sample code.

Commented:
>> Also I have tried intercepting WM_ACTIVATE and WMACTIVATEAPP but my app never seems to get these messages.

What do you mean by "intercepting" WM_ACTIVATE(APP)? You should not intercept them, you should just handle them in your application's main window windowProc. That should work fine.

>> I saw SetForeGroundWindow but I wasn't sure it worked across processes.

It does.

>> I also need to get the window handle of the fired up app in order to use it.

Use EnumWindows. For each enumerated window you have to check whether the processID to which the window belongs (see GetWindowThreadProcessID) matches the one you got from CreateProcess. This way you get a list of windows which belong to the process you've started.

Regards, Madshi.

Author

Commented:
> What do you mean by "intercepting" WM_ACTIVATE(APP)? ...

By "intercept" I mean I added a test of the message to see if it was a WM_ACTIVATE or WM_ACTIVATEAPP message inside the message loop (see my original code posting above).  I then put a breakpoint on it, but the breakpoint was never taken.

I then also added a TRACE (see below).  In both cases it does not appear that either of these messages are being sent to the mainframe window.

...
  while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  {
    if (msg.message == WM_QUIT)
      fin = TRUE;
    else
    {
      // ---- if statement added ------
      if (msg.message == WM_ACTIVATE ||
          msg.message == WM_ACTIVATEAPP)
      {
        TRACE("%x\n", msg.message);
      }
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

>> I also need to get the window handle ...
>Use EnumWindows. ...

OK I can get the handles of the top-level windows.  How do I know which one is for the process created?


Have you ever done what I am asking?  If so could you please post some code?  This would be very much easier than this continual try this -- but how do I - well try this -- but ...

Commented:
>> Have you ever done what I am asking?

Not 100% the same, but quite the same thing.

>> If so could you please post some code? This would be very much easier than this continual try this -- but how do I - well try this -- but ...

That's right, but I'm a Delphi programmer, so you would probably not understand my code.

>> In both cases it does not appear that either of these messages are being sent to the mainframe window.

You're checking for WM_ACTIVATE(APP) in the wrong place. Like I said in my previous comment: "You should not intercept them, you should just handle them in your application's main window windowProc". That's a difference, because in your windowProc you will get both messages that were sent by PostMessage and by SendMessage. Inside of the message loop, you might miss the messages, that were sent trough SendMessage.

>> OK I can get the handles of the top-level windows.  How do I know which one is for the process created?

I already explained that in my previous comment, too. Something like this (Delphi code!). You should make your processInfo variable be a global one:

var processInfo : TProcessInformation;

function EnumWindowsProc(wnd: dword; lParam: integer) : bool; stdcall;
var pid : dword;
begin
  // we want to have all windows
  result := true;

  // for each enumerated window let's first of all ask the processID
  GetWindowThreadProcessID(wnd, @pid);
 
  // does the pid match the process we've created?
  if processInfo.dwProcessID = pid then begin

    // ha! we've found the window!!
    SetForegroundWindow(wnd);

    // stop enumerating
    result := false;
  end;
end;

Regards, Madshi.

Author

Commented:
Sorry for the delay in replying, but I have been flat out on other things and the EE server has been down a few times too.

> Like I said in my previous comment: "You should
> not intercept them, you should just handle them
> in your application's main window windowProc".

As I explained in my previous post all I meant by "intercept" is that I put in a breakpoint in my message loop.  Also I though by creating a message loop that then became the main window's WindowProc.  It seems that Windows is even more convoluted than I thought.

Anyway, as my code is just an add-on module I can't get access to the main window WindowProc.  Its seems I have to "subclass" it using SetWindowLong(GWL_WNDPROC).

Commented:
Right, in that case you have to subclass it...   :-(

Author

Commented:
I "subclassed" the WindowProc and it gets called with the WM_ACTIVATE nd WM_ACTIVATEAPP messages.  I do this just before the the while(!fin) loop above and "unsbclass" jast after the loop.

However, now MsgWaitForMultipleObjects does not return even when the other process terminates.

Commented:
Hmmm... Would you please show me your subclassed window function?

If all else fails, I would try to put the WaitForXxx in a thread. You could just create a very little thread, which does nothing but wait for the other application to finish (with WaitForSingleObject), then the thread could post a message to the window that you have subclassed, then the thread would be done. In your subclassed window function you could check for that special message that was sent from your waiting thread. Well, not nice. It would be nicer/better, if you could make it work as it is.

Regards, Madshi.

Commented:
Perhaps you can repost all of your current code, just to see it in one piece...

Author

Commented:
static WNDPROC prev_windowproc;

static LRESULT CALLBACK InvokeWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_ACTIVATE:
        TRACE("THIS IS SEEN\n");
        break;
    case WM_ACTIVATEAPP:
        TRACE("THIS IS SEEN TOO\n");
        break;
    }

    return CallWindowProc(prev_windowproc, hwnd, msg, wParam, lParam);
}
....

  STARTUPINFO startupInfo;
  PROCESS_INFORMATION processInfo;
   
  ZeroMemory(&startupInfo,sizeof(startupInfo));  
             startupInfo.cb = sizeof(startupInfo);

  if (!::CreateProcess(NULL, buf,NULL,NULL,FALSE,0, NULL,NULL, &startupInfo,&processInfo))
    // handle error ...
  else
  {
    BOOL fin = FALSE;
    AfxGetMainWnd()->EnableWindow(FALSE);
    prev_windowproc = (WNDPROC)::SetWindowLong(AfxGetMainWnd()->m_hWnd,
                             GWL_WNDPROC, (LONG)&InvokeWindowProc);

    while (!fin)
    {
       MSG msg;

       switch (MsgWaitForMultipleObjects(1, &processInfo.hProcess, FALSE, INFINITE, 0xFFFF))
       {
       case WAIT_OBJECT_0 + 0:
         fin = TRUE;
         break;

       case WAIT_OBJECT_0 + 1:
         while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
         {
           if (msg.message == WM_QUIT)
             fin = TRUE;
         }
         break;
       }
    }
    (void)::SetWindowLong(AfxGetMainWnd()->m_hWnd, GWL_WNDPROC, (LONG)prev_windowproc);
    AfxGetMainWnd()->EnableWindow(TRUE);
    CloseHandle(processInfo.hProcess);
    CloseHandle(processInfo.hThread);

Author

Commented:
Sorry, it's working now.

It was actually stuck inside the PeekMessage loop as I was getting continuous WM_PAINT messages while debugging due to the app window being behind Dev Studio.

I still have to implement the code to find the window of the created process and call SetForeGround on it.  I can't see any major problems with the info yuo have kindly supplied.

Thanks Madshi.

Author

Commented:
I was hoping for some example code that I could just slot in.  (I'm sure this has been done many times before.)  However, the info given was excellent.

Commented:
If you run into problems with the window searching, you can ask here, I'm still listening...