Fog Creek Software
Discussion Board




This app won't die

Going back to Win32, I quickly spat out code to get a simple window application. It all works fine, compiles perfectly and I am able to build an EXE but for some reason, the EXE does not stop running even after I close the window whether from the control box or from the system menu. It keeps running as a process. I am processing WM_DESTROY but I think GetMessage still does not return 0 even after hearing from PostQuitMessage. Could you please tell me what is it that I am forgetting? I know its going to be some foolish thing.

PS: I also noted some changes from VC++ 5.0 to VC++ 6 IDE. Back then, it was simpler to select an Application type workspace and you'd get a project workspace for Win32. Now, its a little more confusing. Anyway, here's my simple code, the most cliche' code on Win32. I guess you'll have to spot my fault in either the message loop in WinMain or in the WndProc message handler, though I don't have any messages there but for WM_DESTROY.

[CODE]

[FILE]
WinMain.c
[/FILE]
#include "StdAfx.h"

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);


WNDCLASS wc;
HWND hWnd;
MSG msg;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow)
{

    int nWidth,nHeight;
    int iRetVal;
    
    nWidth=0;
    
    nWidth=GetSystemMetrics(SM_CXSCREEN);
    nHeight=GetSystemMetrics(SM_CYSCREEN);
    
    if (hPrevInstance==0)
    {
        wc.cbClsExtra=0;
        wc.cbWndExtra=0;
        wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
        wc.hCursor=LoadCursor(NULL,IDC_ARROW);
        wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
        wc.hInstance=hInstance;
        wc.lpfnWndProc=(WNDPROC)WndProc;
        wc.lpszClassName="SampleWin32";
        wc.lpszMenuName=0;
        wc.style=CS_HREDRAW|CS_VREDRAW;
    }
    
    iRetVal =RegisterClass(&wc);

    if (iRetVal==0)
    {
        MessageBox(hWnd,"Could not register WNDCLASS","Quitting",MB_OK);
    }

    hWnd = CreateWindow("SampleWin32","A Sample Win32 Application",WS_OVERLAPPEDWINDOW,0,0,
        nWidth,nHeight,0,NULL,hInstance,NULL);

    ShowWindow(hWnd,nCmdShow);
    UpdateWindow(hWnd);

    while(GetMessage(&msg,hWnd,0,0) != 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (msg.wParam);

} //WinMain



[FILE]
WndProc.c
[/FILE]
#include "StdAfx.h"

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    switch(msg)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hWnd,msg,wParam,lParam));
    }

    return 0;

} //WndProc

[/CODE]

Sathyaish Chakravarthy
Saturday, December 06, 2003

Change:
while(GetMessage(&msg,hWnd,0,0) != 0)

To:
while(GetMessage(&msg,NULL,0,0) != 0)

Check the API documentation to see why.

C# fan
Saturday, December 06, 2003

Wow! Thanks, man!

But I read the documentation and could not relate what you said with it. What's the reason?

Sathyaish Chakravarthy
Saturday, December 06, 2003

C# fan  : WHY ?

Back In The Future
Saturday, December 06, 2003

API Documentation:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/messagesandmessagequeues/messagesandmessagequeuesreference/messagesandmessagequeuesfunctions/getmessage.asp

Beware the power of my stinky feet!
Saturday, December 06, 2003

I still don't get it. I've read it a couple of times now. Its the same that I read from my older version offline MSDN. If you're talking about this line,

<QUOTE FROM MSDN>
Warning 
Because the return value can be nonzero, zero, or -1, avoid code like this:

while (GetMessage( lpMsg, hWnd, 0, 0)) ...
The possibility of a -1 return value means that such code can lead to fatal application errors. Instead, use code like this:

BOOL bRet;

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
    if (bRet == -1)

blah..blah..
</QUOTE FROM MSDN>

then that's not it. Here, the guy says don't test for a boolean return value from GetMessage because of the possibility of the window handle being invalid leading to a negative return value. Instead, go for an explicit comparison with the comparison operators.

Sathyaish Chakravarthy
Saturday, December 06, 2003

