Inter-Process Communication

Introduction
Perhaps one of the most important means of communication in windows is Messages. The traditional program starts at your main() function, moves down line-by-line in your code, and eventually exits. The Windows concept is different. The way you program in windows is by responding to events. These events are called messages.
Messages can signal many events, caused by the user, the operating system, or another program. An event could be caused by a mousemove, a key-press, or by your window getting resized. There are 2 kinds of messages: a window message, or a thread message. Since Threads are an advanced issue, I'll refer only to window messages.
Window Messages:
In general, a message must be sent to a window. All the messages sent to you are stored in a Message Queue, a place in the memory which stores message which are transferred between applications.
Message Loop:
the way you retrieve messages from the Message Queue is by creating a Message Loop. A Message Loop is a loop that checks for messages in the Message Queue. once a message is received, the Message Loop dispatches the message by calling a Message Handler, a function designed to help the Message Loop at processing the message.
The Message Loop will end when a WM_QUIT message is received, signaling the application to end. This message could be sent because the user selected Exit from your File menu, clicked on the close button (the X small button in the upper right corner of your window), or pressed Alt+F4. Windows has default Message Handlers for almost all the messages, giving your window the default window behavior. In fact, all the standard controls are simply windows with Message handlers. Take a Button for example. When it gets a WM_PAINT message it will draw the button. When you Left-click the button, it gets a WM_LBUTTONDOWN message, and it draws the pressed-button. When you let go of the mouse button it receives a WM_LBUTTONUP message, and respectively draws the button.
Windows defines many different message types (which are stored as UINTs). They usually begin with the letters "WM" and an underscore, as in WM_CHAR and WM_SIZE. The names of the message are usually a good indicator of what they represent. WM_SIZE for sizing messages, WM_CHAR for character entry messages and so on. The naming convention in MFC for message handler functions is to take away the "WM_" and replace it with "On", so the message handler for WM_SIZE is usually called OnSize.
A message comes with 2 parameters that give you more information about the event. Each parameter is a 32-bit value: lParam and wParam. For example: WM_MOUSEMOVE will give you the mouse coordinates in one paramter, and in the other some flags indicating the state of the ALT, Shift, CTRL and mouse buttons.
A Message may also return a value which allows you to send data back to the the sending program. For example, the WM_QUERYENDSESSION message sent by windows before the computer is shutdown, expects you to return a Boolean value. If your application can terminate conveniently, it should return TRUE; otherwise, it should return FALSE. Other message such as the WM_CTLCOLOR messages expect you to return an HBRUSH.
Note: In the rest of the tutorial I will focus on MFC for simplicity reasons. All the information above applies to both SDK programs, and MFC programs.
Message Handlers:
Fortunately, MFC will give all the code needed for the message loop, One of the CWinApp member functions called by WinMain—Run—provides the message loop that pumps messages to the application's window. The only thing you need to do so you can receive messages is to create Message Handlers, and inform MFC of them. So, how do you create a Message Handler? Once you have an MFC C++ class that encapsulates a window, you can easily use ClassWizard to create Message Handlers.
Using ClassWizard to create Message Handlers:
Press Ctrl + W to start the ClassWizard, or right click the Add button and select ClassWizard from the context menu. Open ClassWizard, select Message Maps tab. In Class name select the name of your C++ class. on Object IDs select either the ID of a menu item (for messages caused by the user interacting with a menu), the ID of a control (for messages caused by the user interacting with a control), or the first option to handle messages other messages. Choose the message from the Messages list, WM_SIZE for example, and Click on Add Function. Click OK, then click Edit Code. ClassWizard will write a new empty function (OnSize for example) with the proper prototype in the class header. The code generated should look similar to this:
http://www.codeproject.com/images/minus.gif Collapse
void CAboutWindow::OnSize(UINT nType, int cx, int cy)
{
         CDialog::OnSize(nType, cx, cy);
         // TODO: Add your message handler code here


         // Here is where you can resize controls in your window, change

         // the size of a bitmap in it, or do what ever you can think of.

}
That's it, now you can handle messages. If you want to handle a message and then let the default message handler handle the message, you should call the base class member function that corresponds with the message. Take the following WM_CLOSE Message Handler as an example:
http://www.codeproject.com/images/minus.gif Collapse
void CAboutWindow::OnClose()
{
         //The User or another program is trying to close our window...

         //If you don't add code to close the window, your window will never close

}
If you want windows to get a shot at the message, you should call the base class member function OnClose:
http://www.codeproject.com/images/minus.gif Collapse
void CAboutWindow::OnClose()
{
         MessageBox(_T("Closing the window!"))
         //Call the Base class member function, which will close the window.

         CWnd::OnClose()
}
You could use this behavior to screen-out events. For example, a program that prompts the user if he is sure that he wants to close the window:
http://www.codeproject.com/images/minus.gif Collapse
void CAboutWindow::OnClose()
{
         int Ret = MessageBox(_T("Are you sure you want to close the window?"),
                              _T("Close Window?"), MB_YESNO);
         if(Ret == IDYES){
                 // The User is sure, close the window by calling the base class

                 // member

                 CWnd::OnClose()
         }
         else{
                 // The user pressed no, screen out the message by not calling

                 // the base class member


                 //Do nothing

         }
}
Sending Messages:
Besides receiving messages, you will often find your self sending messages. You might want to send messages to communicate between to windows in your program, or to communicate between different programs. In order to send a message you need a pointer to a c++ window class. This can be retrieved using various functions, including CWnd::FindWindow, GetDlgItem(), GetParent(), and more. The CWnd class has aSendMessage() member function which allows you to send messages to it's window. For example, Let’s say you have a CWnd pointer to the Calculator, and you want to close it. What you should do is send a WM_CLOSEmessage, which will notify the Calculator that it should close. You can use the following code. In order to get a pointer to Calculator, I use the static CWnd::FindWindow() function and pass the title of the window, which in our case is "Calculator".
http://www.codeproject.com/images/minus.gif Collapse
CWnd *pCalc;
//Get a pointer to the "Calculator" Window

