PeekMessage and Filtered Nested Message Loops
|
|
Thread rating:  |
Jai Singh - 17 Sep 2008 19:43 GMT I am a non-MFC programmer that primarily works in .NET as well as legacy VB 6. In both of those high-level RAD language worlds, the platforms offer a DoEvents() function. It seems that DoEvents() is a non-filtered nested message pump that is used to flush the message queue. I have determined that DoEvents is essentially "evil" in most programs because of subroutine re-entrancy problems and the generation of unplanned for execution sequences (this opinion was confirmed via web searches and opinions provided by industry "leaders" as well as Microsoft Developers).
.NET provides the IMessageFilter interface which allows a programmer to create a filter on incoming messages before calling Application.DoEvents()... however in legacy VB 6 there is no such functionality.
Problem:
In some instances, it appears that programmers do in fact need to retrieve and dispatch a specific set of Windows Messages for a thread or hWnd at a particular point in time in order to put a specific control in a particular state. DoEvents should not be used because it retrieves and dispatches all messages irregardless of type or destination.
Solution:
In such an instance it seems that a programmer in a RAD language could utilize the Win32 API and its PeekMessage(), TranslateMessage(), DispatchMessage() functions to essentially create a filtered version of DoEvents.
I was wondering if there are any potential pitfalls in using PeekMessage() to create a nested message pump that only dispatches certain messages and does not dispatch others?
Thanks in advance, Jai
MikeD - 18 Sep 2008 01:23 GMT >I am a non-MFC programmer that primarily works in .NET as well as legacy VB >6. [quoted text clipped - 33 lines] > to create a nested message pump that only dispatches certain messages and > does not dispatch others? OK...FIRST...are you using VB6 (or under) or .NET? It's unclear because you say you work with both (but primarily .NET). If .NET, you need to ask in a newsgroup with "dotnet" or "vsnet" in the name. Regardless, it'd probably be helpful if you stated exactly what your ultimate goal is. Depending on whether you're using VB6 or .NET, the answer could be different (which is why you need to clarify this and post in the appropriate newsgroup).
THIS newsgroup is for VB6 and under only. We don't deal with .NET here.
As far as DoEvents in VB6, all it does it yield to Windows to give it (and your app) time to process messages I personally have never encountered a problem with DoEvents, except for calling it too often and slowing performance because the app is yielding too much. Although, I am aware of circumstances where DoEvents can be more detrimental. VB6's Help documents this. It states:
"Any time you temporarily yield the processor within an event procedure, make sure the procedure is not executed again from a different part of your code before the first call returns; this could cause unpredictable results. In addition, do not use DoEvents if other applications could possibly interact with your procedure in unforeseen ways during the time you have yielded control."
For the most part (there are other circumstances), in VB6 you most frequently need to use DoEvents in a loop when you also need your program to be responsive to user input (such as the user clicking a Cancel button to exit the loop prematurely). Other times you might use DoEvents to allow Windows time to redraw the screen or a portion of it (a low priority in Windows) BEFORE you enter into a tight loop (but invoking a control's or form's Refresh method is usually better than DoEvents in this case because it's more specific to what you need accomplished).
 Signature Mike Microsoft MVP Visual Basic
Jai Singh - 18 Sep 2008 11:58 GMT I thought that I was being SPECIFIC, but perhaps not enough FOR YOU so let me CLARIFY.
1st of all, I am concerned about DoEvents in Visual Basic 6.0 (your newsgroup grouping structure simply uses "Visual Basic" and "Windows API"). The pitfalls of DoEvents in VB 6 is a relatively worn topic:
http://blogs.msdn.com/tmiller/archive/2003/11/07/57524.aspx
http://blogs.msdn.com/jfoscoding/archive/2005/08/06/448560.aspx
http://discuss.fogcreek.com/joelonsoftware3/default.asp?cmd=show&ixPost=92327&ix Replies=20
http://discuss.joelonsoftware.com/default.asp?dotnet.12.481760.7
http://www.codinghorror.com/blog/archives/000159.html
I have read the VB 6 documentation and its explanation of DoEvents. The warning to watch for re-entrancy problems is understated at best. In multi-component, multi-tier applications of any complexity it is nearly impossible to avoid re-entrancy when a call to DoEvents is made inside of some component (i.e. one team's right hand doesn't know what the left hand of the other team is doing).
To my understanding DoEvents is essentially a wrapper for a Do loop that utilizes PeekMessage(), TranslateMessage(), and DispatchMessage(). Many non C programmers today are unaware of message pumps etc... but DoEvents is essentially a nested message pump.
I mentioned .NET because it has the IMessageFilter interface which allows a programmer to place a filter on the underlying message pump and even on the nested message pump that Application.DoEvents() uses. VB6 does not have this feature so I wanted to determine the best way to implement similar functionality in VB 6.
Again, DoEvents, if I understand it correctly, represents a non-filtered nested message loop. I want to created a filtered nested message loop which enables me to retrieve and dispatch a set of messages by message type or target (i.e. hwnd). MY QUESTION TO YOU IS: what are the potential pitfalls of creating such a filtering system using PeekMessage?
HERE IS AN EXAMPLE:
Public Function Pump(ByVal bRenderUIs As Boolean, ByVal colWinHandles As MTKList, ByVal colMessagesToDispatch As MTKList) As Boolean On Error Resume Next Dim tMSG As MSG Dim lRet As Long Dim lNum1 As Long Dim lNum2 As Long Dim lVal1 As Long Dim lVal2 As Long
'OBTAIN AND DISPATCH SPECIFIC MESSAGES FOR SPECIFIC WINDOWS If Not colWinHandles Is Nothing And Not colMessagesToDispatch Is Nothing Then For lNum1 = 1 To colWinHandles.Count lVal1 = colWinHandles.Item(lNum1) If IsValidWinHandle(lVal1) = True Then For lNum2 = 1 To colMessagesToDispatch.Count lVal2 = colMessagesToDispatch.Item(lNum2) If IsValidMessage(lVal2) = True Then lRet = PeekMessage(tMSG, lVal1, lVal2, lVal2, PM_REMOVE) If lRet = 0 Then TranslateMessage tMSG DispatchMessage tMSG End If End If Next lNum2 End If Next lNum1 End If If bRenderUIs = True Then PumpWMPaintForAllWindowHandles End If PumpMessagesForWindowHandles = True
End Function
"WHY WOULD I LIKE TO CREATE SUCH A FUNCTION?" (gee... I'm glad that you are so courteous and helpful and asked...):
In some rare situations a developer may be working with a 3rd party control (i.e. closed source, black box). At a specific point in execution the program may invoke a method (say .CellFocus(x,y)) on that control in order to place the control in a specified state (lets say at line 100). THE PROBLEM is that at line 101 the control is not actually in that state even though, according to the documentation, it should be. Lines 102 to 105 of the program expect the control to be in that state. The programmer discovers that if he places DoEvents at line 102 that the control then enters the proper state (presumably because a particular MESSAGE was received by that control). However, re-entrancy occurrs in some scenarios because DoEvents was utilized and because the large program written by 15 developers over 8 years has places where re-entrancy is possible.
EXAMPLE:
wbBrowser.Navigate2 (msDisplayFileName) DoEvents
SOLUTION:
So, it seems like that it might be useful to create a version of DoEvents that filters out certain Windows Messages and dispatches others (thus avoiding re-entrancy via the pump).
AGAIN, TO CLARIFY, MY QUESTION:
What are the potential pitfalls of creating a filtered nested message pump in a VB6 application vai the PeekMessage() Win32 API function?
> >I am a non-MFC programmer that primarily works in .NET as well as legacy VB > >6. [quoted text clipped - 65 lines] > form's Refresh method is usually better than DoEvents in this case because > it's more specific to what you need accomplished). Wolfgang Enzinger - 18 Sep 2008 13:04 GMT >I thought that I was being SPECIFIC, but perhaps not enough FOR YOU so let me >CLARIFY. > >1st of all, I am concerned about DoEvents in Visual Basic 6.0 (your >newsgroup grouping structure simply uses "Visual Basic" and "Windows API"). I think you misunderstand the concept of newsgroups. This isn't Mike's newsgroup grouping structure. The structure was made by MS, and almost everybody replying here does so on a volunteer basis.
>> For the most part (there are other circumstances), in VB6 you most >> frequently need to use DoEvents in a loop when you also need your program to [quoted text clipped - 4 lines] >> form's Refresh method is usually better than DoEvents in this case because >> it's more specific to what you need accomplished). The Refresh method was my preferred approach in this situation, however, it will work only for 20 seconds or so in WinXP or higher. Then, without a DoEvents, the form won't be redrawn anymore until the thread is idle again.
So a was looking for an alternative solution. DoEvents was not an option, because I didn't feel like disabling every button and so on only to avoid a second, reentrant routine call.
So - and this might answer Jai's question - I found this replacement, and it works fine without any unwanted side effects in all of my projects for a couple of years now:
FUNCTION AllowRefresh (BYVAL hWnd AS LONG) AS BOOLEAN DIM mMSG AS tagMSG IF PeekMessage(mMSG, hWnd, WM_PAINT, WM_PAINT, PM_REMOVE) THEN TranslateMessage mMSG DispatchMessage mMSG FUNCTION = True END IF END FUNCTION
HTH, Wolfgang
Jai Singh - 18 Sep 2008 15:33 GMT Wolfgang,
Thanks for that confirmation. That is exactly what I was doing in my PumpWMPaintForAllWindowHandles() function related to the bRenderUIs flag.
Glad to know that it hasn't caused you any grief.
Jai
> >I thought that I was being SPECIFIC, but perhaps not enough FOR YOU so let me > >CLARIFY. [quoted text clipped - 39 lines] > HTH, > Wolfgang Karl E. Peterson - 18 Sep 2008 21:04 GMT > So - and this might answer Jai's question - I found this replacement, > and it works fine without any unwanted side effects in all of my [quoted text clipped - 8 lines] > END IF > END FUNCTION So what is that, PowerBasic?
 Signature .NET: It's About Trust! http://vfred.mvps.org
