Communication between windows programs using WM_COPYDATA messages

Author: Wolfgang Büscher (DL4YHF)
Rev/Date: 2004-10-02 (YYYY-MM-DD)
Master copy: yhf_comm\yhf_comm_info.htm


This document describes a simple method how WIN32-applications can communicate with each other, using a simple message-based method. It was originally used in some of the author's hobbyist projects, without having to dig in the depths of OLE, COM/DCOM etc. Sending and receiving a windows message is very simple, so the WM_COPYDATA message is used to send blocks of data from one application ("program") to another was the basic idea. Without large overhead, the "message-based protocol" described in this document is open for future extensions too. But beware: At the moment this is only a description but not a specification, which means I may decide to change parts of the protocol in future (I'm open to suggestions)..

The "protocol" described in this document was originally implemented in "C" in a module called YHF_Comm. The sourcecodes and a simple test suite can be downloaded from the author's website. If you read this document online, you can download the sourcecodes from this link: Unpack them while maintaining the directory structure, so you don't have to modify the pathnames in the C++Builder project file. On the author's PC, the source modules were located in directories c:\CBproj\YHF_tools and c:\CBproj\MsgTester . There is a short description of the "Message Tester" in the zipped archive (MsgTester\readme.txt) which describes how to send and receive WM_COPYDATA messages with that tool (it may be helpful for you, if you are a programmer and want to implement the "protocol" in your own software too).

The WM_COPYDATA message

For general info about sending and receiving WM_COPYDATA messages in your own program, regardless of the programming language, search the net for "WM_COPYDATA message" which should take you to the official documentation. You may already have the "Win32 programmer's reference" in a file named Win32.hlp on your harddisk (this file is distributed with many programming languages, Borland C++Builder is only one of them).

Here just the basics about sending and receiving WM_COPYDATA messages in your application (partly copied from Win32.hlp) :

Definition of the WM_COPYDATA message

WM_COPYDATA message with the following message parameters:
wParam = (WPARAM) (HWND) hwnd; // handle of sending window
lParam = (LPARAM) (PCOPYDATASTRUCT) pcds; // pointer to structure with data

The "lParam" parameter points to a COPYDATASTRUCT structure that contains the data to be passed.

Return Values
If the receiving application processes this message, it should return TRUE; otherwise, it should return FALSE.

An application must use the SendMessage function to send this message, not the PostMessage function.
The data being passed must not contain pointers or other references to objects not accessible to the application receiving the data.
While this message is being sent, the referenced data must not be changed by another thread of the sending process.
The receiving application should consider the data read-only. The pcds parameter is valid only during the processing of the message. The receiving application should not free the memory referenced by pcds. If the receiving application must access the data after SendMessage returns, it must copy the data into a local buffer.

The COPYDATASTRUCT structure contains data to be passed to another application by the WM_COPYDATA message.

