Cross-process communication between VB applications is difficult to get right. I've seen developers try to achieve it through various approaches, ranging from writing messages into shared text files or registry keys to using full-blown ActiveX EXEs as communication servers. Each approach has its drawbacks: The former is prone to error, while the latter is very inefficient due to the cross-process marshalling that must take place with each call to an object in the server. One tried-and-true approach is to hook into your VB application's own message queue and listen for custom Windows messages sent from other processes, a technique known as subclassing.
In previous articles in this series on the joys of the Windows messaging system, I described how and why you'd implement subclassing in VB. I also showed you how to use the Windows API to send messages to your applications, unlocking hidden control capabilities. In this article, I'll explain how you can send messages to other applications and how sending custom messages can solve the cross-process communication conundrum. I'll also provide you with a reusable ActiveX DLL, Messenger.dll, which you can use to implement custom messaging in your own applications with little fuss.
Interact with other apps through messaging
Because messaging is the fundamental way Windows communicates with the applications it runs, any window or control in any application can be sent a message using SendMessage or one of its cousins. Further, the standard messages have the same meaning to all applications running on Windows. Those two facts lead to a conclusion that can be startling when you first realize it: By sending messages, you can easily control any other currently running applications—even applications that you aren't really supposed to be controlling. For example:
- Using the WM_xBUTTONDOWN and WM_xBUTTONUP messages, you can simulate mouse clicks in another application.
- Sending the WM_KEYDOWN and WM_KEYUP messages will simulate key presses.
- The truly mischievous programmer can even force another application to close by sending a WM_CLOSE message to its top-level window or use WM_ENDSESSION to fool it into thinking Windows is shutting down.
Figuring out who you're talking to
The key to sending a message to another window not contained in your application lies in obtaining its window handle, or hWnd. The Windows API has a number of functions meant to retrieve a particular window's handle. The most commonly used one is FindWindow, which can find a top-level window based on the text in its caption (lpWindowName) and return its window handle. The VB Declare statement for FindWindow is:
Private Declare Function FindWindow Lib "user32" _
Alias "FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Finding child windows
You can use FindWindowEx to find the window handle of a child window, such as a particular control on a form.
Custom messages provide a common language
Okay, let's review: Sending messages is an easy and efficient way to communicate with another application, and you can easily simulate user interaction with that application's GUI by sending standard Windows event messages. But what if you need to relay some nonstandard message to another one of your applications, perhaps that a data file is ready for processing? You could, of course, redefine the meaning of one of the standard messages to mean "Data is Ready," subclass the other app's main window, and implement special handling for that message.
Would that really work? Yes, it would, but it's far from an optimal solution. For one thing, your data processing app would be unable to normally respond to whatever message you had decided to redefine. It would be better if you could define a custom message that only your applications understood.
Happily, the RegisterWindowMessage API function does just that. You need to pass it a unique text message identifier, and it takes care of the rest—registering the message with Windows and returning a special message identifier that's guaranteed to be unique and is good for the current Windows session. Subsequent calls to RegisterWindowMessage with the same message identifier, whether from the same app or not, will return the same identifier assigned on the first call. The VB declaration for RegisterWindowMessage is as follows:
Private Declare Function RegisterWindowMessage _
Lib "user32" Alias "RegisterWindowMessageA" _
(ByVal lpString As String) As Long
Putting the pieces together
That's the last piece in the cross-process communication solution. Any app that needs to receive custom messages from another app should subclass its main window and provide a unique, globally known window caption to make its hWnd easily obtainable by FindWindow. (Or the first app should employ another method of locating the second app's hWnd.) Both apps should register the same custom window message and hang onto the identifier they receive. When one app needs to notify another app of the special event that the custom message represents, it finds the other app's window handle and sends the custom message using one of the SendMessage variants. Any additional information can be communicated by the use of SendMessage'swParam and lParam parameters or its return code.
As promised, I've produced a special Messenger component that can be used to easily implement custom messaging in your VB6 applications. The component's source can be found in the following code listings:
- Listing A is the source for the component's main class, cMessenger.
- The source for a private utility class called cMessageInfo is in Listing B.
- The component makes use of function addresses and needs a utility module, which you'll find in Listing C.
Messenger takes care of subclassing, registering, listening for, and sending custom messages for you. It's instance-safe, so multiple applications can use the same copy of the component.
The StartListening method accepts as its only parameter the window handle of a window to subclass. It then inserts its own window procedure for the window and begins waiting for custom messages, which you define using the RegisterMessage method. The class notifies its host of the arrival of a custom message by raising an IncomingMessage event and responds only to custom messages you registered through RegisterMessage. Be sure to unhook cMessenger from a window's message queue by calling StopListening before closing the window.
To send messages to another application, you call the SendMessage method, which accepts the caption of the window you want notified and the text of a message you previously registered with it through RegisterMessage. The class makes use of some rudimentary caching by keeping track of windows you previously sent messages to in a private collection. You can manually add new windows to the cache by using the RegisterWindow or RegisterWindowByHandle methods.