Alfie [UK] - 18 Sep 2008 23:21 GMT >> So - and this might answer Jai's question - I found this replacement, >> and it works fine without any unwanted side effects in all of my [quoted text clipped - 10 lines] > >So what is that, PowerBasic? Well apart from the 'Function=True' which should be 'AllowRefresh=True' it's a VB classic version of a C message pump for WM_Paint.
 Signature Alfie [UK] <http://www.delphia.co.uk/> God must love stupid people, he made so many of them.
Karl E. Peterson - 18 Sep 2008 23:46 GMT >>> So - and this might answer Jai's question - I found this replacement, >>> and it works fine without any unwanted side effects in all of my [quoted text clipped - 13 lines] > Well apart from the 'Function=True' which should be 'AllowRefresh=True' > it's a VB classic version of a C message pump for WM_Paint. Well, yeah, that and the fact it's never seen the inside of a VB IDE, eh?
 Signature .NET: It's About Trust! http://vfred.mvps.org
Alfie [UK] - 19 Sep 2008 19:38 GMT >Well, yeah, that and the fact it's never seen the inside of a VB IDE, eh? Hmmm, I see Wolfgang confirmed, but I didn't even consider case when checking if it was valid VB :)
 Signature Alfie [UK] <http://www.delphia.co.uk/> Ever stop to think and forget to start again?
