First Win32 message sent

Started by
7 comments, last by Kippesoep 18 years, 10 months ago
Up until now I believed that the first message sent to my WndProc() function was WM_NCCREATE, but it seems that it isn't (I don't know which one is, I just know that its equal to 36). Does anybody know what the very first message sent to a window procedure is? I'm making a window class with a window procedure as a method of the class. And when it tries to call the procedure, I get an access violation. Here's my code:

//Window creation
bool Window::Create()
{
	WNDCLASS WndClass;
	ZeroMemory(&WndClass, sizeof(WNDCLASS));
	WndClass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	WndClass.hCursor		= LoadCursor(0, IDC_ARROW);
	WndClass.hIcon			= LoadIcon(0, IDI_APPLICATION);
	WndClass.hInstance		= GetModuleHandle(0);
	WndClass.lpfnWndProc	= StaticWndProc;
	WndClass.lpszClassName	= "DFTLIB::Window";
	if(!RegisterClass(&WndClass))
		return false;

	m_Handle = CreateWindowEx(0, "DFTLIB::Window", m_Caption.c_str(), m_nStyle, m_iX, m_iY, m_nWidth, m_nHeight, 0, 0, GetModuleHandle(0), this);
	if(!m_Handle)
		return false;
	return true;
}


// My static window procedure
LRESULT CALLBACK Window::StaticWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	Window* pWnd = 0;
	switch(nMsg)
	{
	case WM_NCCREATE:
		{
			pWnd = (Window*)(((LPCREATESTRUCT)lParam)->lpCreateParams);
			SetWindowLong(hWnd, GWL_USERDATA, (long)pWnd);
			//pWnd is still zero here
			break;
		}
	}

	pWnd = (Window*)GetWindowLong(hWnd, GWL_USERDATA);
	WINDOWDATA WndData;
	WndData.Handle	= hWnd;
	WndData.Message	= nMsg;
	WndData.wParam	= wParam;
	WndData.lParam	= lParam;
	if(pWnd->m_HandlerFunc(&WndData))
		return 0;
	return DefWindowProc(hWnd, nMsg, wParam, lParam);
}

I use the debugger in MSVC++ 6 and I have a breakpoint in StaticWndProc. I step through it and it skips over my WM_NCCREATE case. Any help would be appreciated, thanks!
Advertisement
36 decimal is 24hex which corresponds to WM_GETMINMAXINFO. That message is sent right before any changes to window size, so it would be sent before WM_SIZE which is called when the window is shown.

I saw this in the documentation for CreateWindow() "If the WS_VISIBLE style is specified, CreateWindow sends the window all the messages required to activate and show the window. "
Just my thoughts.
Go on an Intense Rampage
Thank you very much! I've been trying to figure this out for a couple weeks now (I didn't look at CreateWindow() since I'm using CreateWindowEx()). Thanks again!

Edit:
Okay, now I'm really confused. I coded up a test class real quick and for some reason its working (when it shouldn't). It should be giving me an access violation error (I ran debugger and it says that pWnd is equal to 0x00000000), but yet it still calls the class's WndProc():
class Window{	HWND	m_Handle;public:	bool Create()	{		WNDCLASS WndClass;		ZeroMemory(&WndClass, sizeof(WNDCLASS));		WndClass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);		WndClass.hCursor		= LoadCursor(0, IDC_ARROW);		WndClass.hIcon			= LoadIcon(0, IDI_APPLICATION);		WndClass.hInstance		= GetModuleHandle(0);		WndClass.lpfnWndProc	= StaticWndProc;		WndClass.lpszClassName	= "DFTLIB_WindowClass";		if(!RegisterClass(&WndClass))		{			LogFile::LogStream("errorlog.txt", true, "Error registering the window class %s.\n", WndClass.lpszClassName);			return false;		}		m_Handle = CreateWindowEx(0, WndClass.lpszClassName, "Window Class Test", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, 0, 0, GetModuleHandle(0), this);		if(!m_Handle)		{			LogFile::LogStream("errorlog.txt", true, "Error creating the window %s.\n", "Window Class Test");			return false;		}		return true;	}	void Run()	{		MSG Msg;		ZeroMemory(&Msg, sizeof(MSG));		while(GetMessage(&Msg, 0, 0, 0))		{			TranslateMessage(&Msg);			DispatchMessage(&Msg);		}	}		LRESULT CALLBACK WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)	{		switch(nMsg)		{		case WM_KEYDOWN:			{				switch(wParam)				{				case VK_ESCAPE:					{						PostQuitMessage(0);						break;					}				}				return 0;			}		}		return DefWindowProc(hWnd, nMsg, wParam, lParam);	}	static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)	{		Window* pWnd = 0;		switch(nMsg)		{		case WM_CLOSE:		case WM_DESTROY:			{				PostQuitMessage(0);				break;			}					case WM_NCCREATE:			{				pWnd = (Window*)(((LPCREATESTRUCT)lParam)->lpCreateParams);				SetWindowLong(hWnd, GWL_USERDATA, (long)pWnd);				break;			}		}				pWnd = (Window*)GetWindowLong(hWnd, GWL_USERDATA);		return pWnd->WndProc(hWnd, nMsg, wParam, lParam);	}};int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){	Window Wnd;	Wnd.Create();	Wnd.Run();	return 0;}


