2

I am making a Windows API C++ wrapper. The header file looks like this:

#include <windows.h>
#include <exception>
#include <stdexcept>
const int NOID = -1;

class inst {
    struct impinst;  // pimpl idiom
    impinst *imp;

    friend class win;   // win needs to see the private members of inst
                        // (namely, the WNDCLASS)
public:
    inst(const char *, HINSTANCE, int=NOID,
         HICON=LoadIcon(NULL, IDI_APPLICATION));

    ~inst();
};
class win {
    struct impwidget;  // pimpl idiom
    impwidget *imp;
public:
    win(inst &, const char *, int=0, int=0, int=600, int=450);
    void show(int);
    WPARAM msgpump();

    ~win();
};

// These are the object oriented message classes
// handler will be implemented by user of the library
class msg {
public:
    virtual void handler();
};
class movemsg : public msg {
public:
    void handler();
};
class sizemsg : public msg {
public:
    void handler();
};

The implementation (cpp) file:

#include "winlib.h"
struct inst::impinst {
    WNDCLASS wc;
    static LRESULT CALLBACK winproc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp)
    {
        return DefWindowProc(hwnd, wm, wp, lp);
    }
    impinst(const char *classname, HINSTANCE hInst, int menuid, HICON hi)
    {
        this->wc.style = 0;
        this->wc.lpfnWndProc = this->winproc;
        this->wc.cbClsExtra = 0;
        this->wc.cbWndExtra = 0;
        this->wc.hInstance = hInst;
        this->wc.hIcon = hi;
        this->wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        this->wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
        this->wc.lpszMenuName = (menuid == NOID) ?
                                NULL : MAKEINTRESOURCE(menuid);
        this->wc.lpszClassName = classname;

        if (!RegisterClass(&this->wc))
            throw "Could not construct window instance";
    }
};
inst::inst(const char *classname, HINSTANCE hInst, int menuid, HICON hi)
{
    this->imp = new impinst(classname, hInst, menuid, hi);
}
inst::~inst()
{
    delete this->imp;
}


struct win::impwidget {
    HWND hwnd;

    impwidget(inst &i, const char *text, int x, int y, int width, int height)
    {
        this->hwnd = CreateWindow(i.imp->wc.lpszClassName, text,
                                  WS_OVERLAPPEDWINDOW, x, y, width, height,
                                  NULL, NULL, i.imp->wc.hInstance, NULL);
        if (this->hwnd == NULL)
            throw "Could not create window";
    }
};
win::win(inst &i, const char *text, int x, int y, int width, int height)
{
    this->imp = new impwidget(i, text, x, y, width, height);
}
void win::show(int cmdshow)
{
    ShowWindow(this->imp->hwnd, cmdshow);
    UpdateWindow(this->imp->hwnd);
}
WPARAM win::msgpump()
{
    MSG msg;

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}
win::~win()
{
    delete this->imp;
}

I have a problem though: How can I pass all the message handler functions to my inst class, so they can be implemented in the winproc function? Right now, it is empty (just calls DefWindowProc), but I need it to somehow get all the user-provided implementations for the handler function and pass them to the winproc for handling. How could I do this? Do I need to pass a pointer to the msg class?

Edit:

My question is different, because my question asks what to pass to the lpParam argument of CreateWindow, not how like in the other question.

lost_in_the_source
  • 10,998
  • 9
  • 46
  • 75

1 Answers1

4

Passing data through to a winproc callback is somewhat roundabout, but if you notice the documentation for CreateWindow, there's an lpParamparameter which you can use to pass data through to a WM_CREATE or WM_NCCREATE event.

So you can do something like this:

MyData* my_data = ...;
this->hwnd = CreateWindow(i.imp->wc.lpszClassName, text,
                          WS_OVERLAPPEDWINDOW, x, y, width, height,
                          NULL, NULL, i.imp->wc.hInstance,
                          my_data /* passed to WM_NCCREATE: */);

Then in your winproc, when you receive a WM_NCCREATE message, we can associate that to the window handle using SetWindowLongPtr, like so:

static LRESULT CALLBACK winproc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_NCCREATE)
    {
        MyData* my_data = (MyData*)(LPCREATESTRUCT(lParam)->lpCreateParams);
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)my_data);
    }
    ...
}