Karl E. Peterson - 19 Sep 2008 19:46 GMT >>Well, yeah, that and the fact it's never seen the inside of a VB IDE, eh? > > Hmmm, I see Wolfgang confirmed, but I didn't even consider case when > checking if it was valid VB :) I was just sorta joshing, but all those years of editting the VBPJ "hot tips" special editions taught me to recognize code that's never ran in VB immediately. Just amazing how many people write code in their news/email apps! <g>
 Signature .NET: It's About Trust! http://vfred.mvps.org
Wolfgang Enzinger - 18 Sep 2008 23:32 GMT >> So - and this might answer Jai's question - I found this replacement, >> and it works fine without any unwanted side effects in all of my [quoted text clipped - 10 lines] > >So what is that, PowerBasic? Yep. ;-)
I copied that snipped from my PB source code and adapted everything that's specific to PB ... well, almost everything. <g>
Actually all of my VB projects nowadays depend on a DLL written in PB, mainly for two reasons:
* I don't wanna see Functions that have proofed to be stable over the years explain themselves over and over again in the VB debugger whenever I forget to press the shift key along with <F8>, which happens oftenly ...
and
* PB allows some stuff that VB doesn't, e.g. calling a function when you only know the function address (retrieved by GetProcAddress()) and of course the parameter list -> no need for hacks like CreateThread() as mentioned in another thread.
Wolfgang
Karl E. Peterson - 18 Sep 2008 23:46 GMT >>> So - and this might answer Jai's question - I found this replacement, >>> and it works fine without any unwanted side effects in all of my [quoted text clipped - 12 lines] > > Yep. ;-) Check's in the mail, right, Bob?! ;-)
> I copied that snipped from my PB source code and adapted everything > that's specific to PB ... well, almost everything. <g> [quoted text clipped - 13 lines] > course the parameter list -> no need for hacks like CreateThread() as > mentioned in another thread. I'm pretty sure that's where I'll end up if/when ClassicVB stops doing the job for me. I'd gotten around that function pointer thing with some code of Curland's, but your parameterless idea will work really nice for some other stuff. Have you started using that newest release yet? It sounded like they'd gone a lot closer to VB-style COM objects, but I've been too busy to really look at it too hard.
 Signature .NET: It's About Trust! http://vfred.mvps.org