typedef struct {
   DWORD dwData;
   DWORD cbData;
   PVOID lpData;

Members of the COPYDATASTRUCT :

Specifies up to 32 bits of data to be passed to the receiving application.
In the YHF_COMM module (by DL4YHF), this member carries a two-letter MODULE identifier (like "CL" for the command line interpreter), and a two-letter COMMAND identifier (like "EC", if the command line interpreter shall execute a command, passed in a text string).
Specifies the size, in bytes, of the data pointed to by the lpData member.

Points to data to be passed to the receiving application. This member can be NULL.
In the YHF_COMM module (by DL4YHF), this member may contain a text string. For example, the command line interpreter ("CL") may use this string as a command line, if the command identifier (3rd and 4th character in dwData) is "EC". Of course, this is application specific, and does not fall into the scope of the YHF_COMM module.

Sending the WM_COPYDATA message

Here is a code snippet from YHF_COMM.CPP, subroutine YHF_SendCommandMessage, which shows how to send a WM_COPYDATA message to another application. The parameter pCmd is a pointer to a T_YHF_COMMAND structure (actually a "union"), which may contain a lot of different data formats, and also the application specific 2-character module- and command-identifier (we'll come back to these ID's in one of the next chapters). The characters (module-id and command) are combined into a single 32-bit value using a special union defined as follows (in "C"):

typedef union { // 8/16/32-bit - adressable "long"-data type
  BYTE b[4];
  WORD w[2];
  INT32 l;
  LWORD ul;

The four characters are written into the "b"-components of this union (byte-by-byte), and read as a single 32-bit value ("ul") from it. If your programming language does not support unions, you may alternatively combine the 4 characters into a single 32-bit integer manually, using bit-shift operations. The 1st character (=byte(0)) will be the least significant byte, the 4th character the most significant byte because all 80x86-"Intel"-compatible CPU's use the "Little Endian" format. "Little Endian" means that the low-order byte of the number is stored in memory at the lowest address.

The "cu" component of the T_YHF_COMMAND struct is such a byte-adressable longword type.

COPYDATASTRUCT cds;   // declare a variable with type copy-data-struct" (windows API)
// Fill the members of the COPYDATASTRUCT.
// Input: pCmd = pointer to a T_YHF_COMMAND struct
// First copy the 4 characters for module + command into dwData
cds.dwData = pCmd->;  // combine 4 characters in a single WORD
cds.cbData = pCmd->lDataSize; // count of bytes in data block
cds.lpData = pCmd->du.bData;  // pointer to data block
 // now SEND the message ("SEND", not "POST" !)
return SendMessage( 
    (HWND)hDestWindow, // HWND hWnd = handle of destination window
    WM_COPYDATA,       // UINT Msg = message to send
   (LPARAM)&cds ); // 2nd msg parameter = pointer to COPYDATASTRUCT

The return value will be 0(zero) if the message could not be delivered. Everything else means "ok". MS suggests to use only 0 or 1 for the 32-bit return value but the return value may be used for other purposes also (for example, several bits in the return value may provide information if a response message will be sent).

Sounds easy so far ? One problem to be solved is, we need to know the "handle" of the destination window. This is a numeric value which will be assigned by the operating system when starting a program (you never know which "handle" your window will have, it only remains valid through the window's lifetime). But there are several ways to retrieve this value; one of them will be explained in the next chapter.

Because the handle of the sending window is passed to the receiving window (alias "application" in this case), the receiver will know how to send an "answering message" to the caller if necessary. More about handling received messages follows later.

Retrieving the "other program's" window handle (method A)

To send a windows message from "our own" program to "another" (already running) program, we need to know the other program's window handle. All "window handles" are assigned by the operating system during runtime, so the handle values cannot be hard-coded in the sending subroutine.

We also need "our own" window handle when sending messages, but this is easy - just save the result from the CreateWindow() function which creates your main window in a variable. If you use a "visual" programming environment like Borland C++ Builder instead of pure WinAPI programming, there will be even simpler.

In the module YHF_COMM, the following method is used to retrieve the other application's window handle: The FindWindow() function is used for this purpose. Look at this sourcecode snippet from YHF_FindWindowByName() :

// The FindWindow function retrieves the handle to the top-level window
// whose class name and/or window name match the specified strings.
// This function does not search child windows.
long lResult = (long)FindWindow(
   pszWindowName, // pointer to class name, for example "TSpectrumLab"
   NULL );        // LPCTSTR lpWindowName = pointer to window name

If the above function succeeds, lResult will be non-zero, and it can be used later as the hDestWindow parameter for sending a message to the other program .

< to be continued >

Handling received messages

To receive WM_COPYDATA messages in your own application, you must add a message handler in the message processing loop. How to achieve this, depends on your programming language - you may do a web search on "Windows Message Handler". Here just a few starting points:

In the message handler, check if the message code is WM_COPYDATA, and -if so- convert the message parameter "LParam" into a pointer to a COPYDATASTRUCT. Then you can let YHF_HandleCopydataMessage() do the rest, or write your own handler for it. The basic principle is as follows:

First split the dwData parameter of the message into four characters. The first two characters are the "module", the second two the "command". The following modules existed at the time of this writing (feel free to extend this table ;-) :

Predefined Module Identifiers for DL4YHF's inter-application message handler
Module Identifier Meaning
CL Command Line Interpreter
AU Audio Data

The third and fourth character in the message's dwData parameter contain the "command". They are specific to the "module ID". Here, for example, a few "commands" understood by the Command Line Interpreter built inside DL4YHF's Spectrum Lab:

Command Identifiers for the command line interpreter in DL4YHF's Spectrum Lab
Command Identifier Meaning
CF Calculate Function (with result returned in response message)
EC Execute Command (no result, will not send a response message)

A practical example: To stop the spectrum analyser, send the following message..

 // Set module ID "CL"  + command ID "EC" :
 cds.dwData = ('C')+((DWORD)'L'>>8)+((DWORD)'E'>>16)+((DWORD)'C'>>24);
 cds.lpData = "spectrum.pause=1";        // command sent to SpecLab's interpreter
 cds.cbData = strlen((char*)cds.lpData); // count of bytes in data block


last modified: 2004-10-02 (ISO-date-format: YYYY-MM-DD)

--... ...-- ...-.-