How can I make a callback function a member of my C++ class?


Instead of a Little Program today, I'm going to answer a Little Question. This is a common beginner question, but I figure I'll just spell it out right here for posterity.

First of all, you probably noticed that you can't do this:

class CountWindows
{
public:
  int CountThem();

private:
  BOOL CALLBACK WndEnumProc(HWND hwnd, LPARAM lParam);

  int m_count;
};

BOOL CountWindows::WndEnumProc(HWND hwnd, LPARAM lParam)
{
   m_count++;
   return TRUE;
}

int CountWindows::CountThem()
{
  m_count = 0;
  EnumWindows(WndEnumProc, 0); // compiler error here
  return m_count;
}

That's because the WNDENUMPROC is declared as a so-called free function, but member functions are not free. Neither are function objects (also known as functors) so you can't use a boost::function as a window procedure either. The reason is that member functions and functors need to have a hidden this parameter, but free functions do not have a hidden this parameter.

On the other hand, static methods are free functions. They can get away with it because they don't have a hidden this parameter either.

Win32 has a general principle that callback functions have a special parameter where you can pass any information you like (known as context or reference data), and that same value is passed back to your callback function so it knows what's going on. In practice, most people will pass a pointer to a class or structure.

In other words, the reference data parameter makes explicit what C++ hides (the this parameter).

class CountWindows
{
public:
  int CountThem();

private:
  static BOOL CALLBACK StaticWndEnumProc(HWND hwnd, LPARAM lParam);

  int m_count;
};

BOOL CountWindows::StaticWndEnumProc(HWND hwnd, LPARAM lParam)
{
   CountWindows *pThis = reinterpret_cast<CountWindows *>(lParam);
   pThis->m_count++;
   return TRUE;
}

int CountWindows::CountThem()
{
  m_count = 0;
  EnumWindows(StaticWndEnumProc, reinterpret_cast<LPARAM>(this));
  return m_count;
}

What we did was pass our this parameter explicitly as the reference data to the Enum­Windows function, and then in the callback, cast the reference data back to this so that we can use it to access our member variables.

If the Wnd­Enum­Proc is long, then it can get tedious typing pThis-> in front of everything, so a common follow-up technique is to make the static member function a wrapper that calls a normal member function.

class CountWindows
{
public:
  int CountThem();

private:
  static BOOL CALLBACK StaticWndEnumProc(HWND hwnd, LPARAM lParam);
  BOOL WndEnumProc(HWND hwnd);

  int m_count;
};

BOOL CountWindows::StaticWndEnumProc(HWND hwnd, LPARAM lParam)
{
   CountWindows *pThis = reinterpret_cast<CountWindows* >(lParam);
   return pThis->WndEnumProc(hwnd);
}

BOOL CountWindows::WndEnumProc(HWND hwnd)
{
    m_count++;
    return TRUE;
}

int CountWindows::CountThem()
{
  m_count = 0;
  EnumWindows(StaticWndEnumProc, reinterpret_cast<LPARAM>(this));
  return m_count;
}

Observe that by putting all the real work inside the traditional member function Count­Windows::Wnd­Enum­Proc, we avoid having to type pThis-> in front of everything.

This principle of using reference data to pass context through a callback is very common in Windows programming. We'll see a few more examples in the future, but I'm not going to jam all the beginner articles in a row because that would bore my regular readers.

Historical note: The term reference data was used in 16-bit Windows, but the Windows NT folks preferred to use the term context. You can tell which team introduced a particular callback function by seeing what they call that extra parameter.

