Search code examples
c++windowsclasswinapiwndproc

WinAPI WndProc silently fails when using maps


First, let me say I'm new to using WinAPI, and I'm trying to learn the basics. That said, I'm trying to create a few objects to make my future work with WinAPI much easier. One of those is a Window Class...class. The other is, of course, a Window class.

I'm trying to make message processing into something simple, like myClassInst.addHandler(WM_PAINT, PaintFunction). For that, I figured I'd use a map that maps uints to function pointers. That seems fine.

But now, the messages aren't being processed. After some debugging, I found that whenever I try to use the map in any way inside my class's WndProc handler, it just silently fails. I get no compile errors, no runtime errors, no crashes; the function just immediately ends there until the next message comes in. I can't for the life of me figure out why. Maybe someone can help me?

Below is a snippet of my code, all in the private section of my WinClass class. The static dummyProc message-passer is the only way I found to allow a class-specific WndProc, so that's there. That's working, as the first part of my WndProc myHandler does, in fact, work, as evidenced by the debug message's output. It just stops the second I try and use the map, even just to get its size.

*Note: WindowFunction is a typedef to a pointer to a function taking an HWND parameter.

  std::map<unsigned int, WindowFunction> messageMap;

  static LRESULT CALLBACK dummyProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    WinClass* context=(WinClass*)GetWindowLong(hwnd, GWL_USERDATA);
    return context->myHandler(hwnd, msg, wParam, lParam);
  }

  LRESULT CALLBACK myHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    std::cout<<"Message: "<<msg<<std::endl; // This gets output
    std::cout<<"  Contains "<<messageMap.size()<<" items"<<std::endl; // This does not
    std::cout<<"  And me!"<<std::endl; // Nothing works below the map usage.

    for (std::map<unsigned int, WindowFunction>::iterator it=messageMap.begin(); it!=messageMap.end(); ++it) {
      std::cout<<"  Have: "<<it->first<<std::endl;
      if (msg==it->first) {
        (it->second)(hwnd);
        break;
      }
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
  }

EDIT So, after some further testing, I've discovered some more information. The context value is always 0, and while I don't understand how that would even allow the myHandler to get called, it is a problem. So I've changed my code to try and set the GWL_USERDATA from the WM_CREATE message...and what I've found is that my dummyProc is never getting the WM_CREATE message.

Below is the new dummyProc, complete with debugging output:

  static LRESULT CALLBACK dummyProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    std::cout<<"Create would be "<<WM_CREATE<<std::endl;
    std::cout<<"The message was "<<msg<<std::endl;
    if (msg==WM_CREATE) {
      SetLastError(0);
      SetWindowLong(hwnd, GWL_USERDATA, lParam);
      std::cout<<"ERROR: "<<GetLastError()<<std::endl;
    }
    long thelong=GetWindowLong(hwnd, GWL_USERDATA);
    std::cout<<"Long: "<<thelong<<std::endl;
    WinClass* context=(WinClass*)thelong;
    std::cout<<"Context: "<<context<<std::endl;
    return context->myHandler(hwnd, msg, wParam, lParam);
  }

What it says is that WM_CREATE has a value of 1, but the messages come in starting from 36, then 129 and 130. What happened to my WM_CREATE message?

Here's the code that creates the window itself:

hand = CreateWindowEx(WS_EX_CLIENTEDGE, myClass.myName, title, WS_OVERLAPPEDWINDOW, x, y, w, h, parent, NULL, inst, &myClass);

Most of those parameters are arguments passed to the Window class's constructor (where this code is). The myClass parameter is a WinClass instance. All WinClass instances set dummyProc as their WndProc handler. Testing shows that &myClass is indeed a valid non-NULL pointer when this is called.

So what's preventing the WM_CREATE message?


Solution

  • As @Ben Voigt says, you're not initialising your GWLP_USERDATA correctly, and you're also not handling the case when it hasn't yet been initialized.

    Note that even your revised code is not correct. On WM_CREATE the lParam value is not your user data pointer. It's a pointer to a CREATESTRUCT, one of the members of which is your user data value.

    Try the following to replace your dummyProc function:

    static LRESULT CALLBACK dummyProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
        if (uMsg == WM_NCCREATE)
            SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>( reinterpret_cast<LPCREATESTRUCT>(lParam)->lpCreateParams ));
    
        WinClass* context=(WinClass*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
        if (!context) return DefWindowProc(hwnd, msg, wParam, lParam);
        return context->myHandler(hwnd, msg, wParam, lParam);
      }