-2

I've been looking into how the Window hierarchy works and have found an inconsistency. The values returned by the two function calls GetParent(hwnd) and (HWND)::GetWindow(hwnd, GW_OWNER) though, mostly agree, don't always for top level windows.

The reason that I am assuming that these are top level windows, is because they were found using the EnumWindows() function, which are only to enumerate top level windows. It was also confirmed using the test hWnd==GetAncestor(hWnd,GA_ROOT) as specified in the answer to What's the best way do determine if an HWND represents a top-level window?.

I've seen this in window class #32770 in AVGUI.exe and window class ComboLBox in explorer.exe, notepad++.exe, TeamViewer.exe, PrivacyIconClient.exe, devenv.exe, ... and the list goes on.

GetParent(hwnd) would return a HWND of GetDesktopWindow(), but (HWND)::GetWindow(hwnd, GW_OWNER) would return nullptr. So if GetParent() should return the owner of a top level window, where is it getting that from when (HWND)::GetWindow(hwnd, GW_OWNER) is returning a nullptr?

It does agree with (HWND)::GetWindowLongPtr(hwnd, GWLP_HWNDPARENT), but that would indicate that it is a child window, which sort of makes sense as the window class for many are listed as ComboLBox. However, I've seen other HWNDs that have values where they should be, and it could be that the value is just being ignored, based on context. Another reason that these could have been non-top level windows at one point is that !(GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD) returns false.

With this extra analysis I've done, it would appear that some application is somehow promoting non-top level windows to top level windows, which would indicate some bug or there is using some undocumented/undefined behaviour going on and is resulting in odd HWND linkages.

Can anyone confirm that these are caused by bugs or is something that is being done for some legitimate reason?

Edit

Minimal, Complete, and Verifiable example:

#include <AtlBase.h> // Conversion routines (CW2A)
#include <Windows.h> // Windows stuff
#include <assert.h>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <set>
#include <psapi.h>
#include <regex>

auto FIELD_SEPERATOR = L",";

auto& output_window_class_and_title(std::wostream & os, const HWND &hWnd)
{
    wchar_t window_class[1024], window_title[1024];
    window_class[0] = window_title[0] = 0;

    ::GetClassNameW(hWnd, window_class, _countof(window_class));
    ::GetWindowTextW(hWnd, window_title, _countof(window_title));
    // replacing any CRLFs with field separators
    auto wc
        = std::regex_replace(window_class, std::wregex(L"(\r\n?|\n\r?)")
            , L" " );
    auto wt
        = std::regex_replace(window_title, std::wregex(L"(\r\n?|\n\r?)")
            , L" " );

    os << CW2A(wc.c_str()) << FIELD_SEPERATOR << CW2A(wt.c_str());
    return os;
}

// Store exe names
std::set<std::wstring> exe_names;

// Map pid to exe name
std::map<DWORD, std::wstring const*> pid_to_exe_name;

// Get exe name (from cache if possible)
const std::wstring * GetProcessName(DWORD pid)
{
    const std::wstring * pProcess_name = nullptr;
    auto it_found_pid = pid_to_exe_name.find(pid);
    if (it_found_pid == pid_to_exe_name.end()) {
        wchar_t exe_name[MAX_PATH]; exe_name[0] = 0;
        if (HANDLE hProcess = ::OpenProcess(
            PROCESS_ALL_ACCESS | PROCESS_QUERY_INFORMATION |
            PROCESS_VM_READ,
            FALSE, pid))
        {
            auto chars_copied = ::GetProcessImageFileNameW(hProcess, exe_name, _countof(exe_name));
            assert(chars_copied > 0);
            exe_name[chars_copied] = 0;
            ::CloseHandle(hProcess);
            auto found = exe_names.emplace(exe_name);
            pProcess_name = &*found.first;
        }
        else
        {
            auto found = exe_names.emplace(L"* Couldn't open process handle *");
            pProcess_name = &*found.first;
        }
        pid_to_exe_name.try_emplace(pid, pProcess_name);
    }
    else {
        pProcess_name = it_found_pid->second;
    }
}

