GetWindow - Infinite Loop?
|
|
Thread rating:  |
Joseph Geretz - 11 Mar 2008 00:24 GMT I have been using GetWindow recursively for about 6 - 7 years now, to search for a particular Window. We've never had a problem up until now.
Recently, a customer started reporting application hangs. We traced this down into our code to the point just before the first (outer) call into the recursive function.
A little Googling turns up the following:
-------------------- http://support.microsoft.com/kb/183009
You can list Windows, including Child Windows, using the GetWindow API. However, an application that calls GetWindow to perform this task risks being caught in an infinite loop... --------------------
There are plenty of hits on GetWindow and 'infinite loop', however every page is simply parroting the same little blurb. I've yet to see any explanation as to what precise circumstance will trigger an infinite loop when using GetWindow. The only circumstance I could see would be in the event that a call to GetWindow starting with a particular hWnd would return the same hWnd. Would this even be possible? Can a Window be both its own parent and child? Or can a circular parent child relationship exist between two or more Windows?
Does any one have any additional information to augment Microsoft's advice that an application that calls GetWindow to perform this task risks being caught in an infinite loop? Does Microsoft simply mean that recursion is inherently risky? Or is there something specific to GetWindow which will create a particular condition which will cause a recursive algorithm to enter an endless loop?
I've posted my function below. As you can see, the loop for siblings ends when a 0 is returned. This should take care for normal end-of-siblings or for any error encountered, since the defined functional return in an error condition is 0. So my own loop doesn't seem succeptible to inifinite looping. And I don't see how the recursion itself can be infinite unless it's possible for a circular parent child relationship within the Windows hierarchy. Is such a thing possible?
Thanks for any advice which you can provide.
- Joseph Geretz -
Public Function FindWindowLike(hWnds() As Long, _ WinTitles() As String, _ WinClasses() As String, _ Optional ByVal hWndStart As Long = 0, _ Optional ByVal WinTitle As String = "", _ Optional ByVal WinClass As String = "", _ Optional ByVal WinChildID As Long = 0, _ Optional ByVal CaseSensitive As Boolean = True) _ As Long
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' This function calls itself recursively. Static variables ' ' keep track of the level of recursion. ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Hold the RecLevel of recursion: Static RecLevel As Long ' Hold the number of matching windows: Static NumMatches As Long
Dim hWnd As Long Dim APIReturn As Long Dim bMatch As Boolean Dim sWinTitle As String Dim sWinClass As String Dim lID As Long
' Initialize if necessary: If RecLevel = 0 Then NumMatches = 0 ReDim hWnds(0 To 0) If hWndStart < 1 Then hWndStart = GetDesktopWindow() End If End If
If CaseSensitive = False Then WinTitle = UCase$(WinTitle) WinClass = UCase$(WinClass) End If
' Increase recursion counter: RecLevel = RecLevel + 1
' Priming action; Get first child window. hWnd = GetWindow(hWndStart, GW_CHILD)
Do Until hWnd = 0
' Search children by recursion: APIReturn = FindWindowLike(hWnds(), _ WinTitles, _ WinClasses, _ hWnd, _ WinTitle, _ WinClass, _ WinChildID, _ CaseSensitive)
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' OK, let's set the match flag to True. Then, as long ' ' as the match flag remains True, we'll apply each ' ' filter criteria in sequence. After applying all filters, ' ' if the match flag is still True, then we've got a match! ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
bMatch = True
If WinTitle <> "" Then sWinTitle = GetWinTitle(hWnd) If CaseSensitive = False Then sWinTitle = UCase$(sWinTitle) End If If Not (sWinTitle Like WinTitle) Then bMatch = False End If End If
If bMatch = True Then If WinClass <> "" Then sWinClass = GetWinClass(hWnd) If CaseSensitive = False Then sWinClass = UCase$(sWinClass) End If If Not (sWinClass Like WinClass) Then bMatch = False End If End If End If
If bMatch = True Then If WinChildID > 0 Then If GetParent(hWnd) <> 0 Then lID = GetWindowLW(hWnd, GWL_ID) If WinChildID <> lID Then bMatch = False End If Else bMatch = False End If End If End If
If bMatch = True Then ' If find a match, increment counter and ' add handle to array: NumMatches = NumMatches + 1 ReDim Preserve hWnds(0 To NumMatches) ReDim Preserve WinTitles(0 To NumMatches) ReDim Preserve WinClasses(0 To NumMatches) hWnds(NumMatches) = hWnd WinTitles(NumMatches) = GetWinTitle(hWnd) WinClasses(NumMatches) = GetWinClass(hWnd) End If
' Get next child window: hWnd = GetWindow(hWnd, GW_HWNDNEXT) Loop
' Decrement recursion counter RecLevel = RecLevel - 1
' Return the number of windows found: FindWindowLike = NumMatches
End Function
Thorsten Albers - 11 Mar 2008 01:45 GMT Joseph Geretz <jgeretz@nospam.com> schrieb im Beitrag <OPi7jWwgIHA.5900@TK2MSFTNGP02.phx.gbl>...
> Does any one have any additional information to augment Microsoft's advice > that an application that calls GetWindow to perform this task risks being
> caught in an infinite loop? Does Microsoft simply mean that recursion is > inherently risky? Or is there something specific to GetWindow which will > create a particular condition which will cause a recursive algorithm to > enter an endless loop? There are functions which may change the relationship between windows (e.g. SetParent()) or the Z-order. Windows may be created or destroyed by applications at any time. This IMHO may confuse the relationship between windows and therefore cause GetWindow() to hang in an infinite loop. Have a look at MSDN articles such as "Window Owners and Parents" , "Win32 Window Hierarchy and Styles", and "Owner-Owned Windows" (all included in the MSDN version shipped with VB 6.0).
 Signature ---------------------------------------------------------------------- THORSTEN ALBERS Universität Freiburg albers@ uni-freiburg.de ----------------------------------------------------------------------
