From: Dan Mitchell on 16 Jun 2005 18:58 I've found a very weird MFC glitch, and I'm wondering if anyone can cast any light on it. Repro: VS.Net 2003; start new MFC project, make it a dialog app. Add a handler to OnOK(): void CexittestDlg::OnBnClickedOk() { ::MessageBox(0, "foo", "bar", MB_OK); // works DestroyWindow(); // yes, I know this is bad, bear with me int rv = ::MessageBox(0, "foo", "bar", MB_OK); // doesn't int err = GetLastError(); } Now, here's the weird thing -- the first call to ::MessageBox works fine. The second one doesn't. No message box comes up; it just silently returns a value of 1 (ie not failure) and GetLastError() is 0. So it thinks it's working, but it's not. I appreciate that what I'm doing is a bit strange in this context, but in our real code we wound up deleting the 'main window' for our application, and then I spent an awful lot of time wondering why ASSERT () didn't work any more in our cleanup code; it turns out that in that case, down in the guts of ASSERT it was doing MessageBox() to notify the user about the assertion failure, that was invisibly returning IDABORT, and the ASSERT would just exit at that point. The fix for this is to set theApp.m_pMainWnd = 0 _before_ doing DestroyWindow() on it, then everything's happy. But in general, why does this happen? I can happily call ::MessageBox from a normal console app, so it doesn't need a message loop as far as I can see. It seems like somehow the MFC app is getting confused because I'm destroying its main window, and that confusion reaches out to clobber the entire rest of the application somehow. Does anyone know anything more about this? After spending a day tracking this down, I'm curious as to what's really going on.. thanks, -- dan
From: Doug Harrison [MVP] on 16 Jun 2005 20:00 On Thu, 16 Jun 2005 15:58:31 -0700, Dan Mitchell wrote: > I've found a very weird MFC glitch, and I'm wondering if anyone can > cast any light on it. > > Repro: VS.Net 2003; start new MFC project, make it a dialog app. Add a > handler to OnOK(): > > void CexittestDlg::OnBnClickedOk() > { > ::MessageBox(0, "foo", "bar", MB_OK); // works > DestroyWindow(); // yes, I know this is bad, bear with me Modal dialogs should use EndDialog. Also, you should override OnOK, which for a message handler, is very weird in that it is a virtual function. That way, you'll handle presses of the Enter key, which don't go to OnBnClicked. > int rv = ::MessageBox(0, "foo", "bar", MB_OK); // doesn't > int err = GetLastError(); > } > > Now, here's the weird thing -- the first call to ::MessageBox works > fine. > > The second one doesn't. No message box comes up; it just silently > returns a value of 1 (ie not failure) and GetLastError() is 0. So it > thinks it's working, but it's not. > > I appreciate that what I'm doing is a bit strange in this context, but > in our real code we wound up deleting the 'main window' for our > application, and then I spent an awful lot of time wondering why ASSERT > () didn't work any more in our cleanup code; it turns out that in that > case, down in the guts of ASSERT it was doing MessageBox() to notify the > user about the assertion failure, that was invisibly returning IDABORT, > and the ASSERT would just exit at that point. > > > The fix for this is to set theApp.m_pMainWnd = 0 _before_ doing > DestroyWindow() on it, then everything's happy. > > > But in general, why does this happen? I can happily call ::MessageBox > from a normal console app, so it doesn't need a message loop as far as I > can see. It seems like somehow the MFC app is getting confused because > I'm destroying its main window, and that confusion reaches out to > clobber the entire rest of the application somehow. > > Does anyone know anything more about this? After spending a day > tracking this down, I'm curious as to what's really going on.. Your usage is somewhat unusual, but this message describes your problem as it more commonly occurs: The problem is that when an MFC app's main window is destroyed, MFC notices this and posts WM_QUIT. The MessageBox function runs a message loop, retrieves WM_QUIT, and returns immediately. There are a couple of ways around this. The usual AppWizard-generated code is: CMyDlg dlg; m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); If you delete the line which assigns to m_pMainWnd, MFC won't post WM_QUIT (and set m_pMainWnd to zero) when the dialog is destroyed. However, it's nice to have a main window, so I leave it alone and make a call to eat the WM_QUIT following DoModal: MSG msg; PeekMessage(&msg,0,WM_QUIT,WM_ýýQUIT,PM_REMOVE); Now you can call MessageBox. See this KB article for another approach: PRB: Windows Flash and Disappear in Dialog-Based Applications http://support.microsoft.com/default.aspx?scid=kb;en-us;138681 -- Doug Harrison Microsoft MVP - Visual C++
From: Joseph M. Newcomer on 16 Jun 2005 21:53 By the time your second MessageBox occurs, the window has been destroyed. This probably causes the message pump to exit. There is special code after this that stops other windows from coming up. Don't do DestroyWindow where you do it. That is begging for catastrophe. Do not expect anything to work after the DestroyWindow (since this appears to be a main dialog). Do not delete the main window for your app like this. That would be erroneous programming. If you are deleting the main window, you have committed a serious breach of programming style. Do not at all be surprised if anything collapses into a mess after that. Don't do it. If you are closing your main window using this technique, your program is incorrect. There is no reason to expect an erroneous program should exhibit anything resembling sensible behavior under any conditions. If you want to exit a dialog-based program, call CDialog::OnOK(). Or maybe OnCancel. Do not use any other method for terminating the program. Certainly nothing that either deletes the main window or calls DestroyWindow. These would simply be incorrect. joe On Thu, 16 Jun 2005 15:58:31 -0700, Dan Mitchell <djmitchella(a)yahoo.com> wrote: > I've found a very weird MFC glitch, and I'm wondering if anyone can >cast any light on it. > > Repro: VS.Net 2003; start new MFC project, make it a dialog app. Add a >handler to OnOK(): > >void CexittestDlg::OnBnClickedOk() >{ > ::MessageBox(0, "foo", "bar", MB_OK); // works > DestroyWindow(); // yes, I know this is bad, bear with me > int rv = ::MessageBox(0, "foo", "bar", MB_OK); // doesn't > int err = GetLastError(); >} > > Now, here's the weird thing -- the first call to ::MessageBox works >fine. > > The second one doesn't. No message box comes up; it just silently >returns a value of 1 (ie not failure) and GetLastError() is 0. So it >thinks it's working, but it's not. > > I appreciate that what I'm doing is a bit strange in this context, but >in our real code we wound up deleting the 'main window' for our >application, and then I spent an awful lot of time wondering why ASSERT >() didn't work any more in our cleanup code; it turns out that in that >case, down in the guts of ASSERT it was doing MessageBox() to notify the >user about the assertion failure, that was invisibly returning IDABORT, >and the ASSERT would just exit at that point. > > > The fix for this is to set theApp.m_pMainWnd = 0 _before_ doing >DestroyWindow() on it, then everything's happy. > > > But in general, why does this happen? I can happily call ::MessageBox >from a normal console app, so it doesn't need a message loop as far as I >can see. It seems like somehow the MFC app is getting confused because >I'm destroying its main window, and that confusion reaches out to >clobber the entire rest of the application somehow. > > Does anyone know anything more about this? After spending a day >tracking this down, I'm curious as to what's really going on.. > > thanks, > > -- dan Joseph M. Newcomer [MVP] email: newcomer(a)flounder.com Web: http://www.flounder.com MVP Tips: http://www.flounder.com/mvp_tips.htm
From: Dan Mitchell on 17 Jun 2005 11:33 Joseph M. Newcomer <newcomer(a)flounder.com> wrote in news:nva4b15jc6ve5h00su82g80kaid54cj7ag(a)4ax.com: > Do not delete the main window for your app like this. That would be > erroneous programming. If you are deleting the main window, you have > committed a serious breach of programming style. Do not at all be > surprised if anything collapses into a mess after that. Don't do it. > If you are closing your main window using this technique, your program > is incorrect. There is no reason to expect an erroneous program should > exhibit anything resembling sensible behavior under any conditions. Fair enough; I'd suspected something like this was the case. > If you want to exit a dialog-based program, call CDialog::OnOK(). Or > maybe OnCancel. Do not use any other method for terminating the > program. Certainly nothing that either deletes the main window or > calls DestroyWindow. These would simply be incorrect. The actual code we have running is not a dialog app, that was just the easiest way to demonstrate the problem -- but if we're not meant to destroy the main window of our app, then I guess I have a different question: Is it legal to _change_ the main window in an MFC app? The basic structure we need is that the program launches, a dialog comes up with a list box in there; if the user hits 'cancel', the app exits, but if they select from the list box and hit 'ok', that dialog goes away and our main app window comes up. To add to the fun, further down the line, we may end up switching the main app window back and forth between the normal one and a different window. All of the 'main' windows after the startup dialog are just standard CWnd, we aren't doing an MFC document/view application, but we want the MFC structure there to make our coding life easier. Is there a good way to do this? We used to have things structured so the main dialog hangs around through all of execution and is invisible when we're done with it, but then we got weird problems where if you minimised the app, it wouldn't restore properly (the WM_RESTORE message got routed somewhere peculiar), and general strangenesses with the taskbar entry not doing what we'd expected. If there's a good way to have MFC around to help with message handling and such, but not have to worry about messing with m_pMainWnd, I'd love to know what it is, because we know that the way we're doing things is a bit sketchy -- it seems to work, but if there's a generally better approach, that would be great. thanks, -- dan
From: Dan Mitchell on 17 Jun 2005 11:52
"Doug Harrison [MVP]" <dsh(a)mvps.org> wrote in news:1x0w900rf4qxr$.1sovb04tf1ha2$.dlg(a)40tude.net: > The problem is that when an MFC app's main window is destroyed, MFC > notices this and posts WM_QUIT. The MessageBox function runs a message > loop, retrieves WM_QUIT, and returns immediately. Ah, that would explain it. We're doing PostQuitMessage further on when all our cleanup is done so that app will exit but I guess if the main window is gone, that's not getting us anything. > There are a couple > of ways around this. The usual AppWizard-generated code is: > > CMyDlg dlg; > m_pMainWnd = &dlg; > int nResponse = dlg.DoModal(); > > If you delete the line which assigns to m_pMainWnd, MFC won't post > WM_QUIT (and set m_pMainWnd to zero) when the dialog is destroyed. > However, it's nice to have a main window, What will happen to MFC if we _don't_ have a main window? If we could just entirely ignore m_pMainWnd (more details in my other post) then we could bounce our app's "main" window around between things much more easily, and I'd have less of a nervous sense that we're doing things MFC isn't expecting us to do. Presumably things like calling AfxGetMainWnd() will fail, so we'd have to replace that with code that tracks our own "main window", but is there any part of the innards of MFC that'll get confused? Poking through the MFC source, there's a lot of bits of code that use m_pMainWnd (ah, and there's the call to AfxPostQuitMessage when the main window goes away) but I'm not really clear what I'm looking at; it seems we'd have to provide our own message loop rather than calling CWinApp::Run(), things like CWinApp::HideApplication() won't work, and we lose some support for frame windows/document-view architecture/default File menu, etc, but none of those are a problem for us. > [snip] > PRB: Windows Flash and Disappear in Dialog-Based Applications > http://support.microsoft.com/default.aspx?scid=kb;en-us;138681 That's essentially what we're doing now, we're telling the app that m_pMainWnd is 0 before we destroy that window, and from that point on we're in our cleanup/shutdown code so we don't mind that there's no main window. If that's a valid solution, then we can stick with it, but if there's a way to entirely avoid m_pMainWnd that would be nicer. thanks, -- dan |