int main()
{
    //auto* filename = "window-tree.txt";
    //static std::wfstream os(filename, std::ios_base::out | std::ios_base::trunc);
    static auto& os = std::wcout;
    os.exceptions(os.badbit | os.failbit | os.eofbit);
    os << std::hex;

    try {
        static HWND hDesktop = GetDesktopWindow();
        EnumWindows([](_In_ HWND hwnd, _In_ LPARAM lParam) -> BOOL
        {
            assert(hwnd);
            HWND hParent = ::GetParent(hwnd);
            if (hParent == hDesktop) {
                auto hOwner                   = (HWND)::GetWindow(hwnd, GW_OWNER);
                auto hParent                  = (HWND)::GetWindowLongPtr(hwnd, GWLP_HWNDPARENT);
                auto hParent_from_GetParent   = ::GetParent(hwnd);
                auto hParent_from_GetAncestor = ::GetAncestor(hwnd, GA_PARENT);
                bool is_top_level             = (hwnd == GetAncestor(hwnd, GA_ROOT));
                bool is_top_level2            = !(GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD);

                DWORD pid;
                auto tid = ::GetWindowThreadProcessId(hwnd, &pid);
                std::wstring const* pProcess_name = GetProcessName(pid);

                os
                                       << std::setw(8) << hwnd
                    << FIELD_SEPERATOR << ::IsWindowVisible(hwnd)
                    << FIELD_SEPERATOR << is_top_level
                    << FIELD_SEPERATOR << is_top_level2
                    << FIELD_SEPERATOR << std::setw(8) << hOwner
                    << FIELD_SEPERATOR << std::setw(8) << hParent_from_GetParent
                    << FIELD_SEPERATOR << std::setw(8) << hParent
                    << FIELD_SEPERATOR << std::setw(8) << hParent_from_GetAncestor
                    << FIELD_SEPERATOR << std::setw(4) << pid
                    << FIELD_SEPERATOR << std::setw(4) << tid
                    << FIELD_SEPERATOR << pProcess_name->c_str()
                    << FIELD_SEPERATOR;
                output_window_class_and_title(os, hwnd);
                os
                    << std::endl;
            }
            return TRUE;
        }
        , 0);
    }
    catch (std::ios_base::failure& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}

Example output:

000A0FF6,0,1,0,00000000,00010010,00000000,00010010,8520,6d44,\Device\HarddiskVolume2\Program Files (x86)\Notepad++\notepad++.exe,ComboLBox,
0094150C,0,1,0,00000000,00010010,00000000,00010010, 3ac,3f14,\Device\HarddiskVolume4\Windows\explorer.exe,ComboLBox,
0078181E,0,1,0,00000000,00010010,00000000,00010010,5e58,5068,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\Tools\spyxx_amd64.exe,ComboLBox,
00FA16AA,0,1,0,00000000,00010010,00000000,00010010, 3ac,242c,\Device\HarddiskVolume4\Windows\explorer.exe,ComboLBox,
01121B00,0,1,0,00000000,00010010,00000000,00010010,4440,7b98,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
021304D0,0,1,0,00000000,00010010,00000000,00010010,5e1c,5b5c,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
011A11EE,0,1,0,00000000,00010010,00000000,00010010,5e1c,5b5c,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
018D1B7A,0,1,0,00000000,00010010,00000000,00010010,5e1c,5b5c,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
0137042A,0,1,0,00000000,00010010,00000000,00010010,5e1c,5b5c,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
0028065A,0,1,0,00000000,00010010,00000000,00010010,5e1c,5b5c,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
005B0472,0,1,0,00000000,00010010,00000000,00010010,8520,6d44,\Device\HarddiskVolume2\Program Files (x86)\Notepad++\notepad++.exe,ComboLBox,
00421248,0,1,0,00000000,00010010,00000000,00010010,8520,6d44,\Device\HarddiskVolume2\Program Files (x86)\Notepad++\notepad++.exe,ComboLBox,
00BA10F8,0,1,0,00000000,00010010,00000000,00010010,8520,6d44,\Device\HarddiskVolume2\Program Files (x86)\Notepad++\notepad++.exe,ComboLBox,
009E0EE2,0,1,0,00000000,00010010,00000000,00010010,8520,6d44,\Device\HarddiskVolume2\Program Files (x86)\Notepad++\notepad++.exe,ComboLBox,
00040822,0,1,0,00000000,00010010,00000000,00010010, 3ac,3d94,\Device\HarddiskVolume4\Windows\explorer.exe,ComboLBox,
000404A4,0,1,0,00000000,00010010,00000000,00010010, 7e0, 7dc,\Device\HarddiskVolume4\Program Files (x86)\Intel\Intel(R) Management Engine Components\IMSS\PrivacyIconClient.exe,ComboLBox,
000404A0,0,1,0,00000000,00010010,00000000,00010010, 7e0, 7dc,\Device\HarddiskVolume4\Program Files (x86)\Intel\Intel(R) Management Engine Components\IMSS\PrivacyIconClient.exe,ComboLBox,
000102FE,0,1,0,00000000,00010010,00000000,00010010,19dc, 4c4,\Device\HarddiskVolume2\Program Files (x86)\TeamViewer\TeamViewer.exe,ComboLBox,
00010290,0,1,0,00000000,00010010,00000000,00010010,19dc, 4c4,\Device\HarddiskVolume2\Program Files (x86)\TeamViewer\TeamViewer.exe,ComboLBox,
00EF16B4,0,1,0,00000000,00010010,00000000,00010010,4440,7b98,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
00840D20,0,1,0,00000000,00010010,00000000,00010010,4440,7b98,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
016A0E64,0,1,0,00000000,00010010,00000000,00010010,4440,7b98,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
021B11F2,0,1,0,00000000,00010010,00000000,00010010,4440,7b98,\Device\HarddiskVolume4\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\devenv.exe,ComboLBox,
Community
  • 1
  • 1
Adrian
  • 10,246
  • 4
  • 44
  • 110
  • `GW_OWNER` is a valid parameter for the `GetWindow` function, not the `GetWindowLongPtr` function. – Jonathan Potter Nov 25 '18 at 21:56
  • @JonathanPotter, thanks. Typo. Fixed – Adrian Nov 25 '18 at 22:06
  • Doesn't look like a typo to me (and the title still has that *"typo"*). If you are passing `GW_OWNER` (value 4) to [GetWindowLongPtr](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowlongptrw), it will simply read out the value at offset 4 in the window extra data. For windows that don't allocate extra data in that offset, the value is indeterminate. For windows that do, the value's meaning is private to the window class implementation. It is unclear, what this question is asking about. – IInspectable Nov 26 '18 at 09:17
  • @IInspectable, Missed the title, thanks. It is a typo. You can check my code. – Adrian Nov 26 '18 at 21:40

1 Answers1

0

A window with a parent but no owner is not a top level window. That is the obvious explanation to the scenario that you describe.

Update

It seems that this is not the case because you now explain that the windows come from EnumWindows. It's documentation says:

The EnumWindows function does not enumerate child windows, with the exception of a few top-level windows owned by the system that have the WS_CHILD style.

I think therefore that these must be the windows that you describe. That is system owned top level windows that gave the WS_CHILD style.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    Also see [Five Things Every Win32 Developer Should Know](https://www.slideshare.net/mobile/wvdang/five-things-every-win32-developer-should-know), there is a section on "Parent and owner windows (review)" (slides #28-33). Slide #31 in particular has a diagram showing the difference between a parent and an owner. – Remy Lebeau Nov 25 '18 at 21:19
  • This an answer by shotgun? Yes, I know all of this, but still doesn't answer my question. These are top level windows, so `GetParent()` should give the owner. However, if the owner is set to null, then where is it getting the owner handle from? – Adrian Nov 25 '18 at 21:32
  • As the item was found using [EnumWindows()](https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-enumwindows), it _must be_ a top level window – Adrian Nov 26 '18 at 19:50
  • In the documentation for EnumWindows it says "The EnumWindows function does not enumerate child windows, with the exception of a few top-level windows owned by the system that have the WS_CHILD style." Are these the windows? Do they have the WS_CHILD style. – David Heffernan Nov 26 '18 at 20:13
  • Also note that the question did not mention EnumWindows until the recent edit. We have no idea where these window handles are coming from and there is still no [mcve] which would make it easy to explain. For all we know you are calling GetWindowLongPtr as per the question title. How about cleaning all this up? – David Heffernan Nov 26 '18 at 20:15
  • They do have `WS_CHILD` style, but are they owned by the system? The pid is that of other non-system processes, so I guess it would depend on if the definition is not dependent on that. – Adrian Nov 26 '18 at 22:00
  • System owned is pretty vague. As I said, the pid is that of the process. Can you explain further? – Adrian Nov 26 '18 at 22:23
  • So we have an unowned window, with WS_CHILD style, that has a parent. Seems quite reasonable. GetParent does not return null because the window has a parent. – David Heffernan Nov 26 '18 at 22:34
  • It's reasonable because it is a non-top most window that is in the top most group of windows? Ok, but why are they there? What is their purpose? – Adrian Nov 26 '18 at 22:42