Home > Back-end >  How to use EnumWindows with a lambda?
How to use EnumWindows with a lambda?

Time:12-20

How to call the winapi EnumWindows in a lambda? My attempt:

        DWORD parentId;
        // ...
        HWND parent_hwnd;
        EnumWindows([parent_hwnd](HWND hwnd, LPARAM lParam)
        {
            DWORD lpdwProcessId;
            GetWindowThreadProcessId(hwnd,&lpdwProcessId);
            if(lpdwProcessId == lParam)
            {
                parent_hwnd = hwnd;
                return FALSE;
            }
            return TRUE;
        }, (LPARAM)&parentId);

CodePudding user response:

EnumWindows() takes a plain ordinary C-style function pointer for its lpEnumFunc parameter. However, a capturing lambda carries state information and thus is NOT convertible to a function pointer, since there is nowhere for the lambda to store its state in such a pointer. Only a non-capturing lambda, which has no state, is convertible to a function pointer.

Note that you have two bugs in your original lambda, which would have caused your code to fail even if you could have used a capturing lambda as the callback:

  • You are capturing the caller's parent_hwnd variable by-value, which means the lambda makes a copy and won't modify the value of the caller's original variable. You would need to capture the variable by-reference instead. Not that it matters in this case, since you can't use a capturing lambda anyway.

  • (this one affects a non-capturing lambda, too) You are passing the caller's parentId variable by DWORD* pointer to the callback's LPARAM, but then you are treating the LPARAM's value as-is, which means your comparison to each window's process ID will never find a match. You would need to cast the LPARAM to a DWORD* pointer and then dereference that pointer in order to get the correct DWORD value for the comparisons, eg if (lpdwProcessId == *(DWORD*)lParam). Otherwise, just pass in the caller's parentId variable by-value instead of by-pointer, then you don't have to worry about this casting. Not that it matters in this case, since you need to pass in more than just a single DWORD to the callback in order for your logic to work.

As such, you need to use a non-capturing lambda, and use the callback's LPARAM parameter to pass around all of your lambda's state information.

Try something more like this instead:

struct enumInfo {
    DWORD procId;
    HWND hwnd;
};

enumInfo info;
info.procId = ...;
info.hwnd = NULL;

EnumWindows(
    [](HWND hwnd, LPARAM lParam)
    {
        enumInfo *info = reinterpret_cast<enumInfo*>(lParam);

        DWORD dwProcessId;
        GetWindowThreadProcessId(hwnd, &dwProcessId);

        if (dwProcessId == info->procId)
        {
            info->hwnd = hwnd;
            return FALSE;
        }
        return TRUE;
    },
    reinterpret_cast<LPARAM>(&info)
);

HWND parent_hwnd = info.hwnd;
// use parent_hwnd as needed...

CodePudding user response:

This is usually what I do when working with C APIs, provided they allow you to pass a context pointer (lParam in this case):

template<typename T>
BOOL EnumWindowsWrapper(const T &callback)
{
    return ::EnumWindows(
        [](HWND hWnd, LPARAM lParam)
        {
            auto &cb = *reinterpret_cast<decltype(&callback)>(lParam);
            return cb(hWnd, lParam);
        },
        (LPARAM)&callback);
}
  • Related