views:

686

answers:

3

My application (the bootstrap application for an installer that I'm working on needs to launch some other applications (my installer and third party installers for my installer's prerequisites) and wait for them to complete. In order to allow the GUI to do screen updates while waiting for an app to complete, I put a message pump in the wait loop using the 'MFC-compatible' example in the Visual Studio documentation on idle loop processing as a guideline. My code (which is in a member function of a CWinApp-derived class) is as follows:

if (::CreateProcess(lpAppName, szCmdLineBuffer, NULL, NULL, TRUE, 0, NULL, NULL,
                    &StartupInfo, &ProcessInfo))
{
  ::GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode);
  if (bWait)
    while (dwExitCode == STILL_ACTIVE)
    {
      // In order to allow updates of the GUI to happen while we're waiting for
      // the application to finish, we must run a mini message pump here to
      // allow messages to go through and get processed.  This message pump
      // performs much like MFC's main message pump found in CWinThread::Run().
      MSG msg;
      while (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
      {
        if (!PumpMessage())
        {
          // a termination message (e.g. WM_DESTROY)
          // was processed, so we need to stop waiting
          dwExitCode = ERROR_CANT_WAIT;
          ::PostQuitMessage(0);
          break;
        }
      }

      // let MFC do its idle processing
      LONG nIdle = 0;
      while (OnIdle(nIdle++))
        ;

      if (dwExitCode == STILL_ACTIVE) // was a termination message processed?
      {
        // no; wait for .1 second to see if the application is finished
        ::WaitForSingleObject(ProcessInfo.hProcess, 100);
        ::GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode);
      }
    }
  ::CloseHandle(ProcessInfo.hProcess);
  ::CloseHandle(ProcessInfo.hThread);
}
else
  dwExitCode = ::GetLastError();

The problem that I'm having is that, at some point, this message pump seems to free up window and menu handles on the window that I have open at the time this code is run. I did a walk through in the debugger, and at no time did it ever get into the body of the if (!PumpMessage()) statement, so I don't know what's going on here to cause the window and menu handles to go south. If I don't have the message pump, everything works fine, except that the GUI can't update itself while the wait loop is running.

Does anyone have any ideas as to how to make this work? Alternatively, I'd like to launch a worker thread to launch the second app if bWait is TRUE, but I've never done anything with threads before, so I'll need some advice on how to do it without introducing synchronization issues, etc. (Code examples would be greatly appreciated in either case.)

+1  A: 

I think your problem is in WaitForSingleObject

Looking in MSDN you see this

Use caution when calling the wait functions and code that directly or indirectly creates windows. If a thread creates any windows, it must process messages. Message broadcasts are sent to all windows in the system. A thread that uses a wait function with no time-out interval may cause the system to become deadlocked. Two examples of code that indirectly creates windows are DDE and the CoInitialize function. Therefore, if you have a thread that creates windows, use MsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx, rather than WaitForSingleObject.

In my code in the message pump use use MsgWaitForMultipleObjects (doc).

With a call this call.

MsgWaitForMultipleObjects(1, &ProcessInfo.hProcess, FALSE, 100, QS_ALLEVENTS);

This should stop your problem with the resources dissapearing.

JProgrammer
Not relevant - the MSDN quote literally states "wait function with no time-out ". The time-out in the question is 100 millisec.
MSalters
Actually, it wasn't that the resources were disappearing. As I noted in my answer post, it was just a case of stale pointers.
RobH
A: 

When you say that window and menu handles seem to be being freed, do you mean that you've got actual HWND and HMENU values that no longer seem to work, or have you got MFC CWnd* and CMenu* variables that fail?

If the latter, the problem is most likely that you're getting the CWnd* pointers by calling CWnd::FromHandle() (or CMenu::FromHandle()) somewhere (or calling something that calls them), and OnIdle() is discarding them.

The underlying reason is that MFC maintains a map from window (or menu, etc.) handles to CWnd* objects in the system. When CWnd::FromHandle() is called, it looks for a match in the map: if one is found, it's returned. If not, a new, temporary CWnd is created, added to the map, and returned. The idea behind OnIdle() is that when it's called all message processing is done, so OnIdle() discards any of these temporary CWnd objects that still exist. That's why the CWnd::FromHandle() documentation warns that the returned pointer may be temporary.

The "correct" solution to this is to not hang onto the CWnd* pointers returned from CWnd::FromHandle(). Given the simplicity of your application, it might be easier to just remove the call OnIdle(): this shouldn't have any negative effects on an installer.

Of course, this is all something of a guess, but it sounds plausible...

DavidK
As it turns out (as I noted in my answer post), it turned out that my disappearing resource problem was simply a case of pointers going stale as a result of the OnIdle processing.
RobH
Well, yes - that's what my answer said, isn't it? :)
DavidK
A: 

I've also posted this question on the Microsoft forums, and thanks to the help of one Doug Harris at Microsoft, I found out my problem with my HWND and HMENU values was, indeed due to stale CWwnd* and CMenu* pointers (obtained using GetMenu() and GetDialogItem() calls. Getting the pointers again after launching the second app solved that problem. Also, he pointed me to a web site* that showed a better way of doing my loop using MsgWaitForMultipleObjects() to control it that doesn't involve the busy work of waiting a set amount of time and polling the process for an exit code.

My loop now looks like this:

if (bWait)
{
  // In order to allow updates of the GUI to happen while we're
  // waiting for the application to finish, we must run a message
  // pump here to allow messages to go through and get processed.
  LONG  nIdleCount = 0;
  for (;;)
  {
    MSG msg;
    if (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
      PumpMessage();
    else //if (!OnIdle(nIdleCount++))
    {
      nIdleCount = 0;
      if (!PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
      {
        DWORD nRes = ::MsgWaitForMultipleObjects(1, &ProcessInfo.hProcess,
                                                 FALSE, INFINITE, QS_ALLEVENTS);
        if (nRes == WAIT_OBJECT_0)
          break;
      }
    }
  }
}
::GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode);

*That Web site, if you're curious, is: http://members.cox.net/doug_web/threads.htm

RobH