mayayana - 11 Mar 2008 04:31 GMT I've read the same blurb. And EnumChildWindows will search through all levels of children with a convenient callback function. So why not just switch to using that?
> I have been using GetWindow recursively for about 6 - 7 years now, to search > for a particular Window. We've never had a problem up until now. [quoted text clipped - 168 lines] > > End Function Joseph Geretz - 11 Mar 2008 23:14 GMT > So why not just > switch to using that? You're in management, right? ;-)
Seriously, *just switching* cost the better part of the afternoon, and significantly rearchitects the solution.. (Is the common BAS module now possibly subject to reentrancy problems?) Anyway, I've gone ahead and done this because it does seem, from all our diagnostics, as though the GetWindow function has reached the limits of its usefulness. The revised code works initially which is nice, but obviously, the entire application needs serious regression testing because Window resolution is at the heart of our most core application features.
Ce la vie.
Thanks for your suggestion!
- Joseph Geretz -
> I've read the same blurb. And EnumChildWindows > will search through all levels of children with [quoted text clipped - 179 lines] >> >> End Function mayayana - 12 Mar 2008 04:11 GMT > You're in management, right? ;-)
> Seriously, *just switching* cost the better part of the afternoon, and > significantly rearchitects the solution.. "Rearchitects the solution"... So you would be in the marketing dept., then? :)
> (Is the common BAS module now > possibly subject to reentrancy problems?) I can't imagine where you'd need to call it concurrently, but I suppose it could be put into a class if you did need to. I've typically used it in a situation where I've got two module-scope variables - a string and a long. The long gets set to 0 and the string gets set to, say, a class name, before the window-hunting function is called. With each call to the EnumChildProc I'm then checking the class name of the window against the string variable. If I find my match I then set the long variable to that window's hWnd and return 0 from EnumChildProc to end the iteration. So when EnumChildWindows returns I've either got 0 or an hWnd in the module-scope long. (You probably already know all that by now.)
I think I was originally using GetWindow, checking class name with a Select Case in order to step down through a known window hierarchy. But then I noticed the same warning you had seen, and came across a posting by Raymond Chen noting that EnumChildWindows will follow all children down until they've all been returned. So that method turned out to be a lot easier. (I'm taking the doc's word for it that EnumChildWindows has its own built-in stability.)
Joseph Geretz - 12 Mar 2008 06:40 GMT > I can't imagine where you'd need to call it > concurrently, but I suppose it could be put into > a class if you did need to. That's news to me. You can put an API callback handler into a Class? I thought this has to be in a BAS module?
My solution differs from yours slightly in that I do not stop after the first match. My application searches for all qualifying windows and returns an array of qualifying hWnds. So I need to cycle through to the end in any case, building up an array as each qualifying window is found.
The existing function takes an array (actually 3 arrays) passed in as parametes. On the initial call (recursion level = 0) the arrays are redimmed to 0 and the recursive search begins. By the time the search unwinds, the array parameters contain hwnds and info for all qualifying entries.
The recoding was a bit of a pain since I wanted to remain compatible with existing clients. The arrays are passed in on the function call, and now these arrays, along with a plethora of search parameters are passed into the BAS module. The callback function now performs the array accumulation. When the Enum call finishes, the arrays must be scooped out of the BAS module and returned back as the parameters to the original call. Not a huge deal, but a significant number of lines of code were moved around.
Anyway, as soon as this passes QA, I'm going to apply for a transfer to marketing on your recommendation - I could use some relaxation!
:-) - Joe Geretz -
>> You're in management, right? ;-) > [quoted text clipped - 32 lines] > it that EnumChildWindows has its own built-in > stability.) mayayana - 12 Mar 2008 15:18 GMT > That's news to me. You can put an API callback handler into a Class? I > thought this has to be in a BAS module? Woops. AddressOf doesn't work inside a class? I would have thought that sending the function pointer could be done from anywhere, but maybe inside a class it's a relative pointer, like a vTable. (?) I guess I've never had an occasion to try it, so I don't know.
Karl E. Peterson - 12 Mar 2008 17:34 GMT >> That's news to me. You can put an API callback handler into a Class? I >> thought this has to be in a BAS module? [quoted text clipped - 4 lines] > pointer, like a vTable. (?) I guess I've never had an occasion > to try it, so I don't know. Nope, not directly. With callbacks that let you pass a DWORD back to yourself, there's an easy workaround, though. You just pass ObjPtr(Me), and the callback procedure is then enabled to directly recieve a reference. Something like this example taken from http://vb.mvps.org/samples/TimerObj . . .
In the class:
' Pass pointer to Me so we can return event to this instance. m_TmrID = SetTimer(m_hWnd, ObjPtr(Me), m_Interval, AddressOf TimerProc)
In the BAS module:
Public Sub TimerProc(ByVal hWnd As Long, _ ByVal uMsg As Long, _ ByVal oTimer As CTimer, _ ByVal dwTime As Long) ' Alert appropriate timer object instance. oTimer.RaiseTimer End Sub
Same strategy would work with most Enum* calls.
 Signature .NET: It's About Trust! http://vfred.mvps.org