Comments (25)
  1. Henke37 says:

    Remember to not truncate the pointer or otherwise screw this up.

  2. Ben says:

    Also, in the DDEML API this doesn't exist. So you have to use DdeSetUserHandle and DdeQueryConvInfo to be able to do this.

  3. a_random_passerby says:

    I'm guessing this only works for 32-bit applications. It wouldn't be terribly hard to create a map of <HWND, CountWindows *> to use this technique in 64-bit applications, but I would be curious if there's a way to pass a 64-bit value into common Windows API functions.

  4. Adam Rosenfield says:

    I was about to ask what happened to the int MSG and WPARAM wParam parameters, and then I realized that this was a callback from EnumWindows(), not a window message handler.  I find the latter to be a much more common question among inexperienced programmers — they want to make their WNDPROC a class member but don't understand the compiler error about the function pointer types being incompatible (or they try to cast it away and don't understand why it crashes).

  5. Matt says:

    Confusing as the name is, an LPARAM is actually a LONG_PTR, not a LONG. So this works on 64-bit too (i.e. there is no truncation).

    @AdamRosenfield: Anyone that uses a cast to get rid of a compiler warning without understanding what the compiler warning means should have a legal injunction obtained against them preventing them from going within 100ft of any computer. Casting to get rid of warnings is like putting duct-tape on your dashboard so you don't have to see the "check engine" light and hoping that means your engine is now OK.

  6. anon says:

    Also, there's a cute technique to implement static callbacks in a C++ class by making it a template class, and then templating the client class (which contains the callback code) on the server class (which gets the pointer(s) to the client's callback functions).

    I think it's used in the WTL?

    Of course, nowadays you could do the same thing with a bunch of fancy Boost code using functor monads or whatever the fashionable paradigm is ;-)

  7. Gabe says:

    It's a shame the designers of the APIs didn't have the presence of mind to pass the context as the first parameters of the callbacks. That would allow you to use member functions as callbacks with just a single cast when passing in the function pointer.

  8. EvanED says:

    "Neither are function objects (also known as functors) so you can't use a boost::function as a window procedure either."

    I actually wrote a little library to make this possible: you give it a boost::function (or something like that, I forget exactly) and it gives you a C function pointer with the same arguments as the function (i.e. no separate context arg). Behind the scenes it used libffi to dynamically generate a thunk that, when called, would add in the "missing" this parameter to the functor.

    It was more of a "can I really do this? yes, yes I can" project than one with practical implications. :-)

  9. John Doe says:

    @Gabe, @Martin Bonner, you can actually do it, but you must declare the method as stdcall.

    For instance, in x86, it makes sure that "this" is accessed like a regular stack argument, not from the ecx register.

    [Modern compilers will probably inline the member function, so you get your desired optimization for free. -Raymond]
  10. ChrisR says:

    @John Doe: Indeed.  See this post by Raymond for more details:

    blogs.msdn.com/…/49028.aspx

  11. Martin Bonner says:

    This works with the Microsoft C++ compiler (and it's very useful), but I'm pretty sure that it's not *actually* legal C++.  The problem is that the function pointer needs to have extern "C" linkage – and static member functions don't.

    @Gabe: It wouldn't have helped.  Member functions expect this to be passed in ecx, not as the first parameter of a normal function call.

  12. Myria says:

    I personally prefer the use of static_cast over reinterpret_cast to go from void * to other pointer types, because if someone else changes the code to have void * be a different pointer type from under you, you'll get a compiler error instead of hard-to-find badness.

    Martin is correct that StaticWndEnumProc *technically* needs 'extern "C"' to be standard-compliant C++, but in reality, unless you are hitting a few rare corner cases, on most systems non-member C++ functions are linkage-compatible with C functions of the same signature.  Too much code relies upon this.  (If there were an equivalent to extern "C" that gave a function C linkage but not C name mangling – because you don't need C code to know its name – more code would be compliant.)

  13. alegr1 says:

    @Myria:

    'extern "C"' is linkage specification and doesn't affect binary calling convention. The compiler takes care of checking binary calling convention and would not allow mismatch.

    Re: reinterpret_cast: Raymond is in love with it.

    [All of the reinterpret_casts here are between LPARAM (an integral type) and pointer. What other cast would you have used? -Raymond]
  14. Georg_Rottensteiner says:

    How's the validity of fastdelegate (http://www.codeproject.com/…/Member-Function-Pointers-and-the-Fastest-Possible)?

    I use it a lot as it allows handling static, free-floating and even virtual member functions.

  15. Azarien says:

    Window procedure can be a lambda:

         wc.lpfnWndProc = [](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT

         {

            return DefWindowProc(hWnd, uMsg, wParam, lParam);

         };

    I don't know if this is officially supported, but it compiles and works.

    But you cannot take anything in the closure, unfortunately…

  16. SYS64738 says:

    To those who suggest static_cast instead of reinterpret_cast: I believe static_cast would not compile. In fact, LPARAM is defined as LONG_PTR, so it's a 32-bit integer ("long") on 32-bit builds, and a 64-bit integer ("__int64") on 64-bit builds. I believe the correct cast in this case is what Raymond uses: reinterpret_cast.

  17. Neil says:

    Someone mentioned templates, so I tried to come up with a templated version, but the best I could do was this:

    int CountWindows::CountThem()

    {

     m_count = 0;

     EnumWindowsTemplate<CountWindows, &CountWindows::WndEnumProc>(this);

     return m_count;

    }

  18. Maurits says:

    @SYS64738 Heck, skip pThis altogether and do:

    return reinterpret_cast<CountWindows *>(lParam)->WndEnumProc(hwnd);

  19. SYS64738 says:

    @Murits[MSFT]: No. The code you proposed seems to me much less readable than having a "pThis" variable, that makes it clear that the code is extracting the C++ "this" pointer from LPARAM.

  20. Joker_vD says:

    [All of the reinterpret_casts here are between LPARAM (an integral type) and pointer. What other cast would you have used? -Raymond]

    The only true cast there is: the C-style cast.

  21. Rick C says:

    @SYS64738, you are correct, Visual C++ throws a C2440 error. ('static_cast' : cannot convert from 'LPARAM' to 'CountWindows *',  Conversion from integral type to pointer type requires reinterpret_cast, C-style cast or function-style cast) once you put back the missing *.

    It's interesting that Raymond didn't fix the typo and delete the comments pointing it out, like he usually does.

    [Typos require me to wrangle my homegrown content management system so I usually batch them up. -Raymond]
  22. Paramanand Singh says:

    Thanks a lot Raymond for explicitly writing out this most useful technique. I have seen many beginners struggling with this problem where they pass a member function in place of a callback. Although the idea behind this technique (the difference between normal functions and member functions) is simple, it takes a bit of patience for beginners to learn and appreciate it. And by the way I am not sure why most of the comments have drifted onto a different topic about type casting the context parameter. In my opinion the issue of type-casting here is much less significant compared to the technique of using static wrapper functions for callback.

  23. John Doe says:

    @Paramanand Singh, people like to go off-topic quite a lot.

    But on topic…

    @Gabe, it's not really the job, responsibility or shame-covering moral-obligation of API designers to think of special cases that induce users (in this case, developers) to apply shimmy techniques, such as directly providing a C++ member function pointer (even if declared as stdcall) as a C callback. Just because it's possible doesn't mean it's right. The right thing to do is what this blog post describes in detail, so it's enough to design a way to provide user data to a callback.

    The Windows APIs that have a callback usually have some way of passing information to the callback. One is in the form of an extra parameter, such as EnumWindows and CreateThread. Another is in the form of providing structure objects that include the API structure at the head, such as the OVERLAPPED structure that is passed to IO completion routines. Providing your own COM interface pointer is almost the same thing as your own C++ object or C-struct with function pointers (plus marshalling when needed).

    A better complaint about shameful design would be to find out the API that take a callback but don't have a way to provide data to it, without a closure, that is. This may prove hard to accomplish correctly for every platform you target or require some complex library for portability just for the purpose. Then, there's memory management e.g. when do the closed-over variables stop referencing valid objects, can the closure outlive the stack point where it was created, is the data thread-bound, etc.

  24. Joshua says:

    @Azarian: I'm sure you know that lambdas don't exist by the time you reach link time.

Comments are closed.