Message Management

Hazards of cross-process 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.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“To iterate is human, to recurse divine” - L. Peter Deutsch