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:
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:
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:
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:
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".
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:- It is
easy to keep track of Message Handlers and the messages they handle
- 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_MESSAGE
allows 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
Post a Comment