Accessing class members in WindowProc()

Started by
10 comments, last by Cygon 22 years, 1 month ago
Imagine I've got a class ("CMainWindow") which manages a window. Its WindowProc() has to call a function from inside that class.
        
 LRESULT CALLBACK WindowProc(HWND p_hWnd, UINT p_uMessage, WPARAM p_wParam, LPARAM p_lParam) {
   switch(p_uMessage) {
     case WM_ACTIVATEAPP: {
       if(p_wParam)
         Resume(); // Resume() contained in CMainWindow
       else
         Pause(); // Pause() contained in CMainWindow

       break;
     }
   }
   //

   return DefWindowProc(hwnd, uMsg, wParam, lParam);
 }

[/SOURCE]

Of course, I cannot add the WindowProc() function to my class because the shadowed 'this' parameter will prevent it from being accepted in CreateWindow() then.

If I would create a global variable, like

[SOURCE]

 CMainWindow *self;
 //

 LRESULT CALLBACK WindowProc(HWND p_hWnd, UINT p_uMessage, WPARAM p_wParam, LPARAM p_lParam) {
   ...
         self->Resume(); // Resume() contained in CMainWindow

    
then the entire thing wouldn't be thread safe. I already tried doing my message handling directly after GetMessage()...TranslateMessage() without calling DispatchMessage(), but it seems not all messages get into the WindowProc() on that way. I thought of adding some extra bytes to the window (a pointer to CMainWindow class to be exact), but this again would prevent my CMainWindow class from using other, preexisting hWnds, like from a CFrame object in an MFC Dialog. And I cannot use the msg.lParam parameter to fill it with a pointer to my class because the docs say that it is used by some messages. Anyone's got an idea ? -Markus- Hm, are no 2 source areas allowed in a single post ? Edited by - Cygon on 9/11/00 3:52:39 PM
Professional C++ and .NET developer trying to break into indie game development.
Follow my progress: http://blog.nuclex-games.com/ or Twitter - Topics: Ogre3D, Blender, game architecture tips & code snippets.
Advertisement
I''m not the best Win32 programmer, but I will try to help... I''ve heard of a PeakMessage() function you can use in the whole TranslageMessage() and DispatchMessage() portion of the program. PeakMessage() looks at the message and determines if it was meant for your program, if not it discards it and goes back to its happy little loop.
There is no spoon.
PeekMessage() is just the same as GetMessage(), just that it doesn''t remove the message from the message buffer.

Actually, I''m using PeekMessage() to obtain the messages I''m passing on through TranslateMessage() to DispatchMessage().

But this is just one way how messages arrive at the WindowProc(), most messages regarding window changes (resizing, moving, etc) are directly handed to the WindowProc(). PeekMessage()/GetMessage() retrieve messages for the entire application, I need messages for my window (WM_PAINT, WM_MOVING...).

Anyway, thanks for your answer.
-Markus-
Professional C++ and .NET developer trying to break into indie game development.
Follow my progress: http://blog.nuclex-games.com/ or Twitter - Topics: Ogre3D, Blender, game architecture tips & code snippets.
It’s far from trivial and requires familiarity with Win32 and some assembly. If you are new to either one this can be very, very painful.
Essentially, here’s what you do:
- register your own window class and set WNDPROC for that class to StartWindowProc().
- create a window of the class you just registered and pass a pointer to C++ class in a CREATESTRUCT
- when StartWindowProc receives WM_NCCREATE pull C++ class pointer from CREATESTRUCT, store window handle in the class member variable, initialize the thunk with class pointer and a pointer to new WNDPROC, and subclass the window with a thunk.
- when thunk is called it will replace HWND (first argument to WNDPROC) with a C++ class pointer (right on the stack) and call WNDPROC that was given to the thunk during initialization. That WNDPROC can safely cast hWnd to C++ class pointer and dispatch the message to the class instance.

Here’s the source. Don’t mind the odd mixture of hunganrian and non-hungarian – I’m experimenting a bit with my naming notations:
        class window_t;// ***// WindowProc thunks - ripped off from ATL with minor changes// ***#if defined(_M_IX86)#pragma pack(push,1)struct _WndProcThunk{	DWORD   m_mov;          // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)	DWORD   m_this;         //	BYTE    m_jmp;          // jmp WndProc	DWORD   m_relproc;      // relative jmp};#pragma pack(pop)#else#error Only X86 is supported#endifclass CWndProcThunk{	void Init(WNDPROC proc, void* pThis)	{#if defined (_M_IX86)		thunk.m_mov = 0x042444C7;  //C7 44 24 0C		thunk.m_this = (DWORD)pThis;		thunk.m_jmp = 0xe9;		thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));#endif        // write block from data cache and        //  flush from instruction cache        ::FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk));    }private:    _WndProcThunk thunk;    friend window_t;};// ***// This class allows OO window message processing// ***class window_t{public:    window_t() : m_hwnd(NULL) {}    virtual ~window_t()    {        if(::IsWindow(m_hwnd))            ::DestroyWindow(m_hwnd);    }public:    static bool RegisterWindowClass(HINSTANCE hInstance, HICON hIcon, HICON hIconSmall,         HBRUSH hbrBackground, HCURSOR hCursor)    {         WNDCLASSEX wcex;                wcex.cbSize = sizeof(WNDCLASSEX);                 wcex.style			= CS_HREDRAW | CS_VREDRAW;        wcex.lpfnWndProc	= (WNDPROC)StartWindowProc;        wcex.cbClsExtra		= 0;        wcex.cbWndExtra		= 0;        wcex.hInstance		= hInstance;        wcex.hIcon			= hIcon;        wcex.hCursor		= hCursor;        wcex.hbrBackground	= hbrBackground;        wcex.lpszMenuName	= NULL;        wcex.lpszClassName	= _T("vladg::window_t window class");        wcex.hIconSm		= hIconSmall;                ATOM atom = RegisterClassEx(&wcex);        if(NULL == atom)            return false;        return true;    }    static LRESULT CALLBACK StartWindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)    {        if(WM_NCCREATE == msg)        {            CREATESTRUCT* pcs = (CREATESTRUCT*)lparam;            window_t* pThis = (window_t*)pcs->lpCreateParams;            if(NULL == pThis)                ERR("Window proc thunking is fubar.\n");            pThis->m_hwnd = hwnd;            pThis->m_thunk.Init(pThis->WindowProc, pThis);            WNDPROC pProc = (WNDPROC)&(pThis->m_thunk.thunk);            WNDPROC pOldProc = (WNDPROC)::SetWindowLong(hwnd, GWL_WNDPROC, (LONG)pProc);#ifdef _DEBUG            // check if somebody has subclassed us already since we discard it            if(pOldProc != StartWindowProc)                WARN("Subclassing through a hook discarded.\n");#else            pOldProc;	// avoid unused warning#endif            return pProc(hwnd, msg, wparam, lparam);        }        else            return ::DefWindowProc(hwnd, msg, wparam, lparam);    }    static LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)    {        try        {            window_t* pThis = (window_t*)hwnd;                        LRESULT result = 0;            if(!(pThis->ProcessWindowMessage(pThis->m_hwnd, msg, wparam, lparam, result, 0)))                result = ::DefWindowProc(pThis->m_hwnd, msg, wparam, lparam);                        if(WM_NCDESTROY == msg)                pThis->m_hwnd = NULL;            return result;        }        catch(_error_t error)        {            error.report();        }        catch(...)        {        }        // bail out of the app        PostQuitMessage(0);        return 0;    }    virtual BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0)    {        // actually I’m using message map macros to implement this method        // but I decided to omit them for brevity        // see ATL if you need message map macros example        if(WM_PAINT == uMsg)        {            // handle WM_PAINT here            lResult = 0            return TRUE;        }        // let default window proc handle it        return FALSE:    }    HWND GetHandle() { return m_hwnd; }    operator HWND() { return GetHandle(); }    virtual void Create(LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int cx, int cy,         HWND hwndParent, HMENU hMenu, HINSTANCE hInstance)    {        CreateEx(0L, lpWindowName, dwStyle, x, y, cx, cy,                 hwndParent, hMenu, hInstance);    }    virtual void CreateEx(DWORD dwExStyle, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y,        int cx, int cy, HWND hwndParent, HMENU hMenu, HINSTANCE hInstance)    {        HWND hwnd = ::CreateWindowEx(dwExStyle, _T("vladg::window_t window class"),            lpWindowName, dwStyle, x, y, cx, cy, hwndParent, hMenu, hInstance, this);        if(!::IsWindow(hwnd))            ERR("failed to create a window.");        // by now thunking code should've rerouted messages to our        // message map and set correct window handle in m_hwnd;        if((!::IsWindow(m_hwnd)) || hwnd != m_hwnd)            ERR("window proc thunking failed.");    }protected:    HWND                m_hwnd;private:    CWndProcThunk       m_thunk;}; // class window_t        


This code won’t compile on your machine because I’m using my own debug macros: ERR() and WARN(). ERR() throws an exception, WARN call OutputDebugString(). WinMain() should be trivial - register class, create window, pump messages.

It’s the most elegant way to do it that I found so far. The idea was ripped off from ATL with some changes. ATL is not using CREATESTRUCT, it stores pointer to a C++ class in a global list (using current thread id as a key, otherwise it won't be thread-safe) and StartWindowProc pulls it out of the list on the first message it receives (again, using current thread id as a key). It allows ATL windows to dispatch pre-NCCREATE messages to the C++ class. I did not think it was very important and decided to get rid of the whole global list mess.


Edited by - vladg on September 11, 2000 6:10:11 PM
Great!

Replacing hWnd with the class''s this pointer is the perfect solution!

In the meantime, I have tried to workaround by using a global array storing each CMainWindow instance with its associated hWnd. My global WindowProc() then searched for the message''s hWnd in the array

(I''m trying to develop a minimal Win32 base application correctly handling Alt-Tab, Window Resizing, Multiple Windows, etc.
www.LunaticSystems.de/d3dapp.zip)

Whatever, thank you for this wonderful solution!
(Hm, raw opcodes, change the instructions at runtime... it will surely take some time for me to fully understand that code )

-Markus-
Professional C++ and .NET developer trying to break into indie game development.
Follow my progress: http://blog.nuclex-games.com/ or Twitter - Topics: Ogre3D, Blender, game architecture tips & code snippets.
Or you could hang the pointer to an object on a window.
- set the "cbWndExtra" member of the WNDCLASS structure to 4
- use SetWindowLong() to set this ptr.
- use GetWindowLong() to get this ptr.

vladg:
Your code looks very interresting, but I must confess I still don''t get it 100%. Why not simply add a pointer in a window to your object? Or am I thinking too simple?

quote:Original post by Cygon

Imagine I''ve got a class ("CMainWindow") which manages a window. Its WindowProc() has to call a function from inside that class.

...

Of course, I cannot add the WindowProc() function to my class because the shadowed ''this'' parameter will prevent it from being accepted in CreateWindow() then.

...

Anyone''s got an idea ?



You could try using SetProp/GetProp, something like this:

-After you have succesfully created your window

SetProp(m_hWin,"MYWINDOW",(HANDLE)this);

- Create two message procs, one static, one non static that you have access to your methods/properties...

in static message proc:
LRESULT CALLBACK MYWindow::WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
MYWindow *pThis = (MYWindow *)GetProp(hWnd,"MYWINDOW");
if(pThis) return pThis->WindowProc(hWnd,msg,wparam,lparam);
return DefWindowProc(hWnd,msg,wparam,lparam);
}

Hope this helps...

-------------------
It''s never 2Late
quote:Original post by Cygon
.
.
.
Hm, are no 2 source areas allowed in a single post ?


use [ /source] and not [ /SOURCE]
quote:Original post by Cygon
Hm, are no 2 source areas allowed in a single post ?

It gets screwy when you edit.

[ GDNet Start Here | GDNet Search Tool | GDNet FAQ | MS RTFM [MSDN] | SGI STL Docs | Google! ]
Thanks to Kylotan for the idea!
quote:Original post by Anonymous Poster

use [ /source] and not [ /SOURCE]


You burped up a 2 year old thread just to say that?
--------------------Go Stick Your Head In A Pig

This topic is closed to new replies.

Advertisement