How is this code possibly working?
And yes, I realize that I passed the WS_VISIBLE flag (I wanted to recreate the error first and then see if the removing the flag would fix it).

[Edited by - Programmer16 on June 3, 2005 12:57:17 AM]
Your Window::WndProc is not virtual and does not reference any variables in Window. This means that the "this" pointer (being NULL) does not get dereferenced.

Note that this behaviour is not guaranteed. It just happens that way on MSVC6 (and possibly/likely other compilers).
Kippesoep
Understanding the Order-of-Messages in the Win32 API is Very important IMHO.

I too, at one point in time, had about the same problem you're asking now. I did'nt know which messages were sent first and that really obscured me from advancing further into the API.

One very good way to figure this out is to use a File Stream, and print the messages to a '.txt' file everytime your Window Procedure recieves a Message.

A simple basic app like the one below will print the following (omitting the exit messages):

WM_CREATE
WM_SETFOCUS
WM_ERASEBKGND
WM_SIZE
WM_PAINT

As you can see, WM_CREATE is sent first. This is done when you call CreateWindow();. Then, WM_SETFOCUS is sent when you call ShowWindow(); and since the window is now visible on the screen, windows sends a WM_ERASEBKGND (to erase/paint the background with the specified background brush/color) message because the whole client area is INVALID. This is because the window was just created and nothing has yet been drawn/painted on it.

WM_ERASEBKGND message is followed by WM_SIZE. It is important that you know that WM_ERASEBKGND is usually sent before WM_SIZE and WM_PAINT. So saving the client dimensions to a static variable on WM_SIZE and using them in WM_ERASEBKGND will give you unwanted results, because, WM_SIZE has not yet been sent when you process the WM_ERASEBKGND.