Wolfgang Enzinger - 19 Sep 2008 14:39 GMT >I'm pretty sure that's where I'll end up if/when ClassicVB stops doing the job for >me. I'd gotten around that function pointer thing with some code of Curland's, but >your parameterless idea will work really nice for some other stuff. Have you >started using that newest release yet? It sounded like they'd gone a lot closer to >VB-style COM objects, but I've been too busy to really look at it too hard. I didn't look very deeply into it so far, too, but here are a few impressions:
* As per documentation, the extended COM support looks really great!
* When I got my copy of PB9, the first thing I tried was to recompile a few DLLs that I had written using PB8. I have to say that there are some compatibility breaks. Some of them were confirmed by the support team (e.g. all .INC files for COM components made with the COM browser that came with PB8 have to be redone with the new COM browser, because the old files contain lots of errors in the eye of the new compiler), others seem to be bugs typical for a x.0 version (at least that's what I infer from the fact that the support team doesn't really comment them).
* Creating COM servers is not as easy as it looks at first glance. My tests so far were a little frustrating, and when I finally had a COM DLL done then it crashed the VB IDE. Maybe that's because I used VB5, but there are several similar reports in the PB forum.
Currently I have no big hurry in using the new features, so probably I'll wait for the first service release (9.1) before continuing.
Wolfgang
Karl E. Peterson - 19 Sep 2008 19:44 GMT I think I'll take your final word ...
> Currently I have no big hurry in using the new features, so probably > I'll wait for the first service release (9.1) before continuing. ... to heart. Appreciate the heads-up. Thanks!
 Signature .NET: It's About Trust! http://vfred.mvps.org
>>I'm pretty sure that's where I'll end up if/when ClassicVB stops doing the job for >>me. I'd gotten around that function pointer thing with some code of Curland's, [quoted text clipped - 28 lines] > > Wolfgang DanS - 18 Sep 2008 23:41 GMT > The Refresh method was my preferred approach in this situation, > however, it will work only for 20 seconds or so in WinXP or higher. [quoted text clipped - 4 lines] > option, because I didn't feel like disabling every button and so on > only to avoid a second, reentrant routine call. Me.Enabled = False DoEvents Me.Enabled = True
Wolfgang Enzinger - 19 Sep 2008 14:39 GMT >> So a was looking for an alternative solution. DoEvents was not an >> option, because I didn't feel like disabling every button and so on [quoted text clipped - 3 lines] >DoEvents >Me.Enabled = True Yeah, but timer controls on this form, for instance, will continue to fire events that will be handled during DoEvents ...
Wolfgang
Thorsten Albers - 18 Sep 2008 11:33 GMT Jai Singh <JaiSingh@discussions.microsoft.com> schrieb im Beitrag <4DCA67A9-57EE-47D3-B425-42BA0FF1B144@microsoft.com>...
> I have determined that > DoEvents is essentially "evil" in most programs because of subroutine > re-entrancy problems and the generation of unplanned for execution sequences > (this opinion was confirmed via web searches and opinions provided by > industry "leaders" as well as Microsoft Developers). No, DoEvents as such is in no way evil. It's use only leads to trouble if the developer using it doesn't know how to use it correctly, and/or if his code relies on a certain order in the firing of events - which the code shouldn't do. If a procedure should not be executed under certain conditions a) your code should store somewhere the current state on which the execution depends, and b) your procedure should check the conditions (which could be at least e.g. a simple static procedure flag 'I am already running' to avoid re-entrance...).
 Signature ---------------------------------------------------------------------- Thorsten Albers albers(a)uni-freiburg.de ----------------------------------------------------------------------
Jai Singh - 18 Sep 2008 12:26 GMT Guten Tag. Thank you for your reply.
> Jai Singh <JaiSingh@discussions.microsoft.com> schrieb im Beitrag > <4DCA67A9-57EE-47D3-B425-42BA0FF1B144@microsoft.com>... [quoted text clipped - 13 lines] > (which could be at least e.g. a simple static procedure flag 'I am already > running' to avoid re-entrance...). expvb - 18 Sep 2008 15:49 GMT > I was wondering if there are any potential pitfalls in using PeekMessage() > to create a nested message pump that only dispatches certain messages and > does not dispatch others? I am aware of the C++ equivalent of DoEvents. One possible pitfall when you process selected messages is that if these messages and their VB6 code changes focus, VB6 maybe sending other messages to handle the focus change and calling Validate event, I am not sure how VB6 handles this. Use Spy++ to see what happens.
If you are only looking for a specific message that has VB6 code that doesn't change the focus, then it should be safe to make your own custom DoEvents.
Dean Earley - 18 Sep 2008 15:49 GMT > I am a non-MFC programmer that primarily works in .NET as well as legacy VB 6. > In both of those high-level RAD language worlds, the platforms offer a > DoEvents() function. It seems that DoEvents() is a non-filtered nested > message pump that is used to flush the message queue. Please don't multipost. http://www.blakjak.demon.co.uk/mul_crss.htm
 Signature Dean Earley (dean.earley@icode.co.uk) i-Catcher Development Team
iCode Systems
|
|
|