Here are my interpretations of correct usages of the Window Procedure with regard to the handling of messages, return values, and calling DefaultWindowProc(). I'll present three interpretations of the same example which handles WM_CREATE, WM_PAINT, and WM_DESTROY. The examples open an "Output.txt" log file which provides information on which procedures or messages are being called/handled. First would be this...
Code:
// cl MegaFiddle4.cpp /O1 /Os /GS- /link kernel32.lib user32.lib gdi32.lib
#define UNICODE // 64,000 Bytes With C Standard Library Loaded (LIBCMT.LIB)
#define _UNICODE
#include <windows.h>
#include <cstdio>
#include <tchar.h>
FILE* fp=NULL;
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
CREATESTRUCT* pCreateStruct=NULL;
fp=fopen("Output.txt","w");
fprintf(fp,"Entering fnWndProc() : case WM_CREATE\n");
pCreateStruct = (CREATESTRUCT*)(lParam);
fprintf(fp," pCreateStruct->cx = %d\n",pCreateStruct->cx);
fprintf(fp," pCreateStruct->cy = %d\n",pCreateStruct->cy);
fprintf(fp," hwnd = %p\n",hwnd);
fprintf(fp,"Leaving fnWndProc() : case WM_CREATE\n\n");
return 0;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC=NULL;
fprintf(fp,"Entering fnWndProc() : case WM_PAINT\n");
hDC=BeginPaint(hwnd,&ps);
fprintf(fp," hwnd = %p\n",hwnd);
fprintf(fp," ps.rcPaint.right = %d\n",ps.rcPaint.right);
fprintf(fp," ps.rcPaint.bottom = %d\n",ps.rcPaint.bottom);
fprintf(fp,"Leaving fnWndProc() : case WM_PAINT\n\n");
EndPaint(hwnd,&ps);
return 0;
}
case WM_DESTROY:
{
fprintf(fp,"Entering fnWndProc() : case WM_DESTROY\n");
fprintf(fp," hwnd = %p\n",hwnd);
PostQuitMessage(0);
fprintf(fp,"Leaving fnWndProc() : case WM_DESTROY\n\n");
fclose(fp);
return 0;
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPTSTR lpszArgument, int iShow)
{
WNDCLASSEX wc={0};
MSG messages;
HWND hWnd;
wc.lpszClassName = _T("Form1");
wc.lpfnWndProc = fnWndProc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,_T("Form1"),_T("Form1"),WS_OVERLAPPEDWINDOW|WS_VISIBLE,200,100,325,300,HWND_DESKTOP,0,hInstance,0);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
#if 0
Entering fnWndProc() : case WM_CREATE
pCreateStruct->cx = 325
pCreateStruct->cy = 300
hwnd = 00000000000904B2
Leaving fnWndProc() : case WM_CREATE
Entering fnWndProc() : case WM_PAINT
hwnd = 00000000000904B2
ps.rcPaint.right = 309
ps.rcPaint.bottom = 262
Leaving fnWndProc() : case WM_PAINT
Entering fnWndProc() : case WM_DESTROY
hwnd = 00000000000904B2
Leaving fnWndProc() : case WM_DESTROY
#endif
The way the above Window Procedure works is that a switch logic construct is looking for WM_CREATE, WM_PAINT, or WM_DESTROY. There is no default clause, and if the message isn't one of the above no code within the switch construct runs, and the message gets passed on to DefaultWindowProc() through the return statement of the procedure. If one of those messages is intercepted message handling code for that message is run and the procedure exits with a return of zero. If I'm going to use a switch logic construct to map messages to the code which I want to run for that particular message, that's the way I do it.
There is another way to do it however, which I seem to see more of than what I do above. The alternate way goes something like so for the same example...
Code:
// cl MegaFiddle5.cpp /O1 /Os /GS- /link kernel32.lib user32.lib gdi32.lib
#define UNICODE // 64,000 Bytes With C Standard Library Loaded (LIBCMT.LIB)
#define _UNICODE
#include <windows.h>
#include <cstdio>
#include <tchar.h>
FILE* fp=NULL;
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
CREATESTRUCT* pCreateStruct=NULL;
fp=fopen("Output.txt","w");
fprintf(fp,"Entering fnWndProc() : case WM_CREATE\n");
pCreateStruct = (CREATESTRUCT*)(lParam);
fprintf(fp," pCreateStruct->cx = %d\n",pCreateStruct->cx);
fprintf(fp," pCreateStruct->cy = %d\n",pCreateStruct->cy);
fprintf(fp," hwnd = %p\n",hwnd);
fprintf(fp,"Leaving fnWndProc() : case WM_CREATE\n\n");
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC=NULL;
fprintf(fp,"Entering fnWndProc() : case WM_PAINT\n");
hDC=BeginPaint(hwnd,&ps);
fprintf(fp," hwnd = %p\n",hwnd);
fprintf(fp," ps.rcPaint.right = %d\n",ps.rcPaint.right);
fprintf(fp," ps.rcPaint.bottom = %d\n",ps.rcPaint.bottom);
fprintf(fp,"Leaving fnWndProc() : case WM_PAINT\n\n");
EndPaint(hwnd,&ps);
break;
}
case WM_DESTROY:
{
fprintf(fp,"Entering fnWndProc() : case WM_DESTROY\n");
fprintf(fp," hwnd = %p\n",hwnd);
PostQuitMessage(0);
fprintf(fp,"Leaving fnWndProc() : case WM_DESTROY\n\n");
fclose(fp);
break;
}
default:
{
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
}
return (0);
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPTSTR lpszArgument, int iShow)
{
WNDCLASSEX wc={0};
MSG messages;
HWND hWnd;
wc.lpszClassName = _T("Form1");
wc.lpfnWndProc = fnWndProc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,_T("Form1"),_T("Form1"),WS_OVERLAPPEDWINDOW|WS_VISIBLE,200,100,325,300,HWND_DESKTOP,0,hInstance,0);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
#if 0
Entering fnWndProc() : case WM_CREATE
pCreateStruct->cx = 325
pCreateStruct->cy = 300
hwnd = 00000000000904B2
Leaving fnWndProc() : case WM_CREATE
Entering fnWndProc() : case WM_PAINT
hwnd = 00000000000904B2
ps.rcPaint.right = 309
ps.rcPaint.bottom = 262
Leaving fnWndProc() : case WM_PAINT
Entering fnWndProc() : case WM_DESTROY
hwnd = 00000000000904B2
Leaving fnWndProc() : case WM_DESTROY
#endif
Where this one differs from the first is that there is a default: clause to the switch construct, and if the message received isn't WM_CREATE, WM_PAINT, or WM_DESTROY, then the default is executed and DefaultWindowProc() is called in the return statement within the switch construct. If the message is one of the above the code for that message executes, and then a break statement is used to exit the switch. In that case execution proceeds to the return statement of the procedure, where a zero is returned. Note that there are unusual messages such as WM_CTLCOLORSTATIC, and others, where something other than zero is returned to indicate that the message has been handled. In such cases a return statement would be needed within the switch to return the correct entity. For reasons such as this, if I'm going to use the switch construct to map messages to message handling code, I use my first shown technique over this one, as at least in my mind it is clearer.
However, except for posting example code such as the above in various Windows Programming Forums, I don't use the switch construct at all to map incoming messages to the code that handles a message. I use rather a for loop construct to do this and I use separate message/event handling functions. To folks just starting out I'm sure it looks much more complicated, but it really is the best way to do it. Here is what I do.
A message such as WM_CREATE is an integral value (one (1) in the case of WM_CREATE), and if one were to create a separate message handling function to handle the WM_CREATE message such as this...
Code:
LRESULT fnWndProc_OnCreate(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
CREATESTRUCT* pCreateStruct=NULL;
fp=fopen("Output.txt","w");
fprintf(fp,"Entering fnWndProc_OnCreate()\n");
pCreateStruct = (CREATESTRUCT*)(lParam);
fprintf(fp," WM_CREATE = %d\n",WM_CREATE);
fprintf(fp," pCreateStruct->cx = %d\n",pCreateStruct->cx);
fprintf(fp," pCreateStruct->cy = %d\n",pCreateStruct->cy);
fprintf(fp," hwnd = %p\n",hwnd);
fprintf(fp,"Leaving fnWndProc_OnCreate()\n\n");
return 0;
}
...then the virtual address of that function represented by the symbol fnWndProc_OnCreate is also an integral value similar in every way to the WNDCLASS::lpfnWndProc as set down usually in WinMain. So since these are both integral values, i.e., an equate/define such as WM_CREATE, and the virtual address of a message handling function, then they can be combined into a struct like so...
Code:
struct EVENTHANDLER
{
unsigned int iMsg;
LRESULT (*fnPtr)(HWND, unsigned int, WPARAM, LPARAM);
};
So what you have in that struct/object is a relationship between the const value of a message as defined in the headers, and the runtime address of the event/message handling function that handles that message. Next step would be to create an array of these EVENTHANDLER objects like so...
Code:
LRESULT fnWndProc_OnCreate (HWND, unsigned int, WPARAM, LPARAM);
LRESULT fnWndProc_OnPaint (HWND, unsigned int, WPARAM, LPARAM);
LRESULT fnWndProc_OnDestroy (HWND, unsigned int, WPARAM, LPARAM);
const EVENTHANDLER MainEventHandler[] =
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_PAINT, fnWndProc_OnPaint},
{WM_DESTROY, fnWndProc_OnDestroy}
};
Constructed like so, a Window Procedure will never amount to more than this, no matter how many messages are handled, and no matter how many thousands of lines of code might be involved in any message handling function...
Code:
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
for(size_t i=0; i<dim(MainEventHandler) ;i++)
{
if(MainEventHandler[i].iMsg==msg)
return (*MainEventHandler[i].fnPtr)(hwnd, msg, wParam, lParam);
}
return (DefWindowProc(hwnd,msg,wParam,lParam));
}
...where the dim macro is defined like so...
Code:
#define dim(x) (sizeof(x) / sizeof(x[0]))
So what the above code is doing is using a for loop to iterate through the array of EVENTHANDLER objects trying to make a match on the EVENTHANDLER::iMsg member. If a match is made the event handling function is called through the function pointer member EVENTHANDLER::fnPtr(). The only refinement I've made to the above is I amalgamate the parameters to the Window Procedure into a WndEventArgs object like so...
Code:
struct WndEventArgs
{
HWND hwnd;
HINSTANCE hInst;
WPARAM wParam;
LPARAM lParam;
};
....and my Window Procedures look like this...
Code:
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
for(size_t i=0; i<dim(MainEventHandler) ;i++)
{
if(MainEventHandler[i].iMsg==msg)
{
WndEventArgs Wea;
Wea.hwnd=hwnd,Wea.lParam=lParam,Wea.wParam=wParam;
return (*MainEventHandler[i].fnPtr)(Wea);
}
}
return (DefWindowProc(hwnd,msg,wParam,lParam));
}
This isn't really too much different from what all the various Class Frameworks do. Only I do it a bit more efficiently, I think. For example, in .NET the OnPaint message handling function has an arguement named PaintEventArgs. And the whole .NET concept of 'delegates' is roughly analogous to what I've done above with my function pointer setup. Anyway, here is my third iteration of the full program above illustrating this third technique...
Code:
// MegaFiddle6.h
#ifndef MegaFiddle6_h
#define MegaFiddle6_h
#define dim(x) (sizeof(x) / sizeof(x[0]))
struct WndEventArgs
{
HWND hwnd;
HINSTANCE hInst;
WPARAM wParam;
LPARAM lParam;
};
struct EVENTHANDLER
{
unsigned int iMsg;
LRESULT (*fnPtr)(WndEventArgs&);
};
LRESULT fnWndProc_OnCreate (WndEventArgs& wea);
LRESULT fnWndProc_OnPaint (WndEventArgs& wea);
LRESULT fnWndProc_OnDestroy (WndEventArgs& wea);
const EVENTHANDLER MainEventHandler[] =
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_PAINT, fnWndProc_OnPaint},
{WM_DESTROY, fnWndProc_OnDestroy}
};
#endif
Code:
// cl MegaFiddle6.cpp /O1 /Os /GS- /link kernel32.lib user32.lib gdi32.lib
#define UNICODE // 64,000 Bytes With C Standard Library Loaded (LIBCMT.LIB)
#define _UNICODE
#include <windows.h>
#include <cstdio>
#include <tchar.h>
#include "MegaFiddle6.h" // Note Include <<< !!!
FILE* fp=NULL;
LRESULT fnWndProc_OnCreate(WndEventArgs& wea)
{
CREATESTRUCT* pCreateStruct=NULL;
fp=fopen("Output.txt","w");
fprintf(fp,"Entering fnWndProc_OnCreate()\n");
pCreateStruct = (CREATESTRUCT*)(wea.lParam);
fprintf(fp," WM_CREATE = %d\n",WM_CREATE);
fprintf(fp," pCreateStruct->cx = %d\n",pCreateStruct->cx);
fprintf(fp," pCreateStruct->cy = %d\n",pCreateStruct->cy);
fprintf(fp," wea.hwnd = %p\n",wea.hwnd);
fprintf(fp,"Leaving fnWndProc_OnCreate()\n\n");
return 0;
}
LRESULT fnWndProc_OnPaint(WndEventArgs& wea)
{
PAINTSTRUCT ps;
HDC hDC=NULL;
fprintf(fp,"Entering fnWndProc_OnPaint()\n");
hDC=BeginPaint(wea.hwnd,&ps);
fprintf(fp," wea.hwnd = %p\n",wea.hwnd);
fprintf(fp," ps.rcPaint.right = %d\n",ps.rcPaint.right);
fprintf(fp," ps.rcPaint.bottom = %d\n",ps.rcPaint.bottom);
fprintf(fp,"Leaving fnWndProc_OnPaint()\n\n");
EndPaint(wea.hwnd,&ps);
return 0;
}
LRESULT fnWndProc_OnDestroy(WndEventArgs& wea)
{
fprintf(fp,"Entering fnWndProc_OnDestroy()\n");
fprintf(fp," wea.hwnd = %p\n",wea.hwnd);
PostQuitMessage(0);
fprintf(fp,"Leaving fnWndProc_OnDestroy()\n\n");
fclose(fp);
return 0;
}
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
for(size_t i=0; i<dim(MainEventHandler) ;i++)
{
if(MainEventHandler[i].iMsg==msg)
{
WndEventArgs Wea;
Wea.hwnd=hwnd,Wea.lParam=lParam,Wea.wParam=wParam;
return (*MainEventHandler[i].fnPtr)(Wea);
}
}
return (DefWindowProc(hwnd,msg,wParam,lParam));
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPTSTR lpszArgument, int iShow)
{
WNDCLASSEX wc={0};
MSG messages;
HWND hWnd;
wc.lpszClassName = _T("Form1");
wc.lpfnWndProc = fnWndProc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
RegisterClassEx(&wc);
hWnd=CreateWindowEx(0,_T("Form1"),_T("Form1"),WS_OVERLAPPEDWINDOW|WS_VISIBLE,200,100,325,300,HWND_DESKTOP,0,hInstance,0);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
#if 0
Entering fnWndProc_OnCreate()
pCreateStruct->cx = 325
pCreateStruct->cy = 300
wea.hwnd = 00000000001F01D6
Leaving fnWndProc_OnCreate()
Entering fnWndProc_OnPaint()
wea.hwnd = 00000000001F01D6
ps.rcPaint.right = 309
ps.rcPaint.bottom = 262
Leaving fnWndProc_OnPaint()
Entering fnWndProc_OnDestroy()
wea.hwnd = 00000000001F01D6
Leaving fnWndProc_OnDestroy()
#endif
And if you look at the way the abbreviated Window Procedure works you'll see its functioning exactly like the first instance of the program in terms of the calling of DefaultWindowProc() and returning the correct number from handled messages.