WindowsMessages

From HerzbubeWiki
Jump to: navigation, search

This page contains notes on the topic of "messages" on the Windows operating system.

  • How does a Message Loop work?
  • How do Window Procedures work?
  • How are messages sent and received within the CWnd subsystem

References

Windows and Messages 
Top-level article on the topic.
GetMessage() 
Retrieves a message from the calling thread's message queue. The function dispatches incoming sent messages until a posted message is available for retrieval.
PeekMessage() 
Dispatches incoming sent messages, checks the thread message queue for a posted message, and retrieves the message (if any exist).
TranslateMessage() 
Translates virtual-key messages into character messages. The character messages are posted to the calling thread's message queue, to be read the next time the thread calls the GetMessage() or PeekMessage() function.
DispatchMessage()
Dispatches a message to a window procedure. It is typically used to dispatch a message retrieved by the GetMessage() function.
CWinApp::PreTranslateMessage() 
  • Override this function to filter window messages before they are dispatched to the Windows functions TranslateMessage and DispatchMessage
  • The default implementation performs accelerator-key translation, so you must call the CWinApp::PreTranslateMessage member function in your overridden version.
CWnd::PreTranslateMessage() 
Used by class CWinApp to translate window messages before they are dispatched to the TranslateMessage() and DispatchMessage() Windows functions.
CWnd::SendNotifyMessage() 
  • If the window was created by the calling thread, SendNotifyMessage() calls the window procedure for the window and does not return until the window procedure has processed the message.
  • If the window was created by a different thread, SendNotifyMessage() passes the message to the window procedure and returns immediately; it does not wait for the window procedure to finish processing the message.
Hooks 
A hook is a point in the system message-handling mechanism where an application can install a subroutine to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure.
About Window Procedures 
Introduction.
Using Window Procedures 
This section explains how to perform the following tasks associated with window procedures:
  • Designing a Window Procedure
  • Associating a Window Procedure with a Window Class
  • Subclassing a Window
Messages and Message Queues 
  • Top-level article branching out into more detailed articles
  • The article also has an overview of the available Message Functions


About Messages and Message Queues

About Messages and Message Queues: Source article for this section.


  • Each window has a function, called a "window procedure", that the system calls whenever it has input for the window
  • The window procedure processes the input and returns control to the system
  • The system passes input to a window procedure in the form of a message
  • The system sends a message to a window procedure with a set of four parameters
    • A window handle: Identifies the window for which the message is intended. The system uses it to determine which window procedure should receive the message.
    • A message identifier: A named constant that identifies the purpose of a message (e.g. WM_PAINT)
    • And two values called message parameters: Specify data or the location of data used by a window procedure when processing a message. The meaning and value of the message parameters depend on the message. When a message does not use message parameters, they are typically set to NULL.
  • There are 2 types of messages
    • System-Defined Messages: 0x0000 through 0x03FF (the value of WM_USER ñ 1)
    • Application-Defined Messages
      • 0x0400 (the value of WM_USER) through 0x7FFF are available for message identifiers for private window classes.
      • If your application is marked version 4.0, you can use message-identifier values in the range 0x8000 (WM_APP) through 0xBFFF for private messages.
      • The system returns a message identifier in the range 0xC000 through 0xFFFF when an application calls the RegisterWindowMessage function to register a message. The message identifier returned by this function is guaranteed to be unique throughout the system. Use of this function prevents conflicts that can arise if other applications use the same message identifier for different purposes.
  • The "About" article has a good overview of which prefixes exist for message names and what they mean
  • The system uses two methods to route messages to a window procedure
    • Posting messages to a first-in, first-out queue called a message queue -> queued messages
      • These are primarily the result of user input entered through the mouse or keyboard
      • Other queued messages include the timer, paint, and quit messages
    • Sending messages directly to a window procedure -> non-queued messages
      • Most of the other messages not mentioned above under queued messages
  • Messages queues
    • The system maintains a single system message queue
    • The system also creates a thread-specific message queue for each GUI thread, as soon as the thread makes its first call to one of the specific user functions
  • Order of events
    • Keyboard/mouse messages are generated by device drivers in reaction to user input
    • The driver places the messages in the system message queue
    • The system removes the messages, one at a time, from the system message queue, examines them to determine the destination window, and then posts them to the message queue of the thread that created the destination window
    • The thread removes messages from its queue and directs the system to send them to the appropriate window procedure for processing
    • With the exception of the WM_PAINT, WM_TIMER and WM_QUIT messages, the system always posts messages at the end of a message queue
    • This ensures that a window receives its input messages in the proper first in, first out (FIFO) sequence
    • The WM_PAINT, WM_TIMER message and WM_QUIT messages, however, are kept in the queue and are forwarded to the window procedure only when the queue contains no other messages
    • In addition, multiple WM_PAINT messages for the same window are combined into a single WM_PAINT message, consolidating all invalid parts of the client area into a single area
    • Combining WM_PAINT messages reduces the number of times a window must redraw the contents of its client area
  • A thread can post a message to its own message queue or to the queue of another thread by using the PostMessage() or PostThreadMessage() function
  • An application can remove a message from its queue by using the GetMessage() function
  • To examine a message without removing it from its queue, an application can use the PeekMessage() function
  • After removing a message from its queue, an application can use the DispatchMessage() function to direct the system to send the message to a window procedure for processing
  • A thread can use the WaitMessage() function to yield control to other threads when it has no messages in its message queue. The function suspends the thread and does not return until a new message is placed in the thread's message queue.
  • An application must remove and process messages posted to the message queues of its threads
  • A single-threaded application usually uses a message loop in its WinMain() function to remove and send messages to the appropriate window procedures for processing
  • Applications with multiple threads can include a message loop in each thread that creates a window
  • Message Loops
    • A simple message loop consists of one function call to each of these three functions
      • GetMessage()
      • TranslateMessage()
      • DispatchMessage()
    • Example implementation
MSG msg;
BOOL bRet;
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
  if (bRet == -1)
  {
    // handle the error and possibly exit
  }
  else
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
}
    • GetMessage() returns -1 if an error occurs. This should be handled in whatever way is appropriate.
    • GetMessage() returns 0 if it encounters WM_QUIT. The message loop should terminate as a result.
    • GetMessage() (and PeekMessage()) can be used to retrieve only certain messages, this is called message filtering
      • WM_KEYFIRST and WM_KEYLAST can be used as filter values to retrieve all keyboard messages
      • WM_MOUSEFIRST and WM_MOUSELAST can be used to retrieve all mouse messages
      • Any application that filters messages must ensure that a message satisfying the message filter can be posted
      • For example, if an application filters for a WM_CHAR message in a window that does not receive keyboard input, the GetMessage function does not return. This effectively "hangs" the application.
    • TranslateMessage() must be included if the thread is to receive character input from the keyboard
      • The system generates virtual-key messages (WM_KEYDOWN and WM_KEYUP) each time the user presses a key
      • A virtual-key message contains a virtual-key code that identifies which key was pressed, but not its character value
      • To retrieve this value, the message loop must contain TranslateMessage(), which translates the virtual-key message into a character message (WM_CHAR) and places it back into the application message queue
      • The character message can then be removed upon a subsequent iteration of the message loop and dispatched to a window procedure.
    • DispatchMessage() sends a message to the window procedure associated with the window handle specified in the MSG structure
  • Ways how to modify a message loop
    • An application that uses accelerator keys must be able to translate keyboard messages into command messages. To do this, the application's message loop must include a call to the TranslateAccelerator() function
    • If a thread uses a modeless dialog box, the message loop must include the IsDialogMessage() function so that the dialog box can receive keyboard input
    • An application can end its own message loop by using the PostQuitMessage() function, typically in response to the WM_DESTROY message in the window procedure of the application's main window
  • Window Procedure
    • A window procedure is a function that receives and processes all messages sent to the window
    • Every window class has a window procedure, and every window created with that class uses that same window procedure to respond to messages
    • A window procedure does not usually ignore a message. If it does not process a message, it must send the message back to the system for default processing
    • The window procedure does this by calling the DefWindowProc() function, which performs a default action and returns a message result
    • The window procedure must then return this value as its own message result
    • Most window procedures process just a few messages and pass the others on to the system by calling DefWindowProc()
  • The page contains more information on
    • Posting your own messages
    • Sending your own messages
    • Broadcasting messages
    • Message deadlocks


Message Handling and Mapping

Message Handling and Mapping: Source article for this section.

  • Conceptually there are 3 types of messages:
    • Windows messages: All messages that start with "WM". Exception: WM_COMMAND.
    • Control notifications: WM_COMMAND messages that are sent by controls to their parent windows.
    • Command messages: WM_COMMAND messages that are sent by user interface objects (e.g. menu items, toolbar items, accelerator keys)
  • Messages are processed by so-called "message handlers"
  • A class that is derived from CCmdTarget can define a so-called "message map"
    • A message map defines for various messages what function (message handler) to call when the message is delivered
  • The message map is declared in the header file (.h), like this:
class CMyDialog : public CDialog
{
  DECLARE_MESSAGE_MAP()
};
  • Attention: Members that are declared after the message map must use a renewed public/protected/private declaration.
  • The message map is defined in the implementation file (.cpp), like this:
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
  ON_WM_PAINT()
  ON_COMMAND(IDM_ABOUT, OnAbout)
END_MESSAGE_MAP()
  • Each message handler function must be declared and implemented separately.
    • In the header file:
afx_msg void OnClose();
    • In the implementation file:
void CMainFrame::OnClose()
{
  ...
}
  • For various messages that are predefined by the system, the system also prescribes how the corresponding message handler function must be named. For these you don't specify a function name in the message map. Examples:
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
  ON_WM_CREATE()
  ON_WM_SETFOCUS()
  ON_WM_CLOSE()
  ON_WM_SYSCOMMAND()
  ON_WM_TIMER()
END_MESSAGE_MAP()
  • Predefine messages are declared in afxmsg_.h
  • Other messages are defined with ON_COMMAND
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
  ON_COMMAND(ID_VIEW_KONTO, OnViewKonto)
END_MESSAGE_MAP()


Using Messages and Message Queues

Using Messages and Message Queues: Source article for this section.

The following code examples demonstrate how to perform the following tasks associated with Windows messages and message queues:

  • Creating a Message Loop
  • Examining a Message Queue
  • Posting a Message
  • Sending a Message
HINSTANCE hinst; 
HWND hwndMain; 
 
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpszCmdLine, int nCmdShow) 
{ 
    MSG msg;
    BOOL bRet; 
    WNDCLASS wc; 
    UNREFERENCED_PARAMETER(lpszCmdLine); 
 
    // Register the window class for the main window. 
 
    if (!hPrevInstance) 
    { 
        wc.style = 0; 
        wc.lpfnWndProc = (WNDPROC) WndProc; 
        wc.cbClsExtra = 0; 
        wc.cbWndExtra = 0; 
        wc.hInstance = hInstance; 
        wc.hIcon = LoadIcon((HINSTANCE) NULL, 
            IDI_APPLICATION); 
        wc.hCursor = LoadCursor((HINSTANCE) NULL, 
            IDC_ARROW); 
        wc.hbrBackground = GetStockObject(WHITE_BRUSH); 
        wc.lpszMenuName =  "MainMenu"; 
        wc.lpszClassName = "MainWndClass"; 
 
        if (!RegisterClass(&wc)) 
            return FALSE; 
    } 
 
    hinst = hInstance;  // save instance handle 
 
    // Create the main window. 
 
    hwndMain = CreateWindow("MainWndClass", "Sample", 
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL, 
        (HMENU) NULL, hinst, (LPVOID) NULL); 
 
    // If the main window cannot be created, terminate 
    // the application. 
 
    if (!hwndMain) 
        return FALSE; 
 
    // Show the window and paint its contents. 
 
    ShowWindow(hwndMain, nCmdShow); 
    UpdateWindow(hwndMain); 
 
    // Start the message loop. 
 
    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    { 
        if (bRet == -1)
        {
            // handle the error and possibly exit
        }
        else
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    } 
 
    // Return the exit code to the system. 
 
    return msg.wParam; 
} 


WM_NOTIFY

References:

Notes:

  • Many child controls send a WM_NOTIFY message to their parent window to inform them about an event that has occurred in the control, or to gather some kind of information required by the control
  • To handle a WM_NOTIFY message, the following message-map macros and member function signatures can be used:
  • The ON_NOTIFY message-map macro has the following syntax:
ON_NOTIFY(wNotifyCode, controlId, memberFunction)
afx_msg void Foo::memberFunction(NMHDR* pNMHDR, LRESULT* pResult);
ON_NOTIFY_RANGE(wNotifyCode, controlId, controlIdLast, memberFunction)
afx_msg void Foo::memberFunction(UINT controlId, NMHDR* pNMHDR, LRESULT* pResult);
  • If the WM_NOTIFY message should be further processed, the following message-map macros and member function signatures can be used; the handler functions may return FALSE to continue processing, or TRUE to signify that the WM_NOTIFY message has been handled
ON_NOTIFY_EX(wNotifyCode, controlId, memberFunction)
afx_msg BOOL Foo::memberFunction(UINT controlId, NMHDR* pNMHDR, LRESULT* pResult);
ON_NOTIFY_EX_RANGE(wNotifyCode, controlId, controlIdLast, memberFunction)
afx_msg BOOL Foo::memberFunction(UINT controlId, NMHDR* pNMHDR, LRESULT* pResult);
  • For instance, to handle WM_NOTIFY messages with code LVN_KEYDOWN for the list control IDC_LIST1 by member function OnKeydownList1():
ON_NOTIFY(LVN_KEYDOWN, IDC_LIST1, OnKeydownList1)
afx_msg void Foo::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult);
  • Same example, but for several controls
ON_NOTIFY(LVN_KEYDOWN, IDC_LIST_FIRST, IDC_LIST_LAST, OnKeydownList)
afx_msg void Foo::OnKeydownList(UINT controlId, NMHDR* pNMHDR, LRESULT* pResult);
  • A general purpose approach is to override CWnd::OnNotify()
// wParam identifies the control that sends the message if the message is from a control. Otherwise, wParam is 0.
//
// lParam is a pointer to a notification message (NMHDR) structure that contains the notification code and
// additional information. For some notification messages, this parameter points to a larger structure that has
// the NMHDR structure as its first member.
//
// If the message is handled, the result code must be stored in pResult
//
// Returns nonzero if message is processed, otherwise 0.
BOOL Foo::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
  NMHDR* pnmh = (NMHDR*)lParam;
  if (TCN_SELCHANGING == pnmh->code)  // tab control is about to change
  {
    // do something
  }
  else if (TCN_SELCHANGE == pnmh->code)  // tab control has changed
  {
    // do something
  }
  [...]
  return BaseClass::OnNotify(wParam, lParam, pResult);
}