0

I've been looking at creating a custom control with WinApi for my application, and I have made a class which contains the CustomDialogProc and CreateWindowEx and RegisterClass() functions.

I can set a breakpoint inside the CustomDialogProc and it hits, so the class is registered correctly.

However, I have to declare the CustomDialogProc function as static int he header of my class

static LRESULT CALLBACK CustomDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam,LPARAM lParam);

If I don't set it to static, I get the error

Error   C3867   'CustomControl::CustomDialogProc': non-standard syntax; use '&' to create a pointer to member   

IS this necessary, this requires all my controls created within this control to be static as well. What if I want multiple instances of this control? How can I get around this? The main MsgProc doesn't seem to be a static function. Neither is the UpDownDialogProc in the first link shown below

Below is my code for CustomControl.h in case anyone needs it. Put together from code found at: https://msdn.microsoft.com/en-us/library/windows/desktop/hh298353(v=vs.85).aspx https://www.codeproject.com/Articles/485767/True-Windows-control-subclassing

Thanks,

#pragma once
#include <windows.h>

#include <commctrl.h>

#pragma comment(lib, "comctl32.lib")

class CustomControl
{
public:
    CustomControl();
    ~CustomControl();

    LRESULT CALLBACK CustomDialogProc(HWND hWnd, UINT uMsg, WPARAM wParam,LPARAM lParam)
    {
        switch (message)
        {
            case WM_CREATE:
            //DO STUFF HERE
            break;
        }
    }

    bool CreateControl(HWND hwnd, HINSTANCE* m_hApp_instance)
    {
        g_hInst = m_hApp_instance;

        RegisterSubClass(*g_hInst, WC_LISTBOX, TEXT("CustomControl"), CustomDialogProc);

        HWND hwndCustom = CreateWindow(TEXT("CustomControl"), NULL, WS_CHILD | WS_VISIBLE,
        0, 0, 0, 0, hwnd, (HMENU)100, *g_hInst, NULL);

        return true;
    }

private:

    HINSTANCE* g_hInst;

    WNDPROC RegisterSubClass(HINSTANCE hInstance, LPCTSTR ParentClass, LPCTSTR ChildClassName, WNDPROC ChildProc) {
        WNDCLASSEX  twoWayStruct;
        WNDPROC     parentWndProc;

        if (GetClassInfoEx(NULL, ParentClass, &twoWayStruct)) {
            parentWndProc = twoWayStruct.lpfnWndProc; // save the original message handler 

            twoWayStruct.cbSize = sizeof(WNDCLASSEX); // does not always get filled properly
            twoWayStruct.hInstance = hInstance;
            twoWayStruct.lpszClassName = ChildClassName;
            twoWayStruct.lpfnWndProc = ChildProc;

            /* Register the window class, and if it fails return 0 */
            if (RegisterClassEx(&twoWayStruct))
                return parentWndProc; // returns the parent class WndProc pointer;
                                      // subclass MUST call it instead of DefWindowProc();
                                      // if you do not save it, this function is wasted
        }
        return 0;
    }
};
Mich
  • 3,188
  • 4
  • 37
  • 85
  • DialogProcs (and similar callbacks) cannot be non-static member functions, as they need to be passed to C APIs, which don't understand such things. –  Feb 10 '17 at 20:26
  • 1
    Since you have Visual Studio installed you should read the MFC source code. It's one of the few implementations that get this right (the accepted answer doesn't). – IInspectable Feb 10 '17 at 21:29
  • To be honest, writing a good Windows API wrapper is a full-time job. This isn't something that can be slapped together so easily. The API is complex enough with all sorts of twists and turns, and if you don't know C++ enough (enough to be at least close to advanced level), that in itself is a hurdle to climb. – PaulMcKenzie Feb 10 '17 at 22:05
  • Possible duplicate of [Class method for WndProc](http://stackoverflow.com/questions/21727015/class-method-for-wndproc) – Raymond Chen Feb 11 '17 at 00:08

2 Answers2

2

The most common way is to use SetWindowLongPtr to store a pointer to the object associated with the window handle.

HWND hWnd = CreateWindow(...);
SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) this);

And then in your dialog proc, get that pointer and call into your class:

