Message Management |
|
There are two kinds of messages which you, as a programmer, can defined. The first kind is the compile-time-constant variety. I have found, after years of experience, These Are Not Your Friend. I'll nonetheless describe them as many people still prefer them, but as a matter of opinion I avoid them as much as I can. The second kind are messages which are guaranteed to be unique within a system. I much prefer these, and use them exclusively.
Older books on Windows programming tell about how to define user-defined messages using the symbol WM_USER. This technique is obsolete. There were too many problems with WM_USER-based symbols conflicting with messages that Microsoft was using. The new method is to use WM_APP as the base. If you have something that uses WM_USER, the usage is identical to the usage of messages based on WM_APP. Therefore, I will not reproduce the discussion here.
If you are comfortable with the idea of compile-time constant messages--and after you read the next section, you may not be--then you can use definitions based on the symbol WM_APP, which Microsoft now specifies as the desirable symbol to use. The correct form of the definition of such a symbol is
#define UWM_MYMESSAGE (WM_APP + n)
where n is some integer, typically a small integer like 1, 2, 3, etc. This defines a value which identifies the message. While strictly speaking the parentheses are not mandatory, good programming practice demands their presence.
I prefer a naming convention that does not conflict with the Microsoft naming convention. For one thing, it makes your code difficult for someone else to read and understand; for another, it makes it hard for you to read and understand. My preference is to use a UWM_ prefix (User Window Message); other people have used WMU_ (Window Message, User), and you can pick any convention you want, but do not use prefixes that conflict with those already in use by Microsoft.
Note that there is absolutely no requirement that every user-defined message be unique. Messages are always interpreted in the context of a particular window. Thus, you can have messages like
#define UWM_PAINT_VIEW_PURPLE (WM_APP + 7)
#define UWM_RESET_VIEW (WM_APP + 7)
These are perfectly valid providing that the view that accepts the purple request is never sent a reset request, and vice-versa.
To create an entry in the table to dispatch on these messages, you make an entry
ON_MESSAGE(UWM_RESET_VIEW, OnReset)
This requires that you define the handler OnReset. In the message handler part of your .h file, you add the declaration
afx_msg LRESULT OnReset(WPARAM, LPARAM);
When your window class receives the UWM_RESET_VIEW message, it will call the OnReset handler.
There are several problems with constant messages.
The way this is solved is by using a Registered Window Message. This is a message which is guaranteed to be unique. Only those windows or processes or DLLs that create it, and those which specifically use it, will actually have the same message number.
How is this done?
There is a range of messages, 0xC000 through 0xEFFF, which is reserved for use by registered window messages. When you call the API function ::RegisterWindowMessage you pass it a string. It looks up the string in an internal table. If it finds the string, it returns the integer which has been assigned. If it does not find the string, it creates a new entry in the table, assigns it a new integer value from the range 0xC000 through 0xEFFF, and returns that integer value. The table in which these strings are kept is global to all processes on the machine, so if two completely different programs register the same string, they both get the same integer. They may now communicate with each other via these messages.
No, you can't "unregister" a message. You don't need to.
So a simple form of the user-defined message would be to declare a variable, which I usually just make static in each module that uses it:
static const UINT UWM_RESET_VIEW =
::RegisterWindowMessage(_T("UWM_RESET_VIEW"));
I'll tell you later why this still isn't quite adequate, but take it as a working example for the moment.
The way you handle a registered message is just like you handle a constant user-defined message. The macro is slightly different, but the rest of the handling is the same. Add the line to your MESSAGE_MAP:
ON_REGISTERED_MESSAGE(UWM_RESET_VIEW, OnReset)
As with the constant messages, you will need to define the OnReset handler by adding a declaration to the handler section of your class in the .h file:
afx_msg LRESULT OnReset(WPARAM, LPARAM);
The handlers for a registered window message and for a constant user-defined message are absolutely identical. In fact, in the handler, you can't really tell if the programmer has rewritten the code to use one or the other.
You may have already discovered a problem that is identical for both constant messages and registered messages. What if you choose a nice, obvious name like "UWM_RESET_VIEW" as the string name of the message, and some other programmer has also chosen another nice, obvious, simple name for his or her DLL, such as "UWM_RESET_VIEW". Have we really made progress?
No.
But there is a way around it. There is a program which is part of the SDK, called GUIDGEN. What this program does is create a unique 128-bit binary value. Each time you create a new Globally Unique IDentifier, a GUID, you can be sure that it is really, truly, unique. It is not only unique for you, it is unique for everyone, everywhere, all the time. It incorporates the time and date, the MAC address from your network card (and if you don't have a network card, it uses another method, which has something like one chance in 263 of conflicting with another GUID), and a bunch of other information. Therefore, there is no way, short of explicit collusion between two programmers, that they will use the same GUID.
All my registered window messages use a GUID as part of their name.
When you run GUIDGEN, you get a screen that looks like this:
Select option 4, "Registry format". Click the "Copy" button. A copy of the string as shown in the Result box will be placed in the clipboard. Then go to your editor and type something like
#define UWM_RESET_VIEW_MSG _T("UWM_RESET_VIEW-<paste>")
which creates a name like
_T("UWM_RESET_VIEW-{4E7F6EC0-6ADC-11d3-BC36-006067709674}")
I actually have a macro
#define DECLARE_USER_MESSAGE(name) \
static const UINT name = ::RegisterWindowMessage(name##_MSG);
which handles most of the hassle for me. While strictly speaking my GUID can suffice for all my messages, I usually just generate a new GUID for each message.
Any time I want to create an instance of a message, for use in a message table or for posting, I just do
DECLARE_USER_MESSAGE(UWM_RESET_VIEW)
and, because of my convention of naming the string the same, I get the variable initialized. Typically these appear in the outer block of each module that uses the symbols. All I have to make sure is that the header file for DECLARE_USER_MESSAGE and the header file for the messages I want are both included.
So why do I even bother to put a readable name into the string? The 128-bit GUID should be sufficient! Well, the truth is that the readable name is totally redundant and essentially irrelevant. Unless you happen to be using Spy++ to trace message traffic. In that case, you really, really want to see something you understand, and 128-bit GUIDs are not really high on the list of easily readable values. The only reason the readable string is there is for the convenience of you, the developer. It doesn't matter, otherwise.
User-defined messages are an interface. As an interface, they need to be defined. I have some macros in my text editor that make this easy. They generate a standard header for me which I fill in. The header looks something like the examples below.
Example 1: A message which has simple parameters, and which is sent but whose response doesn't matter:
/***************************************************************
* UWM_COLOR_IT
* Inputs:
* WPARAM: ignored, 0
* LPARAM: RGB value to use for coloring
* Result: LRESULT
* Logically void, 0, always
* Effect:
* Causes the view to repaint itself in the specified color
***************************************************************/
#define UWM_COLOR_IT_MSG _T("UWM_COLOR_IT-{4E7F6EC1-6ADC-11d3-BC36-006067709674}")
Example 2: A message which has no parameters, and which is sent for the purpose of getting a result:
/***************************************************************
* UWM_QUERY_CUT
* Inputs:
* WPARAM: ignored, 0
* LPARAM: ignored, 0
* Result: LRESULT
* (LRESULT)(BOOL) TRUE if a cut operation is possible
* FALSE if there is no selection
***************************************************************/
#define UWM_QUERY_CUT_MSG _T("UWM_QUERY_CUT-{4E7F6EC3-6ADC-11d3-BC36-006067709674}")
Example 3: A message which has complex parameters, and which returns an interesting value:
/***************************************************************
* UWM_SET_COORD
* Inputs:
* WPARAM: (WPARAM)(BOOL) FALSE for absolute
* TRUE for relative
* LPARAM: (LPARAM)MAKELONG(x, y)
* Result: LRESULT
* (LRESULT)MAKELONG(x, y) previous coordinate value
* Effect:
* Sets the coordinate in the view, and returns the previous
* coordinate.
* Notes:
* The x,y values are added to the current position if
* WPARAM is TRUE, otherwise they replace the current
* position.
***************************************************************/
#define UWM_SET_COORD_MSG _T("UWM_SET_COORD-{4E7F6EC2-6ADC-11d3-BC36-006067709674}")
Note that I carefully document the casting that is required to get the desired WPARAM and LPARAM values. Then I know when I'm writing my method how I should cast it. Here's an example of a handler for another message, which takes a pointer to an object.
/***************************************************************
* CMyView::OnAssignMyInfo
* Inputs:
* WPARAM: ignored, 0
* LPARAM: (LPARAM)(LPMYINFO)
* Result: LRESULT
* Logically void, 0, always
* Effect:
* Modifies the current view by the values in LPMYINFO
* Notes:
* If LPMYINFO.IsValidCount is FALSE, the count field is
* not modified
***************************************************************/
LRESULT CMyView::OnAssignMyInfo(WPARAM, LPARAM lParam)
{
LPMYINFO info = (LPMYINFO)lParam;
visible = info.visible;
if(info.IsValidCount)
count = info.count;
return 0; // value is ignored
}
When you pass a pointer as a WPARAM or LPARAM, you need to be careful about what you pass and how you pass it. The key is in whether you do a SendMessage or a PostMessage. If you do a SendMessage, you can use a reference to an object on the stack, an object in static storage, or an object on the heap. This is because control does not resume in the thread that does the SendMessage until the handler completes its action. In particular, this means that any address referencing the stack remains valid during the processing of the message. This does not apply to cross-process messages! See below!
However, if you ever plan to use PostMessage to pass a pointer to an object, then you are constrained to always use a static or heap-based object. The address of a stack-based object is nonsensical in this context. This is because the function that performs the PostMessage will quite possibly return long before the message is processed--in fact, if it is posting the message to the same thread, it must return before the message is processed. This means that the object reference to the stack is pointing to valid space, but space that may have been overwritten. If the object on the stack is a C++ object that has a destructor, the destructor will be called and objects within the object on the stack might be deallocated. For example, you cannot use PostMessage in the following context:
{
CString s;
// ... assign a value to s
PostMessage(UWM_LOG_MESSAGE, 0, (LPARAM)&s);
}
Even if the address referenced on the stack is not overwritten by subsequent calls, the data referenced by the string is deallocated by the CString destructor. When the handler is called and attempts to reference the string, you will get some effect between completely incorrect data and an access fault. The chances of anything working as you expect are incredibly slim.
However, the following code is correct. We first look at the definition:
/***************************************************************
* UWM_LOG_MESSAGE
* Inputs:
* WPARAM: ignored, 0
* LPARAM: (LPARAM)(CString *): String to log
* Result: LRESULT
* Logically void, 0, always
* Effect:
* Logs the message in the debug output window
* Notes:
* The CString object must explicitly deallocated by
* the handler for this message.
* This message is usually sent via PostMessage. If sent
* via SendMessage, the sender must not delete the
* CString, nor should it assume upon return that it has
* not been deleted. The sender must send a pointer to a
* deletable string. To do otherwise is a serious error.
***************************************************************/
Then we can write the handler:
/***************************************************************
* CMainFrame::OnLogMessage
* Inputs:
* WPARAM: ignored, 0
* LPARAM: (LPARAM)(CString *): String to log
* Result: LRESULT
* Logically void, 0, always
* Effect:
* Logs the message in the debug output window
* Notes:
* The CString object must explicitly deallocated by
* the handler for this message
***************************************************************/
(Note that I often replicate the definitions in both places; while it means I have to update the handler comments if I make changes, since I have to edit the code anyway it is no serious hazard).
LRESULT CMainFrame::OnLogMessage(WPARAM, LPARAM lParam)
{
CString * s = (CString *)lParam;
c_Log.AddString(*s);
delete s;
return 0; // logically void, value ignored
}
It is often a Bad Idea to send messages across threads. The problems of potential deadlock are substantial. If you SendMessage to a thread that is blocked, and that thread is waiting for the sending thread to complete, you're dead in the water. Your process is blocked, and it will stay blocked, and you will have to set of sticks of dynamite under it to make it go away.
Note that you can get into this inadvertently. You should never, ever manipulate a GUI object from a worker thread, or a GUI object owned by a thread other than the user-interface thread that is sending the message. If you are getting deadlock, these are key problems to look for.
You are safest, when transferring information across threads, to use PostMessage to handle it. A cross-thread PostMessage will not block the sender. If you need a positive acknowledgement, it is often best to restructure your algorithm to be a two-phase algorithm, where one method posts a message to the alternate thread, and expects the alternate thread to post a message back indicating completion. While harder to program, it avoids the deadlock issue.
If you must send across threads, and you need a positive response, and you have the potential for deadlock, you should use SendMessageTimeout. This will send the message to the thread, but if the thread does not respond within the timeout period, the message is completed, and you get control back, with an error indication. A typical call I use looks like the example below.
// The following replaces the unconditional send
// result = wnd->SendMessage(UWM_QUERY_SOMETHING);
//
DWORD result;
if(!SendMessageTimeout(wnd->m_hWnd, // target window
UWM_QUERY_SOMETHING, // message
0, // WPARAM
0, // LPARAM
SMTO_ABORTIFHUNG |
SMTO_NORMAL,
TIMEOUT_INTERVAL,
&result))
{ /* error or timed out */
// take some appropriate action on timeout or failure
// if we care, we can distinguish timeout from other
// errors
if(::GetLastError() == 0)
{ /* time out */
// take timeout action
} /* time out */
else
( /* other error */
// take error action
} /* other error */
} /* error or timed out */
else
{ /* successful */
// decode the result
switch(result)
{ /* result */
case ...:
break;
case ...:
break;
} /* result */
} /* successful */
Sending Messages Across Processes
Sending a user-defined message across processes is somewhat more complex. First, you really have to use a Registered Window Message. Using a WM_APP-based message is somewhere between seriously dangerous and totally insane.
When you send a message across processes, you are implicitly sending it across threads. All of the caveats about cross-thread messages apply. But even more, there are other serious restrictions on cross-process messages.
The most significant one is that you cannot send a pointer across process boundaries. This is because process address spaces are separate, and a pointer has no meaning when it is received in the other process. For example,
LRESULT CMainFrame::OnLogMessage(WPARAM, LPARAM lParam)
{
CString * s = (CString *)lParam;
if(s[0] == _T('$')) // app crashes hard here
{ /* special message */
// ...
} /* special message */
}
When the operation s[0] is performed, the chances are almost dead certainty that the application receiving the message will take an access fault. The chances of the pointer being valid (and if it is, it will point to meaningless gibberish) are close to zero, and the gibberish pointed to will certainly not resemble a CString.
You can't even pass a pointer to shared memory, even DLL-shared-segment shared memory. This is because the shared memory is not guaranteed to be in the same locations in all processes that share the memory (this is described in detail in Win32 Programming). Essentially, figure that you can't pass information across the process boundary using ordinary messages.
In addition to all of the previous issues, posting messages across processes have additional hazards. For example, you cannot under any circumstances pass an address across process boundaries.
This is true even if the address is in a shared memory area, such as shared DLL data segment or a memory-mapped files. Just because an address is valid in one process does not guarantee it is valid in a different process. And if you have evidence that says this is not so, that you've seen that you can pass a shared memory pointer from one process to another and it is valid, be aware that the phenomenon you are seeing is transient and accidental. While Win32 makes a sincere attempt to map shared memory into the same location in each process, it is important to emphasis that it does not guarantee this. Depending on this opens you to serious problems with the delivered product, which might not show up for months. But that's an essay for some other time.
In addition, you can hit nasty deadlock situations, where a SendMessage does not complete, and hangs forever, or appears to.
Here's a typical example, and we've actually seen this in practice:
My application wants to be the one-and-only-copy running. One of the many detection mechanisms for this is to search for a window of the same class (which is a Bad Idea, since the class names are invented by MFC), the same caption (which is a Bad Idea since the captions may vary depending on which MDI child is active), etc. One of the better ways is to send a Registered Window Message to each window and look for a specific response. It used to be easy; it is now somewhat harder. The naive approach is to do something like shown below. What I show here is the handler for the EnumWindows call:
BOOL CMyApp::myEnumProc(HWND hWnd, LPARAM lParam)
{
CMyApp * me = (CMyApp *)lParam;
if(::SendMessage(hWnd, UWM_ARE_YOU_ME))
{ /* found duplicate */
// ... do something here, e.g.,
me->foundPrevious = TRUE;
return FALSE; // stop enumerating
} /* found duplicate */
return TRUE; // keep enumerating
}
If I have a handler in my main window
LRESULT CMainFrame::OnAreYouMe(WPARAM, LPARAM)
{
return TRUE;
}
then in principle any message sent to any window that doesn't recognize the Registered Window Message will pass it on to DefWindowProc, which, because it does not recognize the message, will return 0, which means that only my window will respond. This is also a technique for locating a client or server window of an affiliated application.
So if I initially set foundPrevious to FALSE and call EnumWindows(myEnumProc, (LPARAM)this) then when I return I'll know if I found a previous instance.
There are three major flaws in this code, none of which are evident.
First, while this is a fine technique for recognizing an affiliated window such as a client window or server window (and in that case, the message would probably pass a window handle as WPARAM to tell the affiliated window who sent the message), it won't work for finding a duplicate instance of the application. That's the topic of a different essay.
Second, it can deadlock on a failed or non-responsive application, leaving you hanging.
Third, Microsoft has gone out of their way to make life totally miserable by installing, on Windows 98, as part of the FrontPage product, a window which ALWAYS responds with TRUE to any message!!!! While it is hard to believe that anyone would have been stupid enough to do this, they have done it, and we all suffer.
Let's look at these problems in detail:
The first flaw is very evident when you have a desktop configured to launch an application on a single click. A user who is accustomed to double-clicking to launch an application will double-click, and this will launch TWO copies of the application. The first one does the EnumWindows loop and sees that it is the only instance running. It then proceeds to create its main window and continue on. The second instance comes up, and does the EnumWindows loop as well. Because we are in a preemptive multithreaded environment, it actually manages to complete its EnumWindows loop while the first application is still creating its main window! So the first application's main window isn't up yet, and the second application doesn't find it, and thinks that IT is the one-and-only application. So we have two copies running, which is not what we wanted.
We can't avoid this. Come up with any scenario (such as not calling the EnumWindows until after the main window is created) and you still get the same race condition.
So we rule this out for finding duplicate copies of the application. But let's look at the issue of polling all the windows to find a client or server. In this case, I might do
HWND target = ::SendMessage(hWnd, UWM_HERE_I_AM,
(WPARAM)m_hWnd);
which is handled by the response:
LRESULT CTheOtherApp::OnHereIAm(WPARAM wParam, LPARAM)
{
other = new CWnd;
other.Attach((HWND)wParam);
return (LRESULT)m_hWnd;
}
which looks like a pretty good exchange of window handles. If you don't know what Attach does, read my essay on Attach/Detach.
Now, somewhere on the system I've got a wedged application. Perhaps it is a copy of IE waiting for an infinitely-long Web page timeout, and is blocked somewhere in the network handler. Perhaps it is some poorly-designed program that is doing a convolution algorithm on a terabit bitmap, and is doing it in the one-and-only main thread, and isn't allowing any messages to come in (it will be finished in a week or two). Perhaps it is one of your own programs you're debugging that is lost in a loop. Whatever. The key is that there is very long, possibly infinite delay, in an application you may not have even heard of. And guess what? Your innocent application hangs.
The solution to this is to use ::SendMessageTimeout, so if one of these applications would block you, you will not hang. You should select a small timeout interval, such as 100ms, otherwise, you'll have a simply very long startup.
Now to the nasty part, in which Microsoft really does us in. There is some application which has a window whose class is "Front Page Message Sink", and which exhibits the pathological and unsociable behavior that it returns a nonzero value for EVERY message it receives. This is colossally stupid. It is undocumented, it violates all known Windows standards, and it will be fatal to you. This only shows up if you have Personal Web Server running. But it is absolutely inexcusable to have done this.
All I know is that I see it return the value 1, consistently, for any kind of Registered Window Message it receives. Perhaps this is the only value it returns. I don't know, and nobody at Microsoft responded to my bug report with anything explaining what is going on.
Thus testing the result of the ::SendMessage or the DWORD result of ::SendMessageTimeout against 0 is not informative. Thus far, if you see 0 or 1, you can't depend on the value having any meaning. With any luck, Microsoft will fix this (hah!), and ideally they will not introduce other gratuitous examples of such insanity in the future.
I worked around this by modifying the receiving handler to return a non-zero, non-one value. For example, I actually use ::RegisterWindowMessage to get a value, although I suppose I could have used ::GlobalAddAtom. At least I know that ::RegisterWindowMessage will return a non-zero, non-one value.
// ... in a header file shared by both applications
#define MY_TOKEN T("Self token-{E32F4800-8180-11d3-BC36-006067709674}")
// ... rewrite the handler as
LRESULT CMainFrame::OnAreYouMe(WPARAM, LPARAM)
{
return ::RegisterWindowMessage(MY_TOKEN);
}
// In the calling module, do something like
UINT token = ::RegisterWindowmessage(MY_TOKEN);
// ... and recode the caller, the EnumWndProc, as
DWORD result
BOOL ok = ::SendMessageTimeout(hWnd,
(WPARAM)m_hWnd, // WPARAM
0, // LPARAM
SMTO_ABORTIFHUNG |
SMTO_NORMAL,
TIMEOUT_INTERVAL,
&result));
if(!ok)
return TRUE; // failed, keep iterating
if(result == token)
{ /* found it */
// ... do whatever you want here
return FALSE; // done iterating
} /* found it */
If you are trying to pass a handle back, instead of just a designated value, you would have to make the test
if(result != 0 &&
result != 1) // avoid Microsoft problem
{ /* found it */
target = new CWnd;
target->Attach(hWnd);
// ... do whatever you want here
return FALSE; // done iterating
} /* found it */
Note that if you choose to use ::FindWindow, it does an ::EnumWindows internally, and consequently can be subject to the same permanent hang. Using ::FindWindow is extremely hazardous, since it assumes that in fact every ::SendMessage will complete. Note that ::FindWindow does a ::SendMessage(hWnd, WM_GETTEXT, ...) to get the window text to search for.
There is a possibly useful message, WM_COPYDATA, that will transfer information across the boundary. It does this by passing the data thru the kernel. Space is allocated in the receiving process to hold the information that is copied, by the kernel, from the source process to the target process. Or something that resembles that. The implementation details are actually concealed from you.
The sender passes a pointer to a COPYDATASTRUCT, which is defined as a structure of the following:
typedef struct tagCOPYDATASTRUCT {
DWORD dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
The dwData member holds an arbitrary 32-bit value that is being passed to the target. You can set this to any value the two processes agree on. The cbData member tells how many bytes are in the value referenced by lpData. When the target process receives the information, it handles it via a method
BOOL CMainFrame::OnCopyData(CWnd* pWnd,
COPYDATASTRUCT* pCopyDataStruct)
The CWnd * is a reference to the sending window, and the COPYDATASTRUCT * references the COPYDATASTRUCT that was passed in. Note that you don't know the actual address in the sender. The data which is passed in must not contain pointers.
There are some potential problems with WM_COPYDATA, in that you need to identify who has sent it in order to understand if it is valid, or you must have some other way to identify it. One way to handle it is to use our old friend the GUID. If you put a GUID in the beginning of the data packet, you can compare it to the expected GUID and if they are equal you know for certain that the packet you received is the packet you want.
You must not store the pCopyDataStruct.lpData pointer, because after you return from the OnCopyData handler the pointer should be assumed to be no longer valid. You must also not attempt to write into the data referenced by the lpData pointer; it must be treated as read-only.
Two other considerations are important: you must not put pointers into the data being passed, and you must completely process it before returning to the SendMessage that sent it. This means you must not store the lpData pointer in any place that would be accessible after you return from the handler (storing it in a local variable in your handler is perfectly fine, but not in a global variable or in any structure on the heap).
The problem of storing pointers is a little bit misleading. You must not store pointers in the structure you pass, even if they are pointers into the same area you are passing, because when the message is received the recipient has no control over where it is placed, and the pointers are likely to be nonsense. Obviously, you can't store pointers to other areas of your own memory because they will be completely nonsensical in the receiving process; they will be interpreted in its own address space.
But the misleading aspect is that if you have a structure which is entirely self-contained in the data area you are sending over (that is, every pointer refers to another structure in the area you are sending), then you can use based pointers to encode them. Based pointers are actually relative offsets measured from a specific starting point (such as the start of the area being transmitted), and are automatically adjusted by the recipient (as long as you set up the correct base value) so they are valid pointers. For example, consider the following structure
LPVOID basevalue; class ListElement { public: ListElement() { next = NULL; prev = NULL; } typedef ListElement __based(basevalue) * BPLISTELEMENT; protected: BPLISTELEMENT next; BPLISTELEMENT prev; int value; };
Notice some problems with this from the viewpoint of object-oriented programming. The operand of the __based qualifier must be an LPVOID global variable. For reasons that are known only to the minds of the compiler writers, this cannot even be a static class member! The name basevalue is bound at the time of the declaration to the global variable. This means that you have to set the value whenever you are doing any sequence of accesses. Using a global variable is not thread-safe, but you can declare it using __declspec(thread) to make the "global" variable actually a "thread global" unique to each thread.
For more information on based pointers, see my essay on based pointer techniques.
MFC has a wonderful message routing mechanism. A command message first comes to the active view, then the view frame, then the document, and so on. This works for WM_COMMAND and WM_UPDATE_COMMAND_UI messages, but does not work for any other kind of message, including user-defined messages. This is a real pain, and somewhat silly because it would be easy for Microsoft to implement. So a problem arises when you need to send a message to some sub-window, but you can't send a WM_COMMAND message because you are not directly responding to a GUI component.
Of course, you could invent a fictitious control and generate your own imitation WM_COMMAND messages. But this is very risky, and contributes to significant future unmaintainability.
What can you do?
I've done several things. Sometimes, I just put a message handler in the child frame whose sole purpose is to route the message to the child window contained in the frame. This can be a pain, but gives you serious control over how the messages are processed. Thus, the main frame, to which I post a message (often from a thread), has a handler of the form
// ... in the message map
ON_REGISTERED_MESSAGE(UWM_THREAD_DID_SOMETHING, OnThreadDidSomething)
// ... the handler
LRESULT CMainFrame::OnThreadDidSomething(WPARAM wParam, LPARAM lParam)
{
CView * active = GetActiveView();
if(active != NULL)
active->SendMessage(UWM_THREAD_DID_SOMETHING,
wParam, lParam);
return 0;
}
Sometimes you have to send the message not to the view, but to its parent frame window. This is done by the following code:
RESULT CMainFrame::OnThreadDidSomething(WPARAM wParam, LPARAM lParam)
{
CFrameWnd * active = GetActiveFrame();
if(active != this)
active->SendMessage(UWM_THREAD_DID_SOMETHING,
wParam, lParam);
return 0;
}
Note the special test above. If, for some reason, you had managed to kill off all the MDI children while the thread was still running, GetActiveFrame returns this, which would mean you would get into a semi-infinite SendMessage loop which would terminate when you ran out of stack space. Note that because the function is defined as returning this we don't have to worry about the possibility that a temporary window handle has been returned, a caution I discuss in my essay on the use of Attach/Detach. This requires that you use the CMDIChildFrame subclass, and introduce a similar handler there:
BEGIN_MESSAGE_MAP(CMyMDIChildFrame, CMDIChildWnd)
ON_REGISTERED_MESSAGE(UWM_THREAD_DID_SOMETHING, OnThreadDidSomething)
// and the handler is
LRESULT CMyMDIChildFrame::OnThreadDidSomething(WPARAM wParam, LPARAM lParam)
{
// ... do things to the frame window (resize, reposition?)
// and if appropriate, pass the message down...
SendMessageToDescendants(UWM_THREAD_DID_SOMETHING,
wParam, lParam,
FALSE, // only go one level deep
TRUE); // only to permanent windows
return 0;
}
Now, you may wonder why I didn't send it directly to the child window. Partly because I'm lazy, and SendMessageToDescendants does what I need. Partly because I don't need the result, because it came in from PostMessage. In a case where I had to route a message to the descendant where I needed the result, I'd instead write
CView * view = (CView *)GetWindow(GW_CHILD);
ASSERT(view != NULL && view->IsKindOf(RUNTIME_CLASS(CView)));
view->SendMessage(...);
User-defined messages are a powerful and flexible mechanism for handling the passing of information and control between levels of your application, threads of your application, and processes. However, using them effectively requires certain degrees of care. I favor using ::RegisterWindowMessage and GUIDs to define all messages. Interthread and interprocess sends are risky, and should be avoided; if you must, use SendMessageTimeout to ensure that you will not end up in deadlock.
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.