August Fourteenth 2003 From: Bob Zale, President PowerBASIC, Inc. PowerBASIC Gazette #38 - Please share with fellow programmers ============================================================= Subject: What about these "Message Pumps"? =========================================== Message Pumps. An ever-present, but often misunderstood factor we need to deal with in Windows GUI programming. So, I guess it's about time to shed a little light on this mildly mysterious topic! {smile} Our thanks to Lance Edmonds for this very informative article. But first, would you indulge me? Would you help us spread the word about PowerBASIC? You know, you're the world's best PowerBASIC advertisement. And every time we add a PowerBASIC customer, it helps us all. Every new user is an investment in the future... with more users, we'll create better products, deliver them sooner, and keep the pricing right for you. How about it? Will you tell a friend about PowerBASIC? I hope so. We'll make it easy for you to "Share the Power"! {smile} Anyway, here's the plan... You give us a name (or a few names), and we'll send just one short, informative e-mail under your name and ours. We won't follow up. We'll never use their e-mail again, unless they choose to sign up. So, Share the Power! Just GoTo... http://www.powerbasic.com/bin/ps.exe You'll see the text of the message and add a contact name! The message is harmless, yet informative. And just think of the compilers we could build for you if everyone brought us just one new PowerBASIC user! An Introduction to Message Pumps - by Lance C. Edmonds ====================================================== What is a message pump? An excellent question. However, before we can answer it, we must first understand what a message is, and why it is an important (and fundamental) part of Windows GUI programming. First, let's note that Windows is an "event-driven" operating system. It consists of a large collection of routines that generally remain idle until they are executed to process an "event". At the lowest level, an event may be a hardware interrupt, signaling a change in the state of an I/O port or device. When this low-level event occurs, the appropriate event handler is executed -- typically a device driver -- which processes the low-level event data. Depending on the nature of the event, the driver may then generate a high-level event (message) that is ultimately sent to one or more running programs to make them aware of the change. For example, when the user moves the mouse, the hardware in the mouse sends a signal to the PC to indicate a change in the mouse position. The mouse driver, in turn, analyses that signal and generates a "mouse move" notification which is sent on to the mouse cursor handler to update the mouse cursor position on the screen. In addition, a mouse movement notification is sent to the application that owns the window underneath the mouse cursor. While the low-level event handling system can be largely ignored by application programs, the high-level events must be processed by a program if the program needs to react to the mouse and keyboard, etc. However, a problem can arise when there is a "flood" of these notifications, such as the rapid movement of the mouse, fast typing, etc. If the operating system had to wait for a program to receive and process each notification as it occurred, a delay might cause some low-level notifications to get lost, which could result in missed keystrokes, mouse activity, or some other equally important event. This problem is easily overcome by the use of a queue: low-level events are translated into high-level messages which are then placed into the tail of the queue at whatever rate they occur at. This leaves programs to read from the head of the queue at their own pace. When the queue becomes emptied, programs revert to an idle state once again. The extraction of messages from the queue is the function of the message pump. Every GUI application that creates a GUI window or dialog requires a message pump, and as you'll see, there are a few variations and traps to be aware of. What does a message pump look like? =================================== In the most basic form, a classic API-based message pump is a simple loop that gets a message from the queue, then calls the window procedure (WndProc/Callback) function of the target window or dialog: WHILE GetMessage(wMsg, hWnd, 0, 0) CALL TranslateMessage(wMsg) CALL DispatchMessage(wMsg) LOOP Hang on a second... if we are simply getting messages from the queue, what does the TranslateMessage() function do? Earlier we mentioned that the messages in the queue are "high-level", but in reality, these can be resolved into still higher levels. For example, when a key is tapped on the keyboard, the message queue will receive both a %WM_KEYDOWN and a %WM_KEYUP message. While this might be useful in some situations, it is often a lot more useful to just know which key was pressed. This is the job of the API function TranslateMessage. Essentially, it takes the raw key messages, and produces composite "character" messages (%WM_CHAR) which it then places back into the message queue. This technique gives programs an opportunity to examine or ignore the raw key messages, and just concentrate on the composite character messages. What else can a message pump do? ================================ Besides translating standard keystroke messages and dispatching them to the appropriate window, the message pump is capable of dealing with a few more special situations that can make GUI programming a bit easier. For example, a great number of PC users rely on the standard keyboard shortcuts that are expected to be available when a dialog is being displayed. These include the TAB and SHIFT+TAB key presses which move keyboard focus forward and back through the "tab order" of controls on a dialog, the cursor keys to move focus within a control group (for example, a set of Option/Radio buttons), the ENTER key to "click" the default button on the dialog (such as the OK button), and finally, the ESCAPE key to "click" on the Close or Cancel button to dismiss the dialog. The amount of code required to implement these common operations is anything but trivial, and so it would be painful to have to include "basic" operations into every program that you write. Luckily, the Windows API library comes to the rescue in the form of the IsDialogMessage() function. By incorporating this function into the message pump loop, we can instantly add complete support for these keystrokes into any dialog we choose! The expanded message pump code would now look something like this: WHILE GetMessage(wMsg, hWnd, 0, 0) IF IsDialogMessage(hWnd, wMsg) = 0 THEN CALL TranslateMessage(wMsg) CALL DispatchMessage(wMsg) END IF LOOP In this extension of the message pump code, the IsDialogMessage() function call examines each message and automatically handles all TAB, SHIFT+TAB, ENTER and ESCAPE keypress messages. If such a message is processed by IsDialogMessage(), the function returns a non-zero result value, and the pump code loops back to pick up the next message in the queue. For all other messages, IsDialogMessage() returns a zero result value, which indicates that the message should be handled by the standard TranslateMessage() and DispatchMessage() pump functions. One interesting point we should make here concerns the function name. Over the years we've seen a number of people make the assumption that the IsDialogMessage() function can only be used with dialogs, but the fact is that this API function is intended to be used to add dialog keyboard shortcut functionality to any Window that contains controls, not just dialogs! At this stage, our pump code is looking quite good. With a minimum of fuss we have added the ability for keyboard focus to switch between controls, move focus between items in a group, dismiss the window, etc. Can we stop there? Absolutely not! We can take the message pump code one step further again and let it handle the translation of a set of user-defined "special" keyboard combinations which result in %WM_COMMAND messages being sent to the target WndProc/Callback instead of the more cryptic keyboard messages. This set of special key combinations is formally known as an "Accelerator table". For example, it is common for applications to use an accelerator table to create a set of "standard" shortcut key combinations -- for example CTRL+S is often used as a shortcut to the FILE SAVE menu item in the FILE menu, CTRL+O is a common shortcut FILE OPEN menu item, etc. Assuming that an accelerator table has been created before the message pump operates, we can expand the message pump to handle these shortcuts with ease: WHILE GetMessage(wMsg, hWnd, 0, 0) IF TranslateAccelerator(hWnd, hAccelTable, wMsg) = 0 THEN IF IsDialogMessage(hWnd, wMsg) = 0 THEN CALL TranslateMessage(wMsg) CALL DispatchMessage(wMsg) END IF END IF LOOP In the latest revision to the message pump block, messages are first passed to the TranslateAccelerator() function, which automatically dispatches any accelerator shortcuts to the target WndProc/Callback. All other messages "fall through" to the IsDialogMessage() function, which extracts any of the dialog-specific keystrokes we discussed earlier, leaving any remaining unhandled messages to pass through to the standard message pump functions. DDT message pumps vs API-based pumps ==================================== In many cases, it can be quite acceptable to use an API-based message pump loop for one or more modeless DDT dialogs (in place of a DIALOG DOEVENTS message pump loop), because the primary operation of the API-based message pump loop will be performing an otherwise identical job to the DIALOG DOEVENTS statement. However, if the dialog uses an accelerator table, the table must be created to match the type of pump being used. For example, an API-based message pump requires the accelerator table be created using either LoadAccelerator() or CreateAcceleratorTable() API functions, so that the handle of the table can be passed to the TranslateAccelerator() function. Importantly, an accelerator table created with the CreateAcceleratorTable() must be explicitly released through a call to the DestroyAcceleratorTable() API. Similarly, the use of a DDT-based message pump would necessitate the accelerator table be created with the ACCEL ATTACH statement so that the DDT engine can reference and release its own accelerator table. Message pumps for Modal and Modeless dialogs ============================================ Of the two types, only Modeless dialogs require an explicit message pump loop in the program because Modal dialog handlers (like DIALOG SHOW MODAL for DDT, or the DialogBox() API for API-based code) provide internal message pumps. In addition, a Modeless dialog may not need to provide an explicit message pump if a Modal dialog is running, as the Modal dialog's message pump will also service the modeless dialog at the same time. For example, a DDT-based text editor might use a Modal dialog for the main application window, and when required, display a child "Find" dialog that the user may also interact with. Typically, the "Find" dialog would be ideal if it was Modeless, since it allows the user to alternate between searching through the document while still being able to edit text in the main dialog at the same time. In this instance, the modeless dialog would only need to rely on the message pump provided by the Modal (parent) dialog. Another situation might require a completely different approach. For example, a "data crunching" application may use a modeless dialog to display the progress of the data processing. In this instance, the message pump code might be regularly intersperced within the actual data processing code, so that messages can be regularly pumped during processing, and the cancellation of the dialog serves to abort the processing code. The outline (pseudocode) for such a program might look something like this: DIALOG NEW hParent, "Process Phase", 70, 70, 100, 41, TO hDlg CONTROL ADD LABEL, hDlg, %IDC_LABEL1, "0%", 5, 5, 90, 10, %SS_CENTER CONTROL ADD BUTTON, hDlg, %IDCANCEL, "Cancel", 25, 20, 50, 15 DIALOG SHOW MODELESS hDlg PercentDone& = 0 DO DIALOG DOEVENTS TO lCount& IF lCount& = 0 THEN EXIT LOOP ' User canceled the dialog, so abort. ' ' Process just one or two data items here ' INCR PercentDone& CONTROL SET TEXT hDlg, %IDC_LABEL1, STR$(PercentDone) + "%" LOOP WHILE PercentDone& <= 100 IF lCount& THEN ' Force dilog closure if active DIALOG END hDlg DO ' Loop until dialog is destroyed DIALOG DOEVENTS TO lCount& LOOP WHILE lCount& END IF In this situation, care must be taken to ensure that the messages are pumped frequently enough so that the dialog can be redrawn and updated frequently. If the pump does not pump messages frequently enough, the dialog will not be updated and might even appear unresponsive. In general, this approach is used when data processing speed is not critical -- for example, it can allow users to abort long printing tasks with relative ease. However, as you may have noticed, the problem with this approach is that a significant amount of CPU time has to be taken away from the processing code in order to serve the needs of the GUI interface. In Windows, these kind of tasks can usually be handled better by making use of threads. Advanced pump topics ==================== The GUI interface is very cunningly designed by Microsoft so that a thread that creates a GUI window/dialog cannot pump messages for the GUI window/dialog created by another thread in the same application. Hence each thread in an application must maintain its own message pump. While it might seem better to have a single message pump that serves all threads, the problem is that a stalled or suspended thread would have the potential to freeze all the windows/dialogs of an application, and that could be disasterous. For example, just because the landing gear won't go down, you would not want the rest of the plane to stop working! The side benefit of maintaining a per-thread message queue is that it facilitates the design of multi-threaded applications that can process data using as much CPU time as there is available, while still allowing an overall GUI interface to run independently, using CPU time only when messages need to be pumped. Compared to the Modeless dialog technique described above, this strategy provides a vastly improved design that can dramatically increase throughput while still allowing the user to view the progress of the processing, and even providing a mechanism to abort the processing if required. The key to this design is to use the primary thread to create and maintain the GUI interface of the application, which then spawns one or more threads to process the data. From there, inter-thread messaging is used to pass status messages back to the GUI thread. In this situation, though, a new set of issues must be considered. First, it is not in the best interests of a processing thread to update the GUI too often, as this will slow the processing thread considerably. The reason for this is that it takes time for the CPU to switch threads, update the screen data, and then switch back to the processing thread. In this case, the best solution is for the threads not to use SendMessage() to pass messages back to the main dialog thread, but for it to use PostMessage() to place messages in the message queue of the GUI thread instead. Ideally, the processing thread(s) would simply pass numeric "progress" values back to the GUI thread, since these can be handled as a portion of the message parameter, whereas updating text needs a buffer that must remain valid until the GUI thread reads the data. While we'll be covering the thread-safety problems that this arrangement can cause in a later article, we'll stick to "safe" numeric messages for the remainder of this article. However, before we present our final example, we'll add in one more interesting piece of information on message pumps. As we noted earlier, Windows maintains a message queue for each thread in an application, but what happens if a thread does not need to provide its own message pump? The answer is actually quite straightforward: Windows does not start a message queue for a given thread until that thread makes a one API call to either USER32.DLL or GDI32.DLL system libraries. This is generally not a problem, as the thread message queue will remain inert unless the thread goes on to create its own GUI window or dialog, or it starts to receive messages via explicit calls from the PostThreadMessage() API, etc. Naturally, once a message pump is established, the thread should continue to provide a message pump until the thread terminates. Messaging based data processing =============================== Finally, to conclude this article, we're providing an example of a simple thread-based data processing application that uses messages for inter-thread signalling, along with the all-important message pump to tie it all together. The code is fairly well commented, and with the explanations above, it should provide an important overview on how important it can be to choose the right type of message pump for a given task. In this case, we're using a Modal dialog, and as noted earlier, the message pump is handled internally by the DIALOG SHOW MODAL statement. I hope this article is of some use to you, but if you have any comments or questions, please be sure to join us for discussions in the peer support forums on the PowerBASIC web site at: http://www.powerbasic.com/support/pbforumsTo keep the size of this Gazette at a reasonable level, we've separated the final example program. You can download it now at: http://www.powerbasic.com/files/pub/pbwin/misc/message.zip =================================================================== If you haven't yet acquired all three PowerBASIC Compilers, now might be a good time to look a bit further. Pricing remains unchanged at the most competitive levels known in our industry. PB/CC is our Console Compiler -- with a text mode interface to 32-bit Windows. PB 7 for Windows is oriented towards a GUI (graphical user interface) for a typical Windows "look and feel". With all of the new extensions, you won't believe how easy it is to build real GUI applications! PB/Forms is our new, state-of-the-art visual designer. It works with PowerBASIC 7 to build GUIs almost as fast as you can imagine them. More information? Sure, it's very simple. Just click to go to each or all of the PowerBASIC product pages... PB/CC 3.0: http://www.powerbasic.com/products/pbcc/ PB/WINDOWS 7.0: http://www.powerbasic.com/products/pbdll32/ PowerBASIC Forms: http://www.powerbasic.com/products/pbforms/ PowerTREE: http://www.powerbasic.com/products/powertree/ Graphics Tools: http://www.powerbasic.com/products/graftool/ SQL Tools: http://www.powerbasic.com/products/sqltools/ Console Tools: http://www.powerbasic.com/products/contools/ PB/WIN 7.0 is attractively priced at $199.00, while PB/CC 3.0 is just $169.00. Upgrades from the prior versions are just $99 and $89. PowerBASIC Forms is priced at $99. Graphics Tools Standard and Pro are $69.95 and $139.95. You can order with ease by just replying to this e-mail. You can call us at (888) 659-8000 or (941) 473-7300, fax us at (941) 681-3100, place an electronic order on our web site (www.powerbasic.com), or even mail it in. But no matter the method you choose, please do it today and do it with confidence. Every product PowerBASIC shipped for physical delivery is offered with a money-back guarantee for a full 30 days from the transaction date. Regards, Bob Zale, President PowerBASIC Inc. =================================================================== PowerBASIC Price List ------------------------------------------------------------------- PB/CC Console Compiler 3.0 - Full Product $169.00 PB/CC Console Compiler 3.0 - Upgrade from vs 2 89.00 PB/CC Console Compiler 3.0 - Upgrade from vs 1 119.00 Add Printed Documentation 39.00 ------------------------------------------------------------------- PowerBASIC for Windows 7.0 (GUI) - Full Product $199.00 PowerBASIC for Windows 7.0 - Upgrade from ver 6 99.00 PowerBASIC for Windows 7.0 - Upgrade from prior versions 129.00 Add Printed Documentation 39.00 PowerBASIC Forms Visual Design Tool for PB/Win 7 99.00 ------------------------------------------------------------------- PowerBASIC for DOS 3.5 - Full Product $99.00 PowerBASIC for DOS 3.5 - Upgrade from prior versions 49.00 Add Printed Documentation (2 book set) 29.00 ------------------------------------------------------------------- PowerTree BTree Manager for DOS and Windows $99.00 PB/Vision for DOS 20.00 PB/Xtra III for DOS and Windows 49.00 ------------------------------------------------------------------- Graphics Tools Standard ver 2 $69.95 Graphics Tools Professional ver 2 139.95 Console Tools Standard 49.95 Console Tools Professional 99.95 SQL Tools Standard Version 99.95 SQL Tools Professional Version 199.95 ------------------------------------------------------------------- Shipping/Handling costs: Software & Each Any Software 1 or 2 books Addl Book E-mail $6 N/A N/A UPS Ground/Mail US $10 $10 $6 Fedex 2-day US $16 $16 $6 Fedex 1-day US $28 $28 $6 Air Mail Canada/Mex $12 $22 $6 Air Mail Intl $16 $28 $6 Fedex Intl $34 $44 $6 Fedex International Rates are for Western Europe, Pacific Rim, Asia, and North America. Others please request a quotation. ------------------------------------------------------------------- Order online at https://www.powerbasic.com/shop/ (secure server) or just send an e-mail with all pertinent information to sales@powerbasic.com and we'll take it from there! ------------------------------------------------------------------- Most PowerBASIC products (those without printed books) can now be delivered by electronic mail. No wait for a package to arrive... No high shipping costs... For just $6 per order, no matter how many products, we'll deliver directly to your computer. If you're outside the U.S., savings might be greater. You won't pay taxes or duties to a freight company or postal service because they aren't involved in the delivery. Check your tax code to be sure, but some countries charge no tax at all on transactions like this. It could just be your lucky day! ==================================================================== Is your PowerBASIC Gazette Electronic Edition subscription coming to you at home or work? If you don't want to miss a single issue, why not subscribe from both e-mail addresses? Send your subscription request to email@powerbasic.com and please include your name and all e-mail addresses you'd like to add as well as your Zip or Postal Code. If you know someone else who would enjoy this newsletter please forward a copy to them so they can subscribe. ==================================================================== All contents Copyright (c) 2003-2009 PowerBASIC Inc All Rights Reserved. PowerBASIC is a registered trademark of PowerBASIC, Inc. PB/CC, PB/DLL, PB/Win, PowerTREE, and PowerBASIC Forms are trademarks of PowerBASIC Inc. Other names are trademarks or registered trademarks of their respective owners. ==================================================================== PowerBASIC Gazette - Electronic Edition Volume 1 - Issue 38 PowerBASIC, Inc. (888) 659-8000 Sales 2061 Englewood Road (941) 473-7300 Voice Englewood, FL 34223 (941) 681-3100 Fax Visit www.powerbasic.com e-mail to sales@powerbasic.com This newsletter is only sent to e-mail addresses in our subscription list. If you have received this newsletter by mistake or no longer wish to receive it, please send a simple unsubscribe request to gazette@powerbasic.com with your name and zip/postal code. ====================================================================