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?
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,
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.