mayayana - 12 Mar 2008 19:52 GMT Thanks. I never thought about that. Matthew Curland showed a similar method for UserControls to subclass themselves, but it hadn't occurred to me that calling out to the .bas might be required.
> Nope, not directly. With callbacks that let you pass a DWORD back to yourself, > there's an easy workaround, though. You just pass ObjPtr(Me), and the callback [quoted text clipped - 20 lines] > .NET: It's About Trust! > http://vfred.mvps.org Karl E. Peterson - 12 Mar 2008 20:02 GMT > Thanks. I never thought about that. Matthew > Curland showed a similar method for UserControls > to subclass themselves, but it hadn't occurred > to me that calling out to the .bas might be > required. I'd be interested in looking at that method by Matt. Do you recall, was it in his book or one of his VBPJ columns?
I'm not honestly sure why AddressOf (doesn't) work the way it does. Sorta smells of paternalistic protection from ourselves, doesn't it? I suspect they figured it was "safe" with BAS modules as the code pointer would be valid for the life of the app?
Thanks... Karl
>> Nope, not directly. With callbacks that let you pass a DWORD back to yourself, >> there's an easy workaround, though. You just pass ObjPtr(Me), and the callback [quoted text clipped - 20 lines] >> .NET: It's About Trust! >> http://vfred.mvps.org
 Signature .NET: It's About Trust! http://vfred.mvps.org