// this static method is registered with your window class
static LRESULT CALLBACK CustomDialogProcStatic(HWND hWnd, UINT uMsg, WPARAM wParam,LPARAM lParam)
{
    auto pThis = (CustomControl*) GetWindowLongPtr(hWnd, GWLP_USERDATA);
    if (pThis != NULL)
        return pThis->CustomDialogProcInstance(hWnd, uMsg, wParam, lParam);
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

// this instance method is called by the static method
LRESULT CustomDialogProcInstance(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    ...
}

Make sure you manage your window and class life cycle appropriately to prevent the window proc from calling a deleted object instance. In many cases, this is as simple as ensuring DestroyWindow is called if your class is destructed.

Michael Gunter
  • 12,528
  • 1
  • 24
  • 58
  • Thanks, I'll try to figure this out before I mark it as an answer – Mich Feb 10 '17 at 20:39
  • I tried it and it compiled, but in the line: auto pThis = (CustomControl*)GetWindowLongPtr(hWnd, GWLP_USERDATA); pThis is always NULL – Mich Feb 10 '17 at 20:46
  • @Kenneth - because you not call `SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);` – RbMm Feb 10 '17 at 20:48
  • I did call `SetWindowLongPtr`, right after CreateWindow. Oddly enough, if I set a breakpoint on return `pThis->CustomDialogProcInstance(hWnd, uMsg, wParam, lParam); ` It does trigger, but only when I close my application, not while it's running. However, I hit breakpoints while running on `if (pThis != NULL)` it's just pThis is NULL until program closes for some reason – Mich Feb 10 '17 at 20:50
  • @Kenneth you can pass this in place last param of `CreateWindowEx` and got it on `WM_NCCREATE`, another way use tls for example - save pointer to *this* in known tls slot and got it back in windowproc – RbMm Feb 10 '17 at 20:56
  • Some messages will come into your message proc before `CreateWindow` returns. At this point, `SetWindowLongPtr` hasn't yet been called. That's why there is the null check. In many cases, you don't need any instance data until well after the `CreateWindow`, so that's OK. In cases where you do need the instance data, use the `lpParam` argument in `CreateWindowEx`. – Michael Gunter Feb 10 '17 at 21:14
  • `GLWP_USERDATA` is useless, since everyone and their dog abuse it, to implement their productivity tools. Since this user is registering their own window class, they should allocate a pointer-sized memory area (`cbWndExtra`) for this. Also, as proposed, this code misses a number of messages (e.g. `WM_CREATE`). Setting up a CBT hook allows you to go for those early messages as well. – IInspectable Feb 10 '17 at 21:27
  • This is weird. If I comment out `if (pThis != NULL)` the `return pThis->CustomDialogProc(hWnd, uMsg, wParam, lParam);` line actually works. And it enters my `CustomDialogProc` But that means `pThis == NULL`, AND I can call other functions from CustomDialogProc, However, I cannot change any variables inside my class. I get the error `this was nullptr.` how does one explain this? – Mich Feb 10 '17 at 21:36
  • @Kenneth It is undefined behavior. Commenting out lines that you know are required, and then see things "work" is not a working program. – PaulMcKenzie Feb 10 '17 at 21:43
  • @IInspectable: I've always been wary of `cbWndExtra` because it's so poorly documented. As far as I've been able to tell, `SetWindowLongPtr(hwnd, 0, ...)` / `GetWindowLongPtr(hwnd, 0)` are the only way to set this data (and I'm only guessing that `0` is the correct flag). If I have `cbWndExtra` that is greater than `sizeof(LONG_PTR)`, how do I access that memory? – Michael Gunter Feb 10 '17 at 22:12
  • @MichaelGunter: [GetWindowLongPtr](https://msdn.microsoft.com/en-us/library/windows/desktop/ms633585.aspx) documents `nIndex` as *"the zero-based offset to the value to be retrieved. Valid values are in the range zero through the number of bytes of extra window memory, minus the size of an integer."* It's just an array of bytes that you can index like any other array of bytes. (The "minus the size of an integer" part is misleading, and based on the assumptions that you are calling `GetWindowLong` and storing integer-sized data only. Both of these assumptions are wrong.) – IInspectable Feb 10 '17 at 22:25
  • Ah, got it. That is so awkward. You have to take your data in bytes, chunk it up into segments that are `sizeof LONG_PTR` wide, write it at offsets of `sizeof LONG_PTR` multiples, then read it back in a similar fashion. Not fun! Thanks for the info. – Michael Gunter Feb 10 '17 at 22:34
  • @MichaelGunter: You *can* do that. However, it is way more common to store pointers to dynamically allocated structures in the extra window memory. Since structures can hold arbitrary amounts of data, it is also common to only ever need to store a single pointer. That is why you often see `0` as the *nIndex* argument. You can store arbitrary data, but the API is optimized for storing and retrieving pointers (or pointer-sized data). – IInspectable Feb 11 '17 at 10:35
  • 1
    `SetWindowSubclass()` is a [much safer option](https://blogs.msdn.microsoft.com/oldnewthing/20031111-00/?p=41883) than using `SetWindowLongPtr()`. You can pass `this` in the callback's `dwContext` and not have to store it in the `HWND` itself at all. – Remy Lebeau Feb 12 '17 at 03:27
  • I tried using SetWindowSubclass, but I keep having a problem passing my custom proc `CustomControl::CustomDialogProc': non-standard syntax; use '&' to create a pointer to member` and it's declared as `LRESULT CALLBACK CustomControl::CustomDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);` – Mich Feb 13 '17 at 17:43
  • It needs to be `static`. – Michael Gunter Feb 13 '17 at 17:44
  • I found this answer http://stackoverflow.com/questions/40823536/subclass-a-button-within-a-custom-class-using-setwindowsubclass and it worked for me, but I'm still stuck with a static function or a non-member function. I need to have a class member function `CustomDialogProc` so that I can create controls within the class, or am I approaching this incorrectly. So I still need to use the SetWindowLongPtr ? – Mich Feb 13 '17 at 17:54
  • There's no way to do this without statics. Period. With `SetWindowSubClass`, you pass `this` as the `dwRefData` parameter. Then you get that value in your static `SUBCLASSPROC` callback as the `dwRefData` parameter. – Michael Gunter Feb 13 '17 at 17:57
  • Right, but what about your original answer with `SetWindowLongPtr`? I have to do that, correct? I thought `SetWindowSubClass` was a ComCtl32.dll version 6 replacement for `SetWindowLongPtr` – Mich Feb 13 '17 at 18:00
  • Do one or the other. Don't do both. – Michael Gunter Feb 13 '17 at 18:02
  • Ok, so here's my problem, from your code above, I have `auto pThis = (CustomControl*)GetWindowLongPtr(hWnd, GWLP_USERDATA); if (pThis != NULL) return pThis->CustomDialogProc(hWnd, uMsg, wParam, lParam);` and pThis always returns a nullptr and never enters `CustomDialogProc`, until I close the program, then it actually enters the CustomDialogProc. And I am sure it calls `SetWindowLongPtr(hwndCustom, GWLP_USERDATA, (LONG_PTR) this);` before. And my class is created in `WM_CREATE` of the main proc. Thnx – Mich Feb 13 '17 at 18:08
  • Ok I found my problem, if I do `hwndCustom = CreateWindow(TEXT("CustomControl"), WC_EDIT, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)100, *g_hInst, reinterpret_cast(this));` before `SetWindowLongPtr(hwndCustom, GWLP_USERDATA, (LONG_PTR) this);` the static `CustomDialogProcStatic` is called before SetWindowLongPtr, but if I flip them around, then `SetWindowLongPtr` returns error code 1400. So it was only calling it WC_CREATE, but I didnt have any controls in my class yet. When I added and used a trackbar, it went into the class member non-static Proc. Thanks for all your help! – Mich Feb 13 '17 at 18:58
  • @Kenneth: `WM_CREATE` (among other messages) is sent **before** `CreateWindow` returns. If you wish to handle all messages in your non-static window procedure, then do like MFC does (it installs a local CBT hook to receive callbacks as windows are created, **before** the system sends `WM_CREATE` and similar messages). – IInspectable Feb 14 '17 at 16:20
0

The Windows API is C language based. It knows nothing about C++, non-static member functions, objects, etc.

So yes, all of your functions that will communicate with the Windows API directly must be static class member functions, or non-class / global / "free" functions.

That doesn't preclude you from writing a C++ wrapper for a C API. That's what libraries such as Microsoft's MFC or the old Borland OWL libraries accomplish. Other independent groups have also written wrappers for the Windows API.

Note that these differing libraries accomplish the goal of hooking a C based API to C++ in different ways. One is to use the SetWindowLongPtr method mentioned in the answer given by @MichaelGunter. Another method is to use maps to associate window handles and static Window procedures.

I would suggest before you try this on your own (creating a wrapper), you investigate how others have done this already, and choose the best approach that fits. Another suggestion is that before you even create a wrapper, you should know the C based API on much more than a cursory level. You need advanced to expert knowledge of any C API you plan to create a C++ wrapper for if you want the wrapper to work relatively flawless under different scenarios.

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45