1

I'm trying to understand the hierarchy of window handles on Windows (Win32's HWNDs.) So my question is, say if I have a starting HWND, how do I build its relational tree with Win32 APIs? Akin to what spy++ tool does in Visual Studio:

enter image description here

I tried searching but all the references I'm getting is how to implement a window finder tool in spy++.

MikeF
  • 1,021
  • 9
  • 29
  • 4
    `EnumWindows()` and `EnumChildWindows()` – Ben Voigt Feb 04 '18 at 21:49
  • 2
    Besides the HWND tree, the other information in your Spy++ screenshot comes from `GetWindowText()`, `GetClassName()`, and `IsWindowEnabled()`/`IsWindowVisible()` (for the icon) – Ben Voigt Feb 04 '18 at 21:54
  • @BenVoigt - no, `EnumChildWindows` is wrong solution, because it enumerate all child (direct and indirect). so order will be lost. need use `GetWindow` with `GW_CHILD` and `GW_HWNDNEXT`. and begin from `GetDesktopWindow`. exactly this and do spy++. and begin from win8 - not forget have `disableWindowFiltering` in manifest – RbMm Feb 04 '18 at 22:06
  • 1
    @RbMm: `GetWindow` specifically says not to use it for this purpose. "The EnumChildWindows function is more reliable than calling GetWindow in a loop. An application that calls GetWindow to perform this task risks being caught in an infinite loop or referencing a handle to a window that has been destroyed." – Ben Voigt Feb 04 '18 at 22:07
  • 1
    @RbMm: To distinguish immediate children from indirect children, better to go ahead and use `EnumChildWindows` and also call `GetParent` on each. – Ben Voigt Feb 04 '18 at 22:08
  • @BenVoigt - no, exactly `GetWindow` use spy++, if you debug it code. at it is the best choice – RbMm Feb 04 '18 at 22:08
  • @RbMm: A lot of Microsoft code doesn't follow best practices or is outright buggy. When the documentation says don't do something, you should avoid it, even if a Microsoft tool ignores that. Doubly true when the documentation explains exactly why it should be avoided. – Ben Voigt Feb 04 '18 at 22:12
  • this is the tool. if it very rarely display not full correct windows tree - this is not critical. for infinite loop need that after we got some *hwnd* and before call `GetWindow(hwnd, *)` - it must be destroyed, new windows is created with same *hwnd* + how minimum 1 child for this window.. and this is not once - but infinite during enumeration. except that it's incredible, very easy defend from this by restrict recursion level and windows count. handle of windows which destroyed - at all not problem. this is not invalid pointer - simply query on this invalid *hwnd* return false. – RbMm Feb 05 '18 at 01:38
  • @BenVoigt anyway with `EnumChildWindows` we also can got already destroyed(invalid) windows in list. or even another window will be created with same *hwnd* - in this case we also build wrong tree. from another side - build tree structure with `GetWindow` much more easy compare `EnumChildWindows` - may be because this exactly `GetWindow` used in spy++. and i think this ir reasonable choice for tool – RbMm Feb 05 '18 at 01:38
  • @BenVoigt: Ben, I checked both `Enum*` functions that you suggested but I don't fully understand how can I get a relationship between windows that they return. I'll just get a long array of window handles, and then what? – MikeF Feb 05 '18 at 01:42
  • @RbMm: Yes, `GetWindow(hwnd, GW_NEXT)` on the destroyed window returns false, and then you lost the rest of the list of siblings. – Ben Voigt Feb 05 '18 at 02:09
  • @MikeF: Each window should appear in the list ahead of its children, due to the way `EnumChildWindows` walks the hierarchy. So it should be sufficient to have a second array equal in length and start filling it with `HTREEITEM` handles. For every child HWND in the array, call `GetParent` and look at the previous half of the array to find the `HTREEITEM` that matches the parent's `HWND` – Ben Voigt Feb 05 '18 at 02:11
  • @BenVoigt - however with `GetWindow` all became much more simply - heed have recursive function `AddWindow` and call it like `if (hwnd = GetWindow(hwnd, GW_CHILD)) { do { AddWindow(hwnd, Data); } while (hwnd = GetWindow(hwnd, GW_HWNDNEXT)); }` - this is how spyxx do this - https://i.imgur.com/mWHiYS7.png – RbMm Feb 05 '18 at 02:20
  • minimal code with build window tree - https://pastebin.com/AGhFrG3N – RbMm Feb 05 '18 at 02:24
  • and result - https://image.prntscr.com/image/B8tgjQWKQRKw9jQbrOuTRQ.png – RbMm Feb 05 '18 at 02:35
  • @RbMm: Yes, if you don't care that one window being destroyed during the walk can impact its siblings. And the `EnumChildWindows` code is not complicated if one uses a `std::map`. – Ben Voigt Feb 05 '18 at 02:46
  • @RbMm: Also, if window parentage is changing during the walk (for example, a "dockable" child window that switches between being an MDI document and being a toolbar -- in both cases it is a descendant of the main frame) then `EnumChildWindows` + `GetParent` will see it and its children correctly added to one or the other part of the tree, while `GetWindow` recursion will.. be a mess – Ben Voigt Feb 05 '18 at 02:50
  • Oh, wow, thank you, both. Quite an interesting discussion. Also thanks @RbMm for your code sample. – MikeF Feb 05 '18 at 03:31
  • @BenVoigt - after `EnumChildWindows` build windows list (via `NewNtUserBuildHwndList`) and before callback will be called - some window can be destroyed and new created with the same handle. so hypothetically we also can build wrong tree in this case. but for *tool* this is not critical. we can simply rebuild tree. spy++ used not reliable `GetWindow` - but are you frequently (or at all) view that spy++ build wrong tree ? – RbMm Feb 05 '18 at 08:20
  • @RbMm: Your opinion, that a tool need not be correct is just that: An opinion. In general, a tool also needs to be correct (e.g. a tool used for integration testing). I don't think that any given DevOps engineer would be fine with false negatives or - worse - false positives, just because the test was performed by a tool. – IInspectable Feb 05 '18 at 12:18
  • @IInspectable - i not say that tool can be incorrect. must be correct as possible. in this concrete case even `EnumChildWindows` can not get absolute "correct" result. after windows list is created - some of this can be just destroyed. even hypothetically created new window with same handle as old, destroyed, from list. and when we query it parent - we got not original (destroyed) window parent, but another. what is show spy++ - only snapshot on current time. some time (very rarelly) it can incorrect display some windows. but are you frequently view false tree from spy++ ? – RbMm Feb 05 '18 at 12:46
  • @RbMm: The fact that changes happen after EnumChildWindows creates the snapshot does not make the tree *wrong*, data becoming stale is a fact of life on a multitasking system. And the fact that it doesn't get parentage atomically is a possible cause for wrong results... but in the most common cases (new window with the same handle is not a common case, but window destruction is, and re-parenting is also possible) the result from EnumChildWindows will include (a) every relationship that existed during the whole interval and (b) no relationships that didn't exist. `GetParent` loop breaks (a). – Ben Voigt Feb 05 '18 at 15:01
  • @BenVoigt all this is clear. but almost always results from `GetWindow` the same. very rarely, if window destroy during enumeration, we can got incorrect tree. but not infinite loop. for such tool not critical if very rarely it show not relevant tree (if we doubt - we can rebuild tree). no major differents with result of `EnumChildWindows`. question patrially was how spy++ do it - it use `GetWindow`. you frequently view bad result from spy++ at this (build tree) stage ? – RbMm Feb 05 '18 at 18:12
  • @RbMm: I think what they say is that `EnumChildWindows` is safer because it creates a snapshot before returning window handles one-by-one into the callback function, while `GetWindow` called in a loop may create a potential deadlock if some other thread continuously changes the child window's z-order, or creates new child windows. Which is obviously rare but possible. **But** even if `EnumChildWindows` has this transient atomicity on that step, calling it again for the next non-child window will break atomicity of the overall snapshot a tool might be making. So Ben, how do you address that? – MikeF Feb 05 '18 at 21:41
  • @BenVoigt: Additionally I was trying to implement this "window walk" function using your suggested EnumWindows(), EnumChildWindows(), and then GetParent() for each child, but I'm still not sure of the correct implementation. Would you mind showing it with actual code? For instance, there could be more than one "generation" of child windows. So say if GetParent() returns a parent that I don't have in my array of collected windows. What should I do then? Also reading GetParent docs, I'm confused. What is owner window? Ancestor window? Parent window? How are they coming into picture? – MikeF Feb 05 '18 at 21:46
  • Sorry for asking too many questions. I just get more confused the more I look into it. Having @RbMm's code for this, I can clearly see his idea, which I agree with. I'm not following the enumeration process though, which should be safer during the call itself, but how do you deal with the results then... – MikeF Feb 05 '18 at 21:49
  • *may create a potential deadlock if some other thread continuously changes* - no this is can not be. `EnumChildWindows()` need call only once for `GetDesktopWindow()` – RbMm Feb 05 '18 at 21:57
  • if you want implementation with `EnumChildWindows` - https://pastebin.com/Q2kDxuAQ . note that you need use `GetAncestor(hwnd, GA_PARENT)` instead `GetParent(hwnd)` and you not need any `std::map` - exist much more elegant and light solution – RbMm Feb 05 '18 at 22:59
  • @RbMm: Pretty cool. Thank you. Let me review your code... – MikeF Feb 06 '18 at 02:11

0 Answers0