Jim Mack - 12 Mar 2008 20:20 GMT > I'm not honestly sure why AddressOf (doesn't) work the way it does. > Sorta smells of > paternalistic protection from ourselves, doesn't it? I suspect > they figured it was > "safe" with BAS modules as the code pointer would be valid for the > life of the app? I suspect it has to do with the fact that modules are static. Is the address of a function in a class distinct across all instances? What happens when it goes out of scope or is otherwise disposed of?
Of course there are ways around anything, but this smells more like "too hard" than "paternalistic". (-:
 Signature Jim
Karl E. Peterson - 12 Mar 2008 20:56 GMT >> I'm not honestly sure why AddressOf (doesn't) work the way it does. >> Sorta smells of [quoted text clipped - 6 lines] > address of a function in a class distinct across all instances? What > happens when it goes out of scope or is otherwise disposed of? Exactly. I would think, though, that the address of a function in a class *instance* would be static, wouldn't you? I suppose class instances may be juggled around in memory?
> Of course there are ways around anything, but this smells more like > "too hard" than "paternalistic". (-: I have a vague recollection of Matt Curland telling me otherwise. Could be I'm mistaken, and we were talking about something much easier, like inheritance. Just don't recall precisely. <g>
 Signature .NET: It's About Trust! http://vfred.mvps.org
Jim Mack - 12 Mar 2008 22:19 GMT >>> I'm not honestly sure why AddressOf (doesn't) work the way it >>> does. Sorta smells of [quoted text clipped - 11 lines] > a class *instance* would be static, wouldn't you? I suppose class > instances may be juggled around in memory? I think not, but to expose a function like that would break encapsulation. Anything not published in the interface should not be accessible (by theory). Again, that's not to say you couldn't find a way, but it would be a hack.
Having said that, if you exposed the function of interest as a method, its address would be in the vtable, which is at a known (or computable) offset from the ObjPtr. So, if you really needed to, I guess you could.
 Signature Jim
Karl E. Peterson - 12 Mar 2008 22:39 GMT >>>> I'm not honestly sure why AddressOf (doesn't) work the way it >>>> does. Sorta smells of [quoted text clipped - 21 lines] > computable) offset from the ObjPtr. So, if you really needed to, I > guess you could. I was definitely thinking you'd have to point to a public method of the object, of course. I guess the attempts I've seen at this are all based around that vtable notion, yeah. Calling back into the object is a PITA, but doable.
 Signature .NET: It's About Trust! http://vfred.mvps.org
Michael C - 19 Mar 2008 06:06 GMT > Exactly. I would think, though, that the address of a function in a class > *instance* would be static, wouldn't you? I know you'll know some or most of this but will explain it all for everyone's benefit.
There could be 1 million instance of a class but there is only 1 function shared by the 1 million instances that is always in the same location and can be called via a callback. The function will never move in memory and will not disappear if all the instances of a class are destroyed. I presume the function exists before any classes are created. The real reason that AddressOf doesn't work on class function is because it has 2 extra hidden parameters, the ObjPtr param and the return value which is returned as a byref param. The actual return value is the hResult which indicates to the caller that an error occurred. As an example, a function in a class like this:
public Function AddOne(ByVal Value As long) as Long
will actually look like this
public Function AddOne(ByVal ObjPointer As Long, ByVal Value as long, ByRef ReturnValue as Long) as Long
The ObjPointer parameter simply indicates to the function what instance of the class should be used to reference data. The reasons this can't be called using AddressOf are fairly obvious but it would be possible for MS to provide a proxy calling function but they didn't implement this.
The other interesting thing is the function wouldn't have to be private as the private functions are in the vtable also, they're just at the end (although this might only apply to PCode from what I remember).
> I have a vague recollection of Matt Curland telling me otherwise. Could > be I'm mistaken, and we were talking about something much easier, like > inheritance. Just don't recall precisely. <g> No, Matt got around it by providing a proxy function in assembly code.
Michael
Michael C - 19 Mar 2008 06:08 GMT > I suppose class instances may be juggled around in memory? Accidentally pushed ctrl-enter before answering this one. AFAIK, class instance are not moved around in memory. Anyone who used ObjPtr(Me) would be in a lot of trouble if they did :-)
Michael
>>> I'm not honestly sure why AddressOf (doesn't) work the way it does. >>> Sorta smells of [quoted text clipped - 17 lines] > be I'm mistaken, and we were talking about something much easier, like > inheritance. Just don't recall precisely. <g> Scott Seligman - 12 Mar 2008 21:47 GMT >> Thanks. I never thought about that. Matthew >> Curland showed a similar method for UserControls [quoted text clipped - 9 lines] >suspect they figured it was "safe" with BAS modules as the code pointer >would be valid for the life of the app? If AddressOf returned something for a class's method, how would anyone use it? There's only one function pointer for each bit of code in a function. If you were to call into a pointer returned by AddressOf, the code in your class would have no way to access "Me". When you call a function, VB has to pass the reference to the class, I'm sure you could come up with limited uses for AddressOf in a class, but for the general case of using it for a call back into some standard Windows API, it'd be useless.
Other languages have similar issues with passing pointers to functions in classes. The answer is always the same, either have a static function, or a function outside of the class, with enough intelligence to know how to call back into the object from some bit of data either passed into the callback, or maintained somewhere else.
 Signature --------- Scott Seligman <scott at <firstname> and michelle dot net> --------- Never underestimate the bandwidth of a station wagon full of tapes hurtling down the highway. -- Andrew S. Tanenbaum
