Use object method as WinApi WndProc callback

10,078

Solution 1

The usual is something on this order:

#include <windows.h>

class BaseWindow {

     static LRESULT CALLBACK internal_WndProc(HWND hWnd, int msg, WORD wParam, LONG lParam) {
        BaseWindow *c = (BaseWindow *)GetWindowLong(hWnd,GWLP_USERDATA);

        if (c == NULL)
            return DefWindowProc(hWnd, msg, wParam, lParam);

        return c->WindowProc(hWnd, msg, wParam, lParam);
    }

public:
    virtual int WindowProc(HWND hWnd, int msg, WPARAM wParam, LPARAM lParam) = 0;

    BaseWindow(HINSTANCE instance) {
        WNDCLASS window_class = {0};
        HWND window;
        HMENU my_menu;

        window_class.lpfnWndProc = (WNDPROC)internal_WndProc;
        /* fill in window_class here */
        RegisterClass(&window_class);

        window = CreateWindow(
            "My Application", "Stupidity", 
            WS_OVERLAPPEDWINDOW, 
            CW_USEDEFAULT, CW_USEDEFAULT, 
            CW_USEDEFAULT, CW_USEDEFAULT, 
            NULL, my_menu, instance, NULL);

        // save the address of the class as the Window's USERDATA.
        SetWindowLong(window, GWLP_USERDATA, (long)this);
    }
};

With this, you derive a class from BaseWindow. In your derived class, you provide a "WindowProc" that overrides the (pure virtual) one in BaseWindow. The trick here is fairly simple: since you can't pass a parameter directly, you store the address of the class in the window's GWLP_USERDATA, then in the window proc (try to) retrieve that and use it to call the derived class' virtual window proc.

As an aside, note that this is a sketch, not a finished work (so to speak). Though it should compile as-is, the result won't actually work unless you fill in a fair number of pieces that aren't here (e.g., the other fields of the WNDCLASS structure being only one of the most obvious).

Solution 2

The other question you linked to only applies partially.

The WindowProc method does need to be static. Then right after the call to CreateWindowEx call SetWindowLongPtr with GWLP_USERDATA as the second parameter and this as the third one. That associates the HWND with the class instance. Then in the static WindowProc method call GetWindowLongPtr (again with GWLP_USERDATA) to get the WindowConsole instance that received the UI event.

That's all explained in detail here:

http://msdn.microsoft.com/en-us/library/windows/desktop/ff381400(v=vs.85).aspx

Solution 3

I use a simple solution. The winproc is a template function. The message receiver is inside setwindowptr.

If the receiver has a function with the message name , for instance, onpaint , then the wm_paint is included at message switch.

http://www.thradams.com/codeblog/wndproc.htm

Share:
10,078
Tomáš Zato
Author by

Tomáš Zato

It might be easier to hire new management than to get a new community of volunteers. - James jenkins If you play League of Legends, check my repository: http://darker.github.io/auto-client/ I no longer play and I am actively looking for someone to maintain the project. It helped thousands of people, literally.

Updated on June 20, 2022

Comments

  • Tomáš Zato
    Tomáš Zato about 2 years

    I'm trying to make a little class that displays console window in parent window. (you can imagine chat or debug info being displayed there)
    Now, since diferent instanes do have different private variables (such as message array or parent window), I need to use non-static method as callback for the Windows events.
    I have thought of ways, where I'd pass the actual class instance to static callback function and then called the proper method on it, but in winAPI, everything is done using TranslateMessage and DispatchMessage giving me no chance to use arguments of my own.
    I found some code here: Class method as winAPI callback, but I don't understand it, and I think it is not exactly what I need. If it is, then please give me further explanation of code provided.
    Error I get:

    error: argument of type 'LRESULT (WindowConsole::)(HWND__, UINT, WPARAM, LPARAM)' does not match 'LRESULT (*)(HWND__, UINT, WPARAM, LPARAM)'

    I don't know what that star in brackets means, but this is what does not match.
    And the code:

    class WindowConsole {
       char messages[255][255];
       HWND mainWindow;
       public:
         int width;
         int height;
         inline HWND create(HWND parent);
         inline bool update();
         inline LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
    };
    
    HWND WindowConsole::create(HWND parent) {
        HINSTANCE inst =  GetModuleHandle (0);
        WNDCLASSEX wincl;
    
        /* The Window structure */
        wincl.hInstance = inst;
        wincl.lpszClassName = "ConsoleClass";
        wincl.lpfnWndProc = this->WndProc;      /* This function is called by windows */
        /* more WNDCLASSEX crap...*/
    
        mainWindow = CreateWindow (
              /*PARAMS*/
        );
        ShowWindow(mainWindow,1);
        return mainWindow;
    
    }
    bool WindowConsole::update() {
       return true;
    }
    LRESULT CALLBACK WindowConsole::WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
        switch (message)                  /* handle the messages */
        {
               /*EVENT crap*/
        }
    
        return 0;
    }
    
  • Tomáš Zato
    Tomáš Zato over 11 years
    Thank you, with your example, I produced something, that, at least, compiles without errors. Now there is obvions question: where do I put the loop to capture window events and dispatch them? Do I have to make my program asynchronous (add another thread?)?
  • Jerry Coffin
    Jerry Coffin over 11 years
    @TomášZato: The message loop is (essentially) always in WinMain, which will typically just create an instance of the window, then go into a message loop. No, you don't normally want to run it in a separate thread.
  • Tomáš Zato
    Tomáš Zato over 11 years
    Well, but my class can create many windows as I can make many instances. So should I keep window handlers (HWND) in some global variable, to dispatch events for each of them in WinMain()?
  • Jeroen
    Jeroen almost 10 years
    This method doesn't work if you want to overwrite non-client-area messages.
  • Terrance
    Terrance about 9 years
    As noted here and below, SetWindowLongPtr() / GetWindowLongPtr must be used for 64-bit executables.