For the other events, you can then retrieve that data using:

LONG_PTR lpUserData = GetWindowLongPtr(hwnd, GWLP_USERDATA);
if (lpUserData)
{
     MyData* my_data = (MyData*)(lpUserData);
     // do stuff with `my_data`
}

It's pretty roundabout so it's handy to wrap it and possibly make your own kind of winproc method on top of the callback to which you pass my_data (or my_data can be a pointer to a class and you can just call a method through that). Also make sure the pointee of my_data isn't destroyed before the window itself is destroyed.

This roundabout nature is needed because we have to first make sure the window is created successfully before we call SetWindowLongPtr. The most immediate place to do that is in the WM_NCCREATE event where we pass over the data through CreateWindow. Then we can retrieve that data and call SetWindowLongPtr, and access it through GetWindowLongPtr in subsequent events.

  • But what should I pass to the `lpParam` argument to `CreateWindow` in my case? I need to be able to call the `handler` function for every class that derives from `msg`. So would I pass a pointer to `msg`? – lost_in_the_source Nov 22 '15 at 17:13
  • 1
    Basically whatever you need -- you have to squeeze it through a pointer. For example, you might pass in some aggregate structure or class which has everything you need available. Maybe you could store a list of base pointers to `msg` which stores all these msg instances through which you can call this `handler` method. –  Nov 22 '15 at 17:15
  • But first step is typically figuring out a way to pass data over to your `winproc` without just using a bunch of global variables. The above covers that part. –  Nov 22 '15 at 17:16
  • 2
    Are you trying to wrap it to kind of your own event handler system, like if you receive a `WM_SIZE` size event, call an instance of your `size_msg` handler? If so, it might be handy to keep a map of some sort which, say, maps WM_SIZE to an instance of `size_msg`. The data you pass through `lpParam` and associate persistently to the window through `SetWindowLongPtr` can contain that map (or possibly some kind of factory if you generate those instances on the fly). –  Nov 22 '15 at 17:19
  • Yes @Ike that is what I'm trying to do. Thanks for the suggestion. Should I use a lookup table to implement the map of windows messages to the class? – lost_in_the_source Nov 22 '15 at 17:26
  • A LUT might be suitable or maybe `std::map`. Latter would be easier since you don't have to think much about how to map the keys in some kind of contiguous fashion. –  Nov 22 '15 at 17:30
  • 2
    Or you can just have a big class storing like `size_msg`, `move_msg`, etc. The inheritance might not help that much here since, for `winproc`, if you want to fetch the values passed to each event (`lParam`, `wParam`), you typically need branching and handle each event separately (have to handle `WM_PAINT` separately from `WM_MOVE` and `WM_SIZE` and so forth). It's hard to generalize that code without making `winproc` into a huge switch statement. So it might be easier to just have a switch case for `WM_SIZE`, then call like `my_data.size_msg.handle()` in there. `winprocs` tend to be... –  Nov 22 '15 at 17:33
  • ... kind of ugly monolithic functions with lots of branching because of the boatload of events to handle separately with separate parameters used for each. –  Nov 22 '15 at 17:35
  • Though if you want a light wrapper, you could, say, make `msg::handle` accept the `lParam` and `wParam` parameters passed to `winproc` and send them over to your individual handler types. Then you can just use a general kind of approach where you don't have to handle each and every winproc event separately -- just grab the appropriate msg handler from the map based on the `wm` and call it with the same parameters. –  Nov 22 '15 at 17:37
  • Could you save `my_data` as a static variable in the window procedure, so that other messages could access it, instead of having to use `SetWindowLong` and `GetWindowLong`? – lost_in_the_source Nov 26 '15 at 14:39
  • @stackptr You can use a file-scope static variable, e.g. Typically it needs to be stored outside of winproc because we generally need to initialize it somewhere outside of it (ex: when creating the window). That can be simpler but the downside is that it only works if you have a single window using the same winproc. The benefit of using these functions instead to *associate* data to a window is the ability to be able to create multiple instances of it without writing a separate winproc for each window, e.g. –  Nov 26 '15 at 14:54
  • @stackptr ... each window, then, can have different data (its own instance data) associated to it. –  Nov 26 '15 at 14:55