mayayana - 13 Mar 2008 00:25 GMT > > Thanks. I never thought about that. Matthew > > Curland showed a similar method for UserControls [quoted text clipped - 4 lines] > I'd be interested in looking at that method by Matt. Do you recall, was it in his > book or one of his VBPJ columns? It's in his book and a limited example was in August 2001 VBPJ: Black Belt Programming: Provide Pointers to Class Functions
If you have the book there are sample projects on the CD, in PowerVB\Samples\CreateWindow.
If you have trouble finding it look for "InitPushParamThunk". That's the name of a function using inline assembly that's part of the very small amount of code needed to set up any number of UCs to subclass themselves and/or their consituent controls. The whole thing was presented in his book as a way to use owner-drawn windows that could be wrapped in the convenience of VB by "anchoring" them on a UC.
(I seem to remember a group discussion some time back about this, so I went to check your site. I was just looking at your HookMe sample, trying to figure out if that was a similar approach, but so far I don't get it. You're using classes to enable multiple subclasses? I've never seen GetProp and SetProp before.)
mayayana - 13 Mar 2008 15:49 GMT Woops. Never mind all of that. While Matthew Curland's code is interesting, and I'm glad I found the HookMe sample, we were talking about callbacks and I realized that I've wandered off into my own private subclassing Idaho. :)
> It's in his book and a limited example was in > August 2001 VBPJ: [quoted text clipped - 19 lines] > to enable multiple subclasses? I've never seen GetProp and > SetProp before.) Karl E. Peterson - 13 Mar 2008 19:51 GMT > Woops. Never mind all of that. While Matthew > Curland's code is interesting, and I'm glad I > found the HookMe sample, we were talking about > callbacks and I realized that I've wandered off into > my own private subclassing Idaho. :) Something appealing about that. Can't say for sure what it may be. <g>
 Signature .NET: It's About Trust! http://vfred.mvps.org
Karl E. Peterson - 13 Mar 2008 19:51 GMT >> > Thanks. I never thought about that. Matthew >> > Curland showed a similar method for UserControls [quoted text clipped - 22 lines] > be wrapped in the convenience of VB by "anchoring" > them on a UC. I'll definitely take a closer look. Thanks.
 Signature .NET: It's About Trust! http://vfred.mvps.org
Michael C - 19 Mar 2008 05:48 GMT >> I can't imagine where you'd need to call it >> concurrently, but I suppose it could be put into >> a class if you did need to. > > That's news to me. You can put an API callback handler into a Class? I > thought this has to be in a BAS module? http://mikesdriveway.com/programming
and look at the sample "VB6 API callbacks without addressof"
BTW, to solve your problem you could add some code in to check for an infinite loop and log the results. This would at least tell you if the parent was it's own child or if there was a relationship with more than 1 window.
Michael
Scott Seligman - 19 Mar 2008 06:51 GMT >>> I can't imagine where you'd need to call it >>> concurrently, but I suppose it could be put into [quoted text clipped - 6 lines] > >and look at the sample "VB6 API callbacks without addressof" Looks like the code's really at http://mikesdriveway.com/code/
Using some machine code, he's creating a stub function in memory that calls into the class function by cracking the vtable.
A little too hackish for my tastes, but I do give him points for a clever method to solve the problem.
 Signature --------- Scott Seligman <scott at <firstname> and michelle dot net> --------- It has been said that democracy is the worst form of government except all the others that have been tried. -- Sir Winston Churchill
Michael C - 19 Mar 2008 07:17 GMT >>and look at the sample "VB6 API callbacks without addressof" > > Looks like the code's really at > http://mikesdriveway.com/code/ Good pickup. I thought I changed that before posting.
> Using some machine code, he's creating a stub function in memory that > calls into the class function by cracking the vtable. Yes.
> A little too hackish for my tastes, but I do give him points for a > clever method to solve the problem. If the language did support callbacks in a class it would do something similar, although I do agree it is pretty hacky.
Michael
|
|
|