Finally, WM_PAINT is sent. WM_PAINT is a low priority message, and because of this, this message is not processed untill all other messages in the message queue have been processed. Therefore, UpdateWindow is called right after ShowWindow. UpdateWindow causes the WM_PAINT message (If there's a WM_PAINT message in the message queue) to be sent IMMEDIATELY instead of untill all other messages have been processed.

Let me also emphasize that an application like this one, on a fairly decent computer, does'nt really need UpdateWindow() to be called right after ShowWindow() because today's computers can process Events lighting fast, and, you really would'nt see any delay in the client area being painted right after eraseing the client background in WM_ERASEBKGND. But, this is really not the case with more advanced, sophisticated appications, or games. An UpdateWindow() would almost certainly be crucial in this case to avoid the user see'ing a delay in painting.

I hope this has helped you. I don't know when WM_NCCREATE is sent as I have yet to deal with this particular message. But you can easily find this out with the technique I mentioned above =)

C and Win32 Code:
#include <windows.h>#include <stdio.h>TCHAR *szWinName = TEXT("3Com");LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);static int InitFile(void);FILE *fp;// pointer to a file streamint WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,				   TCHAR *szCmdLine, int iCmdShow){	static TCHAR szClassName[] = TEXT("3Com");	HWND		hwnd;	MSG			msg;	WNDCLASS	wndclass;	if ( !InitFile() ) // Initialize and open up the file for output	{		return 0;	}	wndclass.cbClsExtra		= 0;	wndclass.cbWndExtra		= 0;	wndclass.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);	wndclass.hCursor		= LoadCursor(NULL, IDC_ARROW);	wndclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);	wndclass.hInstance		= hInstance;	wndclass.lpfnWndProc	= WndProc;	wndclass.lpszClassName	= szClassName;	wndclass.lpszMenuName	= NULL;	wndclass.style			= CS_HREDRAW | CS_VREDRAW;	if ( !RegisterClass(&wndclass) )	{		MessageBox(NULL, TEXT("This Program Requires Windows NT!"),			szWinName, MB_ICONEXCLAMATION);		return 0;	}	if ( !(hwnd = CreateWindow(szClassName,		szWinName,		WS_OVERLAPPEDWINDOW,		CW_USEDEFAULT,		CW_USEDEFAULT,		CW_USEDEFAULT,		CW_USEDEFAULT,		NULL,		NULL,		hInstance,		NULL)) )	{		MessageBox(NULL, TEXT("CreateWindow() Failed!"),			szWinName, MB_ICONEXCLAMATION);		return 0;	}	ShowWindow(hwnd, iCmdShow);	UpdateWindow(hwnd);	while ( GetMessage(&msg, NULL, 0, 0) )	{		TranslateMessage(&msg);		DispatchMessage(&msg);	}	fclose(fp); // close the File stream [if you fail to close before exiting, file will not work]	return msg.message;}LRESULT CALLBACK WndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam){	HDC			hdc;	RECT		updRect;	PAINTSTRUCT	ps;	TEXTMETRIC	tm;	static int cxClient, cyClient;	static int cxChar, cxCaps, cyChar;	switch ( iMsg )	{	case WM_CREATE :		fprintf(fp, "WM_CREATE\n");		hdc = GetDC(hwnd);		GetTextMetrics(hdc, &tm);		cxChar = tm.tmAveCharWidth;		cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;		cyChar = tm.tmHeight + tm.tmExternalLeading;		ReleaseDC(hwnd, hdc);		return 0;	case WM_SETFOCUS :		fprintf(fp, "WM_SETFOCUS\n");		return 0;	case WM_ERASEBKGND :		fprintf(fp, "WM_ERASEBKGND\n");		hdc = GetDC(hwnd);		GetUpdateRect(hwnd, &updRect, FALSE);		FillRect(hdc, &updRect,			(HBRUSH)GetStockObject(WHITE_BRUSH));		ReleaseDC(hwnd, hdc);		return 1;	case WM_SIZE :		fprintf(fp, "WM_SIZE\n");		cxClient = LOWORD(lParam);		cyClient = HIWORD(lParam);		return 0;	case WM_PAINT :		fprintf(fp, "WM_PAINT\n");		hdc = BeginPaint(hwnd, &ps);		// do some drawing here...		EndPaint(hwnd, &ps);		return 0;	case WM_KILLFOCUS :		fprintf(fp, "WM_KILLFOCUS\n");		return 0;	case WM_DESTROY :		fprintf(fp, "WM_DESTROY\n");		PostQuitMessage(0);		return 0;	default :		break;	}	return DefWindowProc(hwnd, iMsg, wParam, lParam);}static int InitFile(void){	if ( !(fp = fopen("C:\\Order of Messages.TXT", "a")) )	{		MessageBox(NULL, TEXT("InitFile Failed!"),			szWinName, MB_ICONEXCLAMATION);		return 0;	}	return 1;}


relient.

[Edited by - xllx_relient_xllx on June 3, 2005 1:55:32 AM]
Quote:Original post by Kippesoep
Your Window::WndProc is not virtual and does not reference any variables in Window. This means that the "this" pointer (being NULL) does not get dereferenced.

Note that this behaviour is not guaranteed. It just happens that way on MSVC6 (and possibly/likely other compilers).


Yeah, but even so, shouldn't it be giving me an access violation since I'm calling pWnd->WndProc() and pWnd is set to 0?

Up until now I've never haven't really had any problems because I haven't really used Win32 (other than creating a window and then moving onto DirectX). I'll take your advice relient since (if I'm going to make any tools) I'll need to become more acquainted with Win32. Almost everywhere I've seen WM_NCCREATE in the docs, it says that it is sent first (thats why I couldn't figure it out).

And this is what Graham was talking about:
Quote:MSDN Documents
Before returning, CreateWindow sends a WM_CREATE message to the window procedure. For overlapped, pop-up, and child windows, CreateWindow sends WM_CREATE, WM_GETMINMAXINFO, and WM_NCCREATE messages to the window. The lParam parameter of the WM_CREATE message contains a pointer to a CREATESTRUCT structure. If the WS_VISIBLE style is specified, CreateWindow sends the window all the messages required to activate and show the window.

Thanks!
Quote:Original post by Programmer16
Yeah, but even so, shouldn't it be giving me an access violation since I'm calling pWnd->WndProc() and pWnd is set to 0?


No, in MSVC6's implementation, it shouldn't. This is because WndProc is not virtual. So there's no need to check the vtable. Basically, what happens in a "thiscall" is this:

class Window{LRESULT WndProc (HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam);}Window *w = ...;w->WndProc (...);


gets translated to:

class Window{static LRESULT WndProc (Window *this, HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam);}Window::WndProc (w, ...);


w can be NULL, as long as WndProc doesn't dereference "this" (which happens by accessing variables).

Also, if the function WndProc were virtual, the compiler wouldn't know to call Window::WndProc, because it might be a descendant of Window. That would mean checking the vtable resulting in a dereferencing of the "w" pointer.

Try making WndProc virtual and it'll crash.
Kippesoep
Ok, I get what you're saying now. Thanks!

Okay, now I have further discovered some problems. It works fine if I have the style set to WM_POPUP, but anything else crashes (it still sends 36 (WM_GETMINMAXINFO) first).

I can actually do WS_POPUP | WS_VISIBLE together and it works.

Edit:
Ok, so the WM_GETMINMAXINFO is sent first if I have a sizeable window. I don't mean to argue, but WM_DLGFRAME isn't sizeable, and WM_GETMINMAXINFO is still sent first.

Thanks for the help everybody!

[Edited by - Programmer16 on June 3, 2005 3:52:12 AM]
You get:
WM_NCCREATE, WM_NCCALCSIZE, WM_CREATE.

If your window is sizeable (it has WS_MINIMIZEBOX, WS_MAXIMIZEBOX or WS_THICKFRAME[==WS_SIZEBOX] set), you get WM_GETMINMAXINFO before that.

In the debugger, add a watch for "nMsg,wm" to see the message name.
Kippesoep

This topic is closed to new replies.

Advertisement