There's no HWND passed as a parameter to the PostQuitMessage function ... so, presumably the WM_QUIT message arrives with NULL == hWnd.

Christopher Wells
Saturday, December 06, 2003

The way I understand it, and of course I could be wrong, is that the WM_QUIT message is not associated with an HWND, like other messages are, rather it is associated with a thread. 

Thus when you call GetMessage( &msg, hWnd, 0, 0 ) you are in effect saying "Get the messages from the message queue that are being sent to the window whose handle is hWnd." and of course this window never recieves the WM_QUIT message becuase you just told GetMessage only to retrieve messages associated with the window you just created and ignore messages directed to the thread.

OTOH, Calling GetMessage ( &msg, NULL, 0, 0 ) says "Get the messages from the message queue that are directed to my window and to my thread."  The WM_QUIT message is directed to the thread.

Because the WM_QUIT message is not associated with a window handle you cannot process it with a window procedure.  Thus you have to process the WM_DESTROY message and then tell the thread that you want to quit by "posting a quit message" i.e. PostQuitMessage(0).

I'm not an expert at this kind of stuff, and I could be wrong.

Dave B.
Saturday, December 06, 2003

The problem is the PostQuitMessage(0) is on WM_DESTROY switch. When that switch is executated the original hWnd is invalid (it was destroyed).

So you have to use NULL on GetMessage.

Another fix to the original program is to use the PostQuitMessage on the WM_CLOSE switch. There, hWnd is still valid.

C# fan
Sunday, December 07, 2003

I disagree C#.  HWND is still valid in the WM_DESTROY message.  It is NOT valid in a WM_QUIT message, which can't be processed in a window proc.  You can still reference HWND in WM_DESTROY before you return 0.  The window has simply been removed from the screen, but all windows (child included) still exist.


Sunday, December 07, 2003

"    ", you are right. My "fix" using WM_CLOSE works, but the reason isn't the one I thinked.

So, I dont know why.

Raymond Chen (  http://software.ericsink.com/index.html ) is the man to solve this ;)

C# fan
Sunday, December 07, 2003

Ooops, wrong URL. The correcy is http://blogs.gotdotnet.com/raymondc/

C# fan
Sunday, December 07, 2003

GetMessage returns -1 if you pass it an invalid HWND (other than NULL) and hWnd becomes invalid after WM_DESTROY so that's why you're not getting WM_QUIT. Passing NULL instead of hWnd is the only fix I would endorse.

asdf
Sunday, December 07, 2003

Once I changed

GetMessage(&msg, hWnd, 0, 0)

with

GetMessage(&msg, NULL, 0, 0)

the program ran fine and exited when the X was pushed. The reason - as has already been noted - is that the WM_QUIT message is not associated with a window (notice that there is no HWND parameter to PostQuitMessage) so if you use a filtered message loop you will never see it.

I can't think of a case where you would want to use a filtered GetMessage. It assumes that there is only one window on your thread - no child windows, etc. On Far East systems this is almost never the case (there are system windows for things like input method editing).

Raymond Chen
Sunday, December 07, 2003

Take a look at the "PostThreadMessage" function.

Though I don't recommend it for daily use try replacing "PostQuitMessage(0);" in the WM_DESTROY case with:

PostThreadMessage(GetCurrentThreadId(), WM_QUIT, 0, 0);

Neato!

Dave B.
Sunday, December 07, 2003

OTOH, don't try that (unless you like experimenting)... it'll mess up your system.  It doesn't actually work like I thought it would.

Dave B.
Sunday, December 07, 2003

Err....

Ok it does work, I was still filtering messages through GetMessage and didn't replace hWnd with NULL.

PostQuitMessage must be a Macro/Function for the special case of WM_QUIT and PostThreadMessage.

Dave B.
Sunday, December 07, 2003

Maybe a more proper way to write it:

long iRet = 0;
do {
  iRet = PostThreadMessage(GetCurrentThreadId(), WM_QUIT, 0, 0);
  Sleep(100);
} while (0 == iRet);

Replace the wParam argument of PostThreadMessage with the return value you want.

Dave B.
Sunday, December 07, 2003

*  Recent Topics

*  Fog Creek Home