pCalc = CWnd::FindWindow(NULL, _T("Calculator));
if(pCalc == NULL){
         //Couldn't find Calculator
}
else{
         pCalc->SendMessage(WM_CLOSE);
         //Presto! The Calculator should close.
}

How do MFC message handlers work?

Whenever your window receives a message, MFC will call a member function of your class. But how does MFC know what function to call?
MFC uses a technique called Message Maps. A Message Map is a table that associates messages with functions. When you receive a message, MFC will go through your Message Map and search for a corresponding Message Handler. I have showed in Part 1 how you add a Message Handler to the Message Map by using ClassWizard, but what really happens code-wise?
MFC uses a large set of rather complicated macros that add the Message Map to your classes. When you use ClassWizard to create a Message Handler, it will first add the function to your class, and add the corresponding macro to your Message Map. For example, examine the following ClassWizard generated WM_CLOSE handler:

Message Map: located in the class implementation

 Collapse
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
         //{{AFX_MSG_MAP(CAboutDlg)
 
         ON_WM_CLOSE()
         //}}AFX_MSG_MAP
 
END_MESSAGE_MAP()

Function Declaration: located in the class declaration.

 Collapse
protected:
         //{{AFX_MSG(CAboutDlg)
 
         afx_msg void OnClose();
         //}}AFX_MSG
 
         DECLARE_MESSAGE_MAP()

Function Implementation: located in the class implementation

 Collapse
void CAboutDlg::OnClose() 
{
         // TODO: Add your message handler code here and/or call default
 
         
         CDialog::OnClose();
}
By adding a DECLARE_MESSAGE_MAP statement to the class declaration, MFC adds the required code to declare the message map. The BEGIN_MESSAGE_MAP tells MFC where the Message Map begins, and identifies your class and it's base class. The reason it needs the base class is because Message Handlers are passed through c++ inheritance, just like any other function. END_MESSAGE_MAP obviously, tells MFC where the Message Map ends. In between these two macros is where your declare the Message Map entry for your Message Handler. MFC has many predefined macros, which associate messages with your member function. Take the the ON_WM_CLOSE macro as an example: It associates the WM_CLOSE message with your OnClose()member function. The macro takes no parameters since it always expects a function called OnClose() which is prototyped as afx_msg void OnClose(). This method gives you 2 advantages:
  1. It is easy to keep track of Message Handlers and the messages they handle
  2. MFC screens out any irrelevant and will break up lParam and wParam to parameters relevant to the message.
Also the return value is simplified, and the Message Handler is prototyped according to the message. For example: If the value should always be zero, MFC simplifies the process and allows you to declare the function as a void, and MFC will be responsible for returning 0. To find the name of the message handler that correlates with a given Message Handler macro you should look it up in the MFC documentation.
There are some messages that ClassWizard doesn't support, but you can manualy add your message handler by adding the function and Message Map macro as described above. If you add message-map entries manually, you may not be able to edit them with ClassWizard later. If you add them outside the bracketing comments//{{AFX_MSG_MAP(classname) and //}}AFX_MSG_MAP, ClassWizard cannot edit them at all. Note that by the same token ClassWizard will not touch any entries you add outside the comments, so feel free to add messages outside the comments if you do not want them to be modified. Messages that are not recognized by ClassWizard, such as message-map ranges, must be added outside the comments.

The all mighty ON_MESSAGE

Sometimes you will find yourself trying to handle a message that ClassWizard doesn't support, and it doesn't have a Message Map macro. MFC has a generic macro just for this kind of situation ON_MESSAGE. ON_MESSAGEallows you to handle any message that exists. The prototype of Message Handlers that use ON_MESSAGE is
 Collapse
afx_msg LRESULT OnMessage(WPARAM wParam, LPARAM lParam);
where OnMessage is the name of your handler function. The ON_MESSAGE macro takes 2 parameters: The address of the handler, and the message it should handle. For example: The following statement MapsWM_GETTEXTLENGTH to OnGetTextLength():
 Collapse
ON_MESSAGE (WM_GETTEXTLENGTH, OnGetTextLength)
OnGetTextLength is prototyped as
 Collapse
afx_msg LRESULT OnGetTextLength(WPARAM wParam, LPARAM lParam);

User-defined messages

Sometimes, you will need to communicate between 2 windows in your application or between 2 windows from different applications. An easy way to do this is by using User-defined messages. The name "User-defined" can be confusing at first; you define a User-defined message and not the user of your program. I have stated in Part 1 that messages are identified by numbers, and that Windows predefines standard messages. The way of using predefined messages is to simply use a number. To make sure that you don't conflict with the system defined messages you should use a number in the range of WM_APP through 0xBFFF:
 Collapse
#define WM_DELETEALL WM_APP + 0x100
//...
 
pYourDialog->SendMessage(WM_DELETEALL, 0, 0);
Handling a user-defined message is done with the ON_MESSAGE macro:
 Collapse
#define WM_DELETEALL WM_APP + 0x100
//...
 
//Message Map entry:
 
ON_MESSAGE (WM_DELETEALL, OnDeleteAll)
//OnDeleteAll is prototyped as 
 
afx_msg LRESULT OnDeleteAll(WPARAM wParam, LPARAM lParam);
//And is implemented as 
 
LRESULT OnDeleteAll(WPARAM wParam, LPARAM lParam){
         //Do What ever you want
 
         return somevalue;
}

Registered Windows Messages

The RegisterWindowMessage function is used to define a new window message that is guaranteed to be unique throughout the system. The macro ON_REGISTERED_MESSAGE is used to handle these messages. This macro accepts a name of a UINT variable that contains the registered Windows message ID. For example:
 Collapse
class CMyWnd : public CMyParentWndClass
{
public:
    CMyWnd();
 
    //{{AFX_MSG(CMyWnd)
 
    afx_msg LRESULT OnFind(WPARAM wParam, LPARAM lParam);
    //}}AFX_MSG
 
 
    DECLARE_MESSAGE_MAP()
};
 
static UINT WM_FIND = RegisterWindowMessage("YOURAPP_FIND_MSG");
 
BEGIN_MESSAGE_MAP(CMyWnd, CMyParentWndClass)
         //{{AFX_MSG_MAP(CMyWnd)
 
                 ON_REGISTERED_MESSAGE(WM_FIND, OnFind)
         //}}AFX_MSG_MAP
 
END_MESSAGE_MAP()
The range of user defined messages using this approach will be in the range 0xC000 to 0xFFFF. And you send it using the regular SendMessage() method:
 Collapse
static UINT WM_FIND = RegisterWindowMessage("YOURAPP_FIND_MSG");
//...
 
pFindWindow->SendMessage(WM_FIND, lParam, wParam);


Comments

Popular posts from this blog

Smart Pointers in C++ and How to Use Them

Operator Overloading in C++

How would you read in a string of unknown length without risking buffer overflow