Using Lambdas in MFC Applications – Replacing Callback Functions

According to C++11 Standard, stateless lambdas, i.e. having an empty lambda introducer or capture no variables, are implicitly convertible to function pointers. Visual C++ in Visual Studio 2012 and newer, supports this feature. Moreover, in Visual C++ stateless lambdas are convertible to function pointers with arbitrary calling conventions. This is great if have to deal with Win32 API functions, most of them using __stdcall calling convention.
Let’s begin with two simple examples of enumerating top-level windows, one by using a callback function and the other by using a lambda expression.

Enumerate windows using “classic” callback functions

Usually, for that purpose we declare and implement as callback, a static member function having the signature required by EnumWindows in its first argument.

class CDemoDlg : public CDialog
{
    // ...

    static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam);    
};
BOOL CALLBACK CDemoDlg::EnumWindowsProc(HWND hWnd, LPARAM lParam)
{
    CDemoDlg* pDlg = reinterpret_cast<CDemoDlg*>(lParam);
    // do something, e.g. add the found window handle to a list
    return TRUE; // continue the enumeration
}

void CDemoDlg::OnButtonEnumWindows()
{
    ::EnumWindows(&CDemoDlg::EnumWindowsProc, reinterpret_cast<LPARAM>(this));
}

Notes

  • CALLBACK is defined in Windows SDK as __stdcall.

Enumerate windows using lambda expressions

The same result can be achieved by using a lambda expression. This is somehow simpler because does not require to declare and define a static member function.

void CDemoDlg::OnButtonEnumWindows()
{
    // in the first parameter of EnumWindows, pass a lambda expression
    ::EnumWindows([](HWND hWnd, LPARAM lParam) -> BOOL
    {
        CDemoDlg* pDlg = reinterpret_cast<CDemoDlg*>(lParam);
        // do something, e.g. push the found window handle in a list.
        return TRUE; // continue the enumeration
    }, 
    reinterpret_cast<LPARAM>(this)); // second parameter of EnumWindows
}

Notes

  • as stated in the introduction, there is not necessary to explicitly convert the lambda expression to the function pointer required by EnumWindowsProc (WNDENUMPROC);
  • as stated in the introduction, there is not possible to capture neither local variables, nor this pointer; luckily, like in many other WinAPI callbacks, we can pass the necessary stuff in a parameter of type LPARAM or LPVOID.

Using nested lambda expressions

Let’s say we want to enumerate the top-level windows in a worker thread. We can write something like this:

void CDemoDlg::OnButtonEnumWindows()
{
    AfxBeginThread([](LPVOID lpParam) -> UINT
    {
        ::EnumWindows([](HWND hWnd, LPARAM lParam) -> BOOL
        {
            CDemoDlg* pDlg = reinterpret_cast<CDemoDlg*>(lParam);
            // do something, e.g. push the found window handle in a list
            return TRUE; // continue the enumeration
        }, 
            reinterpret_cast<LPARAM>(lpParam)); // second parameter of EnumWindows
        return 0;
    },
        reinterpret_cast<LPVOID>(this));  // second parameter of AfxBeginThread
}

We have passed lambdas both for callback parameter of AfxBeginThread and EnumWindows. EnumWindows is called in the first lambda body and takes a lambda as well.
This compiles with no problem in x64 builds, but may give a conversion error for the inner lambda in Win32 ones. That’s a little glitch which AFAIK has been fixed in Visual Studio 2015.
However, to be sure it compiles also in Visual Studio 2012/2013 – Win32 builds, we can simply cast explicitly the inner lambda.

void CDemoDlg::OnButtonEnumWindows()
{
    AfxBeginThread([](LPVOID lpParam) -> UINT
    {
        ::EnumWindows(static_cast<WNDENUMPROC>([](HWND hWnd, LPARAM lParam) -> BOOL
        {
            CDemoDlg* pDlg = reinterpret_cast<CDemoDlg*>(lParam);
            // do something, e.g. push the found window handle in a list
            return TRUE; // continue the enumeration
        }), 
            reinterpret_cast<LPARAM>(lpParam)); // second parameter of EnumWindows
        return 0;
    },
        reinterpret_cast<LPVOID>(this));  // second parameter of AfxBeginThread
}

Notes

  • x64 builds has no __stdcall calling convention; __stdcall keyword is still accepted but simply ignored by the compiler.
  • of course, we can use lambda expressions in the same way also in ATL applications or C++ programs that use plain WinAPI.

Resources and related articles

Leave a Comment