From: Dan Mitchell on
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
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
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
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
"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