Weird bug with WndProc and DispatchMessage. Member Functions don't exist? - c++

So a bit of a weird bug going on when getting a WM_KEYDOWN msg in my Window class.
I have a Global WndProc function that determines which window instance it is and sends the message to it's own local WndProc function.
//Every Windows Message will hit this function.
LRESULT CALLBACK GlobalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
//Get the Window Instance from the userdata
Window* targetWindow = (Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
//If no window exists, then it must be the first time so we should set it
if (!targetWindow) {
//First let's try and extract the Window instance pointer from the lparam
CREATESTRUCT* createStruct = (CREATESTRUCT*)lparam;
if (createStruct) {
targetWindow = (Window*)createStruct->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)targetWindow);
}
else {
//It was some other message which we just can't deal with right now
return DefWindowProc(hwnd, msg, wparam, lparam);
}
}
//Now we can pipe it to the Window's local wnd proc function
return targetWindow->LocalWndProc(hwnd, msg, wparam, lparam);
}
And my local wndproc looks like this:
LRESULT CALLBACK Window::LocalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
switch (msg) {
case WM_KEYDOWN:
ToggleFullScreen(!m_fullScreen);
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
case WM_CLOSE:
PostQuitMessage(0);
return 0;
break;
default:
return DefWindowProc(hwnd, msg, wparam, lparam);
}
}
So it's pretty simple at this point. If any key is pressed, the window should call its member function ToggleFullScreen.
Now for some reason when i run this and I hit any key on the keyboard, I get an exception fault:
Unhandled exception at 0x77c015ee in Athena_Debug.exe: 0xC0000005: Access violation reading location 0x0000012d.
With CallStack:
ntdll.dll!77c015ee()
ntdll.dll!77bf015e()
user32.dll!7588788a()
Athena_Debug.exe!Athena::Window::HandleWindowsMessage() Line 195 + 0xc bytes C++
Athena_Debug.exe!Athena::AthenaCore::StartEngine() Line 96 + 0x12 bytes C++
Athena_Debug.exe!WinMain(HINSTANCE__ * hInstance, HINSTANCE__ * hPrevInstance, char * lpCmdLine, int nCmdShow) Line 44 C++
Athena_Debug.exe!__tmainCRTStartup() Line 547 + 0x2c bytes C
Athena_Debug.exe!WinMainCRTStartup() Line 371 C
kernel32.dll!7702339a()
The line it actually breaks on is the DispatchMessage(&msg) line located here:
MSG Window::HandleWindowsMessage() {
MSG msg;
ZeroMemory(&msg, sizeof(MSG));
if (m_realTime) {
PeekMessage(&msg, m_hwnd, 0, 0, PM_REMOVE);
}
else {
GetMessage(&msg, m_hwnd, 0, 0);
}
if (msg.message != WM_NULL) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg;
}
So the interesting thing is that if I comment out ToggleFullScreen and instead put an OutputDebugTrace there it works just fine and outputs the debug trace. It appears like it can't resolve the function for an instance? If I comment everything out in ToggleFullScreen, it still crashes. If i create a brand new function that does nothing and call it in response to WM_KEYDOWN it still errors.
Anyone have any idea why this is happening or some ideas on how I can go about fixing it?
Thanks!

I would be guessing that you have wrong pointer obntained from GetWindowLongPtr, as it was not set by SetWindowLongPtr. As I wrote in my comment, you should not test if it's empty (I have no idea what is there by default but i wouldn't assume anything.), but set it when you recieve WM_CRERATE (which is guaranteed to be the first message sent to a window). Have you tested if SetWindowLongPtr is acctually called?
Other messages are not dependent on any contents of Window class, so they are working well with wrong this pointer - it is an error to call method for wrong pointer, but if method is not actually using it, it will still work well.
EDIT:
Another possbile cause: Why are you not using result value from PeekMessage? i see that you are testing for WM_NULL, which should be 0 at this point (you are zeroing memory), but PeekMessage does not guarantee that it's not touching MSG structure even if it's not retrieving anything. Using PeekMessage result should be used at all times. Plus zeroing memory is rather inefficient at this point.

The first message does not have to be WM_NCCREATE (or WM_CREATE), you have to be prepared to get WM_SIZE or WM_GETMINMAXINFO (and probably other messages) before any create message!
In your code you would change if (createStruct) { to if (WM_NCCREATE==msg && createStruct) {...
You can see this in Raymond Chen's scratch program.

Your WindowProc callback should look more like this:
//Every Windows Message will hit this function.
LRESULT CALLBACK GlobalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
Window* targetWindow;
if (msg == WM_CREATE)
{
//extract the Window instance pointer from the lparam
CREATESTRUCT* createStruct = (CREATESTRUCT*)lparam;
targetWindow = (Window*)createStruct->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)targetWindow);
}
else
{
//Get the Window Instance from the userdata
targetWindow = (Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
}
if (targetWindow)
{
//Now we can pipe it to the Window's local wnd proc function
return targetWindow->LocalWndProc(hwnd, msg, wparam, lparam);
}
//It was some other message which we just can't deal with right now
return DefWindowProc(hwnd, msg, wparam, lparam);
}

Related

CallWindowProc - strange behavior

It definetly works, but in a strange way.
I'm replacing RichEdit control's procedure with
WNDPROC g_OrigREditText =
(WNDPROC)SetWindowLongPtr( g_hwnd_RichEdit, GWLP_WNDPROC, (LONG_PTR)REditWndProc);
Then, I SendMessage to RichEdit control using EM_REPLACESEL msg, and the text appears in the RichEdit control as it should. But when I replace standard procedure with my own, and process EM_REPLACESEL in mine own procedure, then the following scenario happens. Here's the code:
LRESULT CALLBACK REditWndProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
{
switch( msg )
{
case EM_REPLACESEL:
{
int sdfsdf = 0;
CallWindowProc( (WNDPROC)g_OrigREditText, hwnd, msg, wp, lp );
break;
}
default:
return DefWindowProc( hwnd, msg, wp, lp);
break;
}
return 0;
}
Ok, I send an EM_REPLACESEL message to the RichEdit as usual and it works well, cos I catch operation pointer in "case EM_REPLACESEL" block.
Then CallWindowProc should do its job by passing parameters to the next procedure in the chain, but instead nothing happens, the text doesn't appear in a RichEdit control. Looks like something prevent the message to be passed to old procedure, but! if I replace g_OrigREditText with REditWndProc, then I catch the same UINT msg again, so it definetely pass parameters further, like it should.
So what's wrong with CallWindowProc or with my code, where should I dig to fix the problem?
The whole thing is called "subclassing" and this can be useful .
usually there are more events that occurs in relating with one message so change your code to this
LRESULT CALLBACK REditWndProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
{
if( msg == EM_REPLACESEL)
{
// whatever you want
return TRUE;// comment this
// if you want to give it to the original proc.
}
return CallWindowProc( g_OrigREditText, hwnd, msg, wp, lp );
}

Is it safe to deallocate oneself on WM_NCDESTROY?

Consider the following code snippet:
// MyWindow.h
struct MyWindow
{
LRESULT CALLBACK myWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK myWindowProcWrapper(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
};
extern MyWindow *windowPtr; // windowPtr is initialized on startup using raw new
// MyWindow.cpp
MyWindow *windowPtr = 0;
LRESULT CALLBACK MyWindow::myWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_NCDESTROY:
delete windowPtr;
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK MyWindow::myWindowProcWrapper(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
return windowPtr->myWindowProc(hwnd, msg, wParam, lParam);
}
The question is whether the given code snippet is safe as written.
Basically, MyWindow is the class for a window created with the WinAPI. I need to do some final cleanup when the window is destroyed.
Notice that the instance of MyWindow, windowPtr, is created using a raw new. I have to delete the instance somewhere in a member function, so I delete the reference to the object itself from within a member function.
The code relies on the assumption that WM_NCDESTROY is the last message ever received by that window.
So the questions are as follows:
Is it safe to assume that WM_NCDESTROY is always the last message a window receives and to perform final cleanup there?
Is the listed code safe? If not, under what conditions could it break?
Remark: I'm only interested in whether the code is technically safe, not if it is good practice to use a raw new and/or global variables. I have some good reasons for this implementation.
It is not explicitly documented, that WM_NCDESTROY is the final message a window receives. If you read in between the lines, you can deduce this information, though.
The documentation for WM_NCDESTROY contains the following remark:
This message frees any memory internally allocated for the window.
Window Features: Window Destruction outlines the consequences of this:
When a window is destroyed, the system [...] removes any internal data associated with the window. This invalidates the window handle, which can no longer be used by the application.
Putting those together, destroying a window invalidates its window handle. Once the WM_NCDESTROY message handler has run to completion, the window handle is no longer valid. An invalid window handle no longer receives any messages.
Your implementation is thus safe.
It is doubtful that any of these rules will change in the future (with so many applications relying on WM_NCDESTROY being the final message), but if you want to be prepared, you might want to consider placing a windowPtr = nullptr; statement following delete windowPtr;. Doing so ensures, that your application fails in a predictable way, in case it receives a message after the MyWindow instance has been disposed.
yes, WM_NCDESTROY is the last message sended to WindowProc (how minimum now).
but direct, unconditionally call delete this in myWindowProc can be unsafe in complex case and produce very bad error (because hard to found)
you not take to account that myWindowProc can be called recursively.
let us first consider a simple version:
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
DestroyWindow(hwnd);
}
so we destroy window on ESCAPE pressed. in this case myWindowProc will be called recursively with WM_NCDESTROY where we delete this and then we return to up level myWindowProc (for WM_KEYDOWN ). but at this point this already destroyed and invalid. so we must not more access any members fields or virtual functions. good if we call direct DefWindowProc in the end of own WindowProc. but what be if say our class implement and call virtual LRESULT DefWinProc(..) for allow overwrite DefWindowProc behaviour ? (say to DefMDIChildProc ) ?
now more complex case - assume you implement some child control. based on WM_SOMETHING_1 you set WM_NOTIFY_SOMETHING_1 to parent (via SendMessage ). and parent decide call DestrowWindow when handle this notify (for self, and as result for all childs). so internally your call delete this and when you return from SendMessage(..WM_NOTIFY_SOMETHING_1..) your this already will be deleted, but you will not even know about this.
access this after it will be deleted real in complex windows case, and if we modify some member data - can be not direct crash(which is good for detection) but heap corruption, which will manifest itself later and this is very hard to detect.
however for this exist 100% correct solution, which will be work even if WM_NCDESTROY will be not final message for window.
we must not use global MyWindow *windowPtr but assign it to window data via GWLP_USERDATA and remove on WM_NCDESTROY . and call myWindowProc only when GetWindowLongPtr(hwnd, GWLP_USERDATA) return not 0 . in this case even if will some WM_* message after WM_NCDESTROY - static myWindowProcWrapper not call myWindowProc and we must use reference counting for MyWindow class :
call AddRef(); before SetWindowLongPtrW(hwnd, GWLP_USERDATA,(LONG_PTR)windowPtr);
call Release(); after SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
wrap windowPtr->myWindowProc(hwnd, msg, wParam, lParam) in
AddRef(); Release();
call delete this only from Release and make ~MyWindow()
private:
with this rules we can absolute safe access this pointer in myWindowProc at any time and any situation. and not dependening about WM_NCDESTROY is the last message
struct MyWindow
{
private:
PSTR _somedata;
LONG _dwRef;
public:
MyWindow() : _dwRef(1), _somedata(0)
{
}
void AddRef()
{
InterlockedIncrement(&_dwRef);
}
void Release()
{
if (!InterlockedDecrement(&_dwRef)) delete this;
}
static LRESULT CALLBACK myWindowProcWrapper(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
MyWindow *windowPtr;
if (msg == WM_NCCREATE)
{
windowPtr = (MyWindow *)((LPCREATESTRUCT)lParam)->lpCreateParams;
windowPtr->AddRef();
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)windowPtr);
}
else
{
windowPtr = (MyWindow *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
}
LRESULT lr;
if (windowPtr)
{
windowPtr->AddRef();
lr = windowPtr->myWindowProc(hwnd, msg, wParam, lParam);
windowPtr->Release();
}
else
{
lr = DefWindowProc(hwnd, msg, wParam, lParam);
}
if (msg == WM_NCDESTROY && windowPtr)
{
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
windowPtr->Release();
}
return lr;
}
protected:
LRESULT CALLBACK myWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_NCDESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
DestroyWindow(hwnd);
DbgPrint("%s\n", _somedata);// bug can be here if not use ref semantic
}
break;
case WM_CREATE:
if (_somedata = new CHAR[64])
{
strcpy(_somedata, "1234567890");
}
break;
}
// bug can be here if not use ref semantic, because myDefWinProc virtual
return myDefWinProc(hwnd, msg, wParam, lParam);
//return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// for demo only here, not need in simply case
virtual LRESULT myDefWinProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
private:
~MyWindow()
{
if (_somedata) delete _somedata;
}
};
if (MyWindow* p = new MyWindow)
{
CreateWindowEx(0, L"lpszClassName", L"lpWindowName", WS_OVERLAPPEDWINDOW|WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, 0, 0, p);
p->Release();
}
Yes. Self deallocate is safe in WM_NCDESTROY.
But just to be sure your program will not fail under Wine, for example, I'd recommend to set windowPtr to null after delete, and check it before call windowPtr->myWindowProc.
And, ofcause, it must be only window of that wndclass.

Use class member as WNDPROC/DLGPROC with or without global

I'll go ahead and give a summary to this, how can I use a dialog procedure that is a member of a class? I am creating a window wrapper class, but CreateDialogParam needs a global dialog procedure, so I tried this workaround:
I have done a bit of searching on this topic. I am making a Dialog class which I am subclassing to make a CMainWnd and then instantiating that. In the Dialog class I have a member function defined as INT_PTR CALLBACK Dialog::cb_proc(HWND,UINT,WPARAM,LPARAM). Now, I know that windows must have a global function as a callback procedure.
So I made a std::map<HWND,Dialog*> DlgProcs map to associate the dialogs window handle with its Dialog class pointer.
And a INT_PTR CALLBACK DlgMainProc(HWND,UINT,WPARAM,LPARAM) so I could pass that to CreateDialogParam(). In the body of DlgMainProc(...) I search the map for using the hWnd parameter to find the Dialog* and return its cb_proc(..) member.
My problem is that none of the messages get processed, this is because the member procedure in my Dialog class never gets called. Even though when I put a MessageBox() in DlgMainProc inside a if (DlgProcs.find(hWnd) != DlgProcs.end()) { statement, the messagebox is displayed, over and over again until I have to abort the program from Visual Studio 2008. Which tells me that it is finding the hWnd in my map. The weird thing is it also does this if I put it in the else statement after that, which contradictingly tells me it is NOT finding the hWnd in the map.
If I put a messagebox in the cb_proc member function it does not get displayed at all. But during this I never get any compiler, linker, or runtime errors. When I remove the messagebox from it (as to not have to abort the program, it was just for debugging purposes) the program runs but no messages get processed, the X button does not close the program, button clicks do nothing.
Here is the PasteBin code: http://pastebin.com/GsGUBpZU
Btw, I have no problem subclassing this, my window is created fine, just no messages are processed, cb_proc just never gets called.
EDIT: Here is the relevant parts of the code
map<HWND,Dialog*> g_DlgProcs;
INT_PTR CALLBACK g_MainDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (g_DlgProcs.find(hWnd) != g_DlgProcs.end()) {
Alert("blah"); // Gets executed repeatedly
return g_DlgProcs[hWnd]->cb_proc(hWnd, msg, wParam, lParam);
} else {
Alert("blah"); // Removing the above alert, this gets
// executed repeatedly, erm, as well.. O.o strange
return FALSE;
}
}
Dialog::Dialog(int id, HWND parent /* = HWND_DESKTOP */) {
_id = id;
_parent = parent;
// Tried this before CreateDialogParam
g_DlgProcs.insert(make_pair(_handle, this));
_handle = CreateDialogParam(
(HINSTANCE)GetModuleHandle(NULL),
MAKEINTRESOURCE(id), _parent,
(DLGPROC)g_MainDlgProc, NULL
);
// Then tried it after CreateDialogParam
g_DlgProcs.insert(make_pair(_handle, this));
}
INT_PTR CALLBACK Dialog::cb_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
Alert("blah"); // Never gets executed
bool handled = true;
switch (msg)
{
case WM_INITDIALOG:
OnInitialize();
break;
case WM_COMMAND:
if (HIWORD(wParam) == 0 || HIWORD(wParam) == 1) {
OnMenuCommand((HIWORD(wParam) == 1), (int)LOWORD(wParam));
} else {
OnCtrlCommand((int)HIWORD(wParam), (int)LOWORD(wParam), (HWND)lParam);
}
break;
case WM_NOTIFY:
{
LPNMHDR head = (LPNMHDR)lParam;
OnNotification(head->code, head->idFrom, head->hwndFrom);
}
break;
case WM_CLOSE:
OnClose(); // DestroyWindow(_handle)
break;
case WM_DESTROY:
OnDestroy(); // PostQuitMessage(0)
default:
handled = ProcessMsg(msg, wParam, lParam);
}
// Convert bool to Windows BOOL enum
return ((handled == true) ? TRUE : FALSE);
}
Does anybody know why it never gets called? Or maybe just guide me to another way to use a member function as a DLGPROC?
The standard solution is to pass your this pointer as the last parameter to Create,DialogParam, stash it in DWLP_USER in your WM_INITDIALOG handler, and retrieve it from DWLP_USER thereafter. Basically you use DWLP_USER as your map.
I tried your code and it worked: cb_proc gets called. You will miss any messages (e.g. WM_INITDIALOG) that get sent before CreateDialogParam returns.
You can fix the latter problem by adding the window handle and the object to the map in g_MainDlgProc. If you get a message for an unknown window, you know it belongs to the window you're creating; put the object in a global and you can add the handle/object to the map.
I'm just adding this here in case someone finds it useful; using the magic of C++11 lambdas and templates you can have a simple wrapper template that means you don't have to continually rewrite the boiler-plate code for saving and loading userdata in window and dialog box procedures.
Here's an example for the DialogBoxParam function, but the same technique can be applied to CreateDialogParam and CreateWindowEx as well.
template <typename T, INT_PTR (T::*P)(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)>
INT_PTR DialogBoxThis(T* pThis, HINSTANCE hInstance, LPCWSTR lpTemplateName, HWND hWndParent)
{
return ::DialogBoxParam(hInstance, lpTemplateName, hWndParent, [](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> INT_PTR {
if (uMsg == WM_INITDIALOG) SetWindowLongPtr(hWnd, DWLP_USER, lParam);
T* pThis = reinterpret_cast<T*>(GetWindowLongPtr(hWnd, DWLP_USER));
return pThis ? (pThis->*P)(hWnd, uMsg, wParam, lParam) : FALSE;
}, reinterpret_cast<LPARAM>(pThis));
}
You would use it like this:
class MyClass
{
INT_PTR MyDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
};
// from inside MyClass, we can show a dialog that uses member function MyDlgProc as the dialog procedure
// note it is NOT a static function
DialogBoxThis<MyClass, &MyClass::MyDlgProc>(this, hInstance,
MAKEINTRESOURCE(IDD_MYDIALOG), hWndParent);

Subclassing SHBrowseForFolder and handling WM_NOTIFY

I'm trying to fix SHBrowseForFolder dialog, as it doesn't react on folder renaming (BFFM_SELCHANGED isn't being sent and there is no way to determine is the path now correct or not). I googled a solution, which said like I have to subclass dlg's wndproc and catch TVN_ENDLABELEDIT to send BFFM_SELCHANGED myself.
Here is how I set new wndproc when I get BFFM_INITIALIZED:
for (HWND hChild = GetWindow(hWnd, GW_CHILD); hChild != 0; hChild = GetWindow(hChild, GW_HWNDNEXT)) {
szClassName[256];
GetClassName(hChild, szClassName, sizeof(szClassName));
if (!stricmp(szClassName, "SHBROWSEFORFOLDER SHELLNAMESPACE CONTROL")) {
oldWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(hChild, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(_SubclassWndProc)));
break;
}
}
Here is _SubclassWndProc:
static LRESULT _SubclassWndProc(HWND hWnd, UINT uMsg, WPARAM lParam, LPARAM lpData) {
switch (uMsg) {
case WM_NOTIFY:
switch (((LPNMHDR)lParam)->code) {
case TVN_ENDLABELEDIT:
break;
}
break;
}
return CallWindowProc(oldWndProc, hWnd, uMsg, lParam, lpData);
}
It works only if I comment WM_NOTIFY block. Even an access to lParam breaks dialog (it contains corrupted treee with empty labels). If I call oldWndProc before switch, then it works, but in WM_NOTIFY case lParam obviously doesn't contain a pointer to NMHDR, it contains a small integer value like 1,2,100 etc.
Edit: The question can be shortened to "Why does WM_NOTIFY come without a pointer to NMHDR?"
The error was due to my negligence: I copied wndproc signature from some example, which has agrument names confused. The lParam usually comes last and has a type LPARAM. So I was trying to cast arg usually called wParam, which contains a control id not LPNMHDR.

Object oriented c++ win32?

I want to create my own class to handle creating windows and the window procedure but I have noticed that the window procedure has to be static! I'm now wondering whether its possible to make the window procedure object oriented? I have read some tutorials on object oriented windows, but they always make the procedure static -.- whats the use in that? :/
Any links or info on how to get around this problem would be appreciated,
thanks
You can get around that by making the static WndProc delegate everything to the members:
// Forward declarations
class MyWindowClass;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
std::map<HWND, MyWindowClass *> windowMap;
// Your class
class MyWindowClass {
private:
HWND m_handle;
// The member WndProc
LRESULT MyWndProc(UINT message, WPARAM wParam, LPARAM lParam) { /* ... */ }
public:
MyWindowClass()
{
/* TODO: Create the window here and assign its handle to m_handle */
/* Pass &WndProc as the pointer to the Window procedure */
// Register the window
windowMap[m_handle] = this;
}
};
// The delegating WndProc
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
std::map<HWND, MyWindowClass *>::iterator it = windowMap.find(hWnd);
if (it != windowMap.end())
return it->second->MyWndProc(message, wParam, lParam);
return 0;
}
The general technique of allowing a window instance to be represented by as class instance is to make use of the SetWindowLongPtr and GetWindowLongPtr to associate your class instance pointer with the window handle. Below is some sample code to get you started. It may not compile without a few tweaks. It's only meant to be a reference.
Personally, I've stopped rolling my own window classes back a few years ago when I discovered ATL's CWindow and CWindowImpl template class. They take care of doing all this mundane coding for you so can focus on just writing methods that handle window messages. See the example code I wrote up here.
Hope this helps.
class CYourWindowClass
{
private:
HWND m_hwnd;
public:
LRESULT WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE: return OnCreate(wParam, lParam);
case wM_PAINT: return OnPaint(wParam, lParam);
case WM_DESTROY:
{
SetWindowLongPtr(m_hwnd, GWLP_USERDATA, NULL);
m_hwnd = NULL;
return 0;
}
}
return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
}
CYourWindowClass()
{
m_hwnd = NULL;
}
~CYourWindowClass()
{
ASSERT(m_hwnd == NULL && "You forgot to destroy your window!");
if (m_hwnd)
{
SetWindowLong(m_hwnd, GWLP_USERDATA, 0);
}
}
bool Create(...) // add whatever parameters you want
{
HWND hwnd = CreateWindow("Your Window Class Name", "Your Window title", dwStyle, x, y, width, height, NULL, hMenu, g_hInstance, (LPARAM)this);
if (hwnd == NULL)
return false;
ASSERT(m_hwnd == hwnd);
return true;
}
static LRESULT __stdcall StaticWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
CYourWindowClass* pWindow = (CYourWindowClass*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
if (uMsg == WM_CREATE)
{
pWindow = ((CREATESTRUCT*)lParam)->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (void*)pWindow);
m_hWnd = hwnd;
}
if (pWindow != NULL)
{
return pWindow->WndProc(uMsg, wParam, lParam);
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
};
};
If you are looking for object oriented Win32 API then you should look to MFC and/or WTL.
Just to add to Brian's answer but for a win32 framework that's more beginner friendly take a look at Win32++. The library itself isn't as comprehensive in features compared to MFC or QT but that is a tradeoff the designer made at the beginning to keep the library easy to understand and simple to use.
If you're still interested in this topic, I highly encourage you to take a look at it since it uses yet another technique for saving the 'this' pointer by utilizing thread local storage.
You can use the window handle passed to the WindowProc to grab an object you've created for that particular window and delegate the event handling to that object.
e.g.
IMyWindowInterface* pWnd = getMyWindowObject(hWnd);
pWnd->ProcessMessage(uMsg, wParam, lParam);