An anonymous pipe is the easiest way to establish single-instance one-way communication between related processes on the same machine. (With two anonymous pipes you can communicate in both directions.)
The parent and child program windows of the AnonPipe demo appear in Figure 15.1. In this example, the user has selected a small red triangle from the parents menu, and the resulting shape appears in the childs window.
NOTE
The AnonPipe demo is included on the CD accompanying this book, in the Chapter 15 folder.
Caveat on Testing the AnonPipe Demo
To test the AnonPipe demo, you will want to begin by compiling both the Parent and Child executables. Once these are compiled, you can execute the Parent program (in the Debug directory) and use the Parent program to launch the Child process. However, if you attempt to execute the Parent program from the Developers Workshop (as for debugging), you will receive a message stating: The system cannot find the file specified.
However, if you first copy the compiled Child.EXE program from the /Debug subdirectory to the /AnonPipe directory, the Parent.EXE application will again be able to find the Child.EXE program and launch it.
The problemif you are not already ahead of meis that under the debugger, the Parent.EXE application is not looking in the directory where it is located but in the immediate root directory (where the source files are found).
On the other hand, when the Parent application is executed directly, it looks in the current directory to find the Child.EXE for launch.
For alternatives, refer to the CreateProcess Parameters section earlier in this chapter, and feel free to modify the AnonPipe Parent.C source to experiment with different arrangements.
The parent process files are Parent.H, Parent.RC, and Parent.C. The child process files are Child.H, Child.RC, and Child.C. The two programs together share an additional header, Global.H, which defines structures and values both processes use to communicate with each other.
When the parent puts command information into the pipe, it uses a descriptive structure called FIGURE. A FIGURE variable holds values that represent commands from the parents menu. The commands determine the shape, size, and color of the figure the child should draw.
Figure 15.1: Parent and child windows of the AnonPipe demo
Initializing The global variables at the top of Parent.C include a handle for the child process, a handle for the pipe, and a FIGURE variable. These three pieces of information are what the parent needs to communicate with its child. WinMain initializes figure to describe a small red rectangle.
/*-
PARENT.C [anonymous pipe version]
Contains the parent process for the PROCESS demo program.
In this version, the two processes communicate through
an anonymous pipe.
*/
int WINAPI WinMain( HINSTANCE hinstThis, HINSTANCE hinstPrev,
LPSTR lpszCmdLine, int iCmdShow )
{
...
// This global FIGURE structure records whatever choices
// the user makes to choose the shape, size, and color
// of the figure drawn in the clients window. Here
// we initialize it to the programs startup defaults.
figure.iShape = IDM_RECTANGLE; // draw a rectangle
figure.iSize = IDM_SMALL; // dont fill the whole window
figure.iColor = IDM_RED; // make the rectangle red
...
}
Responding to System Messages The window procedure looks for only three messages. When the user begins to make a choice from a menu, the parent program intercepts WM_INITMENU to update the appearance of the menu. If the child process does not exist, the parent disables the commands that work only with the child present, including Terminate and all the shape options. Figure 15.2 shows two images of the Parent applications Options menu.
Figure 15.2: Two views of the Options menu
In the first instance (upper-left), no child process has been created and, therefore, all of the selections under Options are disabled. In the second instance (lower-right), after the child process is active, the menu options are enabled and, on the Shape submenu, the current selection is indicated by a checkmark.
The first message handler, WM_INITMENU, also places checkmarks by all the options currently selected.
/*
PARENT_WNDPROC
This is where the messages for the main window are processed.
*/
LRESULT WINAPI Parent_WndProc(
HWND hWnd, // message address
UINT uMessage, // message type
WPARAM wParam, // message contents
LPARAM lParam ) // more contents
{
switch (uMessage)
{
HANDLE_MSG( hWnd, WM_INITMENU, Parent_OnInitMenu );
HANDLE_MSG( hWnd, WM_COMMAND, Parent_OnCommand );
HANDLE_MSG( hWnd, WM_DESTROY, Parent_OnDestroy );
default:
return( DefWindowProc( hWnd, uMessage, wParam, lParam ) );
}
return( 0L );
}
The second message handler responds to WM_COMMAND messages from the menu. The user gives commands to start or terminate the child, to modify the shape the child draws, and to close the Parent program. If the user makes any selection from the Options menu, SendCommand writes the updated figure variable into the pipe. The third message handler ends the Parent program in response to WM_DESTROY.
The Parent_OnInitMenu procedure provides the handling to check and uncheck several menu options.
/*
PARENT_ONINITMENU
Check whether the child process exists and enable or
disable the Start, Terminate, and Options commands
accordingly. Also put checkmarks on the option commands
that reflect the users most recent choices.
*/
void Parent_OnInitMenu( HWND hWnd, HMENU hMenu )
{
// While the child process does not exist, some of the
// menu commands make no sense and should be disabled.
// These include the Terminate command and the figure
// options commands.
// get a handle to the options popup menu
HMENU hmenuOptions = GetSubMenu( hMenu, 1 );
if( hProcess )
{ // child process exists; enable Terminate and shape options
EnableMenuItem( hMenu, IDM_START, MF_GRAYED );
EnableMenuItem( hMenu, IDM_TERMINATE, MF_ENABLED );
EnableMenuItem( hmenuOptions, 0, MF_ENABLED | MF_BYPOSITION );
EnableMenuItem( hmenuOptions, 1, MF_ENABLED | MF_BYPOSITION );
EnableMenuItem( hmenuOptions, 2, MF_ENABLED | MF_BYPOSITION );
}
else
{ // child process does not exist; disable Terminate and shape options
EnableMenuItem( hMenu, IDM_START, MF_ENABLED );
EnableMenuItem( hMenu, IDM_TERMINATE, MF_GRAYED );
EnableMenuItem( hmenuOptions, 0, MF_GRAYED | MF_BYPOSITION );
EnableMenuItem( hmenuOptions, 1, MF_GRAYED | MF_BYPOSITION );
EnableMenuItem( hmenuOptions, 2, MF_GRAYED | MF_BYPOSITION );
}
// set a checkmark on one of the three shape commands
CheckMenuItem( hMenu, IDM_ELLIPSE,
( ( figure.iShape == IDM_ELLIPSE) ? (int)MF_CHECKED :
(int)MF_UNCHECKED ) );
CheckMenuItem( hMenu, IDM_RECTANGLE,
( ( figure.iShape == IDM_RECTANGLE) ? (int)MF_CHECKED :
(int)MF_UNCHECKED ) );
CheckMenuItem( hMenu, IDM_TRIANGLE,
( ( figure.iShape == IDM_TRIANGLE) ? (int)MF_CHECKED :
(int)MF_UNCHECKED ) );
// set a checkmark on one of the two size commands
CheckMenuItem( hMenu, IDM_SMALL,
( ( figure.iSize == IDM_SMALL) ? (int)MF_CHECKED :
(int)MF_UNCHECKED ) );
CheckMenuItem( hMenu, IDM_LARGE,
( ( figure.iSize == IDM_LARGE) ? (int)MF_CHECKED :
(int)MF_UNCHECKED ) );
// set a checkmark on one of the three color commands
CheckMenuItem( hMenu, IDM_RED,
( ( figure.iColor == IDM_RED ) ? (int)MF_CHECKED :
(int)MF_UNCHECKED ) );
CheckMenuItem( hMenu, IDM_GREEN,
( ( figure.iColor == IDM_GREEN ) ? (int)MF_CHECKED :
(int)MF_UNCHECKED ) );
CheckMenuItem( hMenu, IDM_BLUE,
( ( figure.iColor == IDM_BLUE ) ? (int)MF_CHECKED :
(int)MF_UNCHECKED ) );
return;
UNREFERENCED_PARAMETER( hWnd );
}
Creating the Pipe and the Child When you choose Start from the File menu, the program calls its StartProcess procedure to create the pipe, launch the child, and send the child its first command. Some complications arise in arranging for the child to inherit one end of the pipe.
The CreatePipe command must not simply accept the default security attributes because, by default, the new handles cannot be inherited. StartProcess begins by filling out a SECURITY_ATTRIBUTES structure to set the bInheritHandle field to TRUE. If the next command were CreateProcess, the new child would automatically inherit copies of both handlesthe reading end and the writing end of the pipe.
Unfortunately, thats still not quite what we want to happen. The child needs only one handle. Anonymous pipes work only one way, and our child needs only to read from the pipe. It should not inherit hPipeWrite. The next command, DuplicateHandle, modifies the handle by changing its inheritance attribute. Because the parameter for the copied handle is NULL, the command does not actually make a copy; instead, it modifies the original. Now we have a reading handle that can be inherited and a writing handle that cannot.
Generally, the child should not inherit handles it does not need. Most objects stay open as long as any process holds an open handle to them. If a child inherits an assortment of extraneous handles, many objects may be forced to linger in memory even after the parent ends. Furthermore, in this particular case, if the child owned handles for both ends of the pipe, it would not know when the parent destroyed its end of the pipe. From the childs point of view, the pipe would remain open because someone (itself) still had a handle to the other end.
When one process closes its end of a pipe, the other process normally notices because of error results from the read or write commands. Thats why we went to the trouble of making sure the child inherits only one of the two pipe handles.
Inheriting a handle is not enough, however, for the handle to be useful. The child still needs to receive the handle explicitly from the parent. In effect, the child inherits only the right to use the handle, not the handle itself. More accurately, it receives an entry in its object table but no handle to the entry. The parent must still find a way to pass the handle to the new process. The system considers the inherited object table entry to be an open handle even though the child has no direct access to it. The handle that the parent later passes directly to the child connects with the inherited object table entry and does not count as a separate new handle.
Rather than passing a handle on the command line, set it in one of the childs standard I/O device channels. The STARTUPINFO procedure passed to CreateProcess contains three standard I/O handles. Two are the default handles returned by GetStdHandle, but the third is the pipe handle. The child will inherit all three devices. (It will use only the pipe handle, but its best to pass all three handles through STARTUPINFO.)
To review, the StartProcess procedure performs these steps:
The STARTUPINFO structure allows the parent to decide how and where the childs window will appear. Many of the fields, however, apply only to character-based console windows. The Child program uses a graphics window, not a character window. Furthermore, because we have set only one activation flag in the dwFlags field, CreateProcess will ignore most of the values anyway. At a minimum, however, you should initialize the cb field, the lpDesktop field, the dwFlags field, and the three reserved fields.
StartProcess checks for errors after almost every command. If any command fails, StartProcess closes all the handles created to that point. The ShowErrorMsg procedure, which comes at the end of Parent.C, displays a message box describing the last error that occurred.
/*-
START PROCESS
In response to the IDM_START command, launch the child
process and create the pipe for talking to it.
-*/
void StartProcess ( void )
{
char szProcess[MAX_BUFFER]; // name of child process image
SECURITY_ATTRIBUTES sa; // security privileges for handles
STARTUPINFO sui; // info for starting a process
PROCESS_INFORMATION pi; // info returned about a process
int iLen; // return value
BOOL bTest; // return value
HANDLE hPipeRead; // inbound end of pipe (for client)
// load name of process image file from resources
iLen = LoadString( hInst, IDS_PROCESS, szProcess, sizeof(szProcess) );
if( ! iLen )
{
return;
}
// fill out a SECURITY_ATTRIBUTES struct so handles are inherited
sa.nLength = sizeof(SECURITY_ATTRIBUTES); // structure size
sa.lpSecurityDescriptor = NULL; // default descriptor
sa.bInheritHandle = TRUE; // inheritable
// create the pipe
bTest = CreatePipe( &hPipeRead, // reading handle
&hPipeWrite, // writing handle
&sa, // lets handles be inherited
0 ); // default buffer size
if( ! bTest ) // error during pipe creation
{
ShowErrorMsg( );
return;
}
// make an uninheritable duplicate of the outbound (write) handle
bTest = DuplicateHandle( GetCurrentProcess( ),
hPipeWrite, // original handle
GetCurrentProcess( ),
NULL, // dont create new handle
0,
FALSE, // not inheritable
DUPLICATE_SAME_ACCESS );
if( ! bTest ) // duplication failed
{
ShowErrorMsg( );
CloseHandle( hPipeRead );
CloseHandle( hPipeWrite );
return;
}
// fill in the processs startup information
memset( &sui, 0, sizeof(STARTUPINFO) );
sui.cb = sizeof(STARTUPINFO);
sui.dwFlags = STARTF_USESTDHANDLES;
sui.hStdInput = hPipeRead;
sui.hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE );
sui.hStdError = GetStdHandle( STD_ERROR_HANDLE );
// create the drawing process
bTest = CreateProcess( szProcess, // .EXE image
NULL, // command line
NULL, // process security
NULL, // thread security
TRUE, // inherit handlesyes
0, // creation flags
NULL, // environment block
NULL, // current directory
&sui, // startup info
&pi ); // process info (returned)
// did we succeed in launching the process?
if( ! bTest )
{
ShowErrorMsg( ); // creation failed
CloseHandle( hPipeWrite );
}
else // creation succeeded
{
hProcess = pi.hProcess; // save new process handle
CloseHandle( pi.hThread ); // discard new thread handle
figure.iShape = IDM_RECTANGLE; // reset to default shape
SendCommand( ); // tell child what to draw
}
CloseHandle( hPipeRead ); // discard receiving end of pipe
return;
}
If the CreateProcess command succeeds, StartProcess performs several final actions. First, it looks at the two handles returned in the PROCESS_INFORMATION structure. It moves the new processs handle to a global variable. The pi variable also holds a handle to the primary thread of the new child process. Having no use for that handle, the program closes it immediately. Then it closes the handle to the childs end of the pipe and calls SendCommand to tell the child what to draw first.
Resetting the figure variable before SendCommand is very important. The parent process can open and close a child many times in one session. To close the child, the parent puts IDM_TERMINATE in the figure variable and writes that to the pipe. The line that resets figure.iShape to IDM_RECTANGLE ensures that the child will not receive a leftover IDM_TERMINATE command as its first message.
Writing to the Pipe The Parent program sends the Child program a message immediately after launching the child when you choose a new shape attribute and when you choose Terminate from the Process menu. At each point, the program calls SendCommand to put information in the pipe.
/*
SEND COMMAND
Tell the child program what to draw. Write the current
contents of the global FIGURE variable into the pipe.
Return
TRUE indicates the write operation succeeded. FALSE means
an error occurred and you have lost contact with the child.
*/
BOOL SendCommand()
{
BOOL bTest; // return value
DWORD dwWritten; // number of bytes written to pipe
// pass the choices to the child through the pipe
bTest = WriteFile( hPipeWrite, // anonymous pipe (outbound handle)
&figure, // buffer to write
sizeof(FIGURE), // size of buffer
&dwWritten, // bytes written
NULL ); // overlapping i/o structure
if( ! bTest ) // did writing succeed?
{
// If the write operation failed because the user has
// already closed the child program, then tell the user
// the connection was broken. If some less predictable
// error caused the failure, call ShowErrorMsg as usual
// to display the systems error message.
DWORD dwResult = GetLastError();
if( ( dwResult == ERROR_BROKEN_PIPE ) || // pipe has been ended
( dwResult == ERROR_NO_DAT ) ) // pipe close in progress
{
// presumably the user closed the child
MessageBox( hwndMain, Connection with child already broken.,
Parent Message, MB_ICONEXCLAMATION | MB_OK );
}
else // an unpredictable error occurred
ShowErrorMsg();
}
// If a write error occurred, or if we just sent an IDM_TERMINATE
// command to make the child quit, then in either case we break
// off communication with the child process.
if( ( ! bTest ) || ( figure.iShape == IDM_TERMINATE ) )
{
CloseHandle( hProcess ); // forget about the child
hProcess = NULL;
CloseHandle( hPipeWrite ); // destroy the pipe
}
return( bTest );
}
The WriteFile command may fail for any of several reasons. One likely problem arises if the user closes the Child program from the childs menu. The parent does not know the child is gone until it tries to write to the pipe and receives an error. In that case, the GetLastError function, which returns a number identifying what error last occurred in a given thread, indicates either ERROR_BROKEN_PIPE or ERROR_NO_DATA.
Instead of handling these results like any other error, SendCommand raises a message box explaining that the connection has been broken. If the pipe fails for any reason at all, however, the parent makes no effort to reestablish contact. It closes the pipe handle and the child process handle. The program also resets the process handle to NULL, which CloseHandle does not do. The Parent_OnInitMenu message handler relies on the value of hProcess to determine whether the child still exists.
A separate ShowErrorMsg procedure uses the FormatMessage function to present error messages.
/*
SHOW ERROR MESSAGE
*/
void ShowErrorMsg ( void )
{
LPVOID lpvMessage; // temporary message buffer
// retrieve a message from the system message table
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, GetLastError(),
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
(LPTSTR) &lpvMessage, 0, NULL );
// display the message in a message box
MessageBox( hwndMain, lpvMessage, Parent Message,
MB_ICONEXCLAMATION | MB_OK );
// release the buffer FormatMessage allocated
LocalFree( lpvMessage );
return;
}
ShowErrorMsg is built around the FormatMessage command, which chooses a message from the systems internal message table to describe the most recent error. (The most recent error value is maintained separately for each thread.) Given the flags weve set in its first parameter, FormatMessage dynamically allocates a message buffer and puts the address in the lpvMessage variable. Note that FormatMessage wants to receive the address of the buffer pointer, not the pointer itself.
The CreateProcess command in the parents StartProcess procedure launches a program called Child. The parent stores the string child.exe in its table of string resources.
Inheriting a Pipe Handle and Creating a Thread The AnonPipe version of Child dedicates a secondary thread to the task of waiting for data to arrive through the pipe. The primary thread processes system messages for the programs window. When the secondary thread receives a new command from the parent, it updates the global variables iShape, iSize, and iColor, and then invalidates the window. The primary thread receives a WM_PAINT message and redraws the display using the new shape values. The secondary thread runs in a loop that ends when it reads an IDM_TERMINATE command from the pipe.
The first part of the Child.C listing includes WinMain, the initialization procedures, and the message handlers. The child performs its most important initialization tasks in response to the WM_CREATE message.
Child_OnCreate recovers the pipe handle inherited from the parent and creates a new thread to read from the pipe. If either action fails, the procedure returns FALSE and the process ends.
/*
CHILD_ONCREATE
On startup open the pipe and start the thread that will
read from it.
*/
BOOL Child_OnCreate( HWND hWnd, LPCREATESTRUCT lpCreateStruct )
{
// open the pipe for reading
hPipeRead = GetStdHandle( STD_INPUT_HANDLE );
if( hPipeRead == INVALID_HANDLE_VALUE )
{
ShowErrorMsg();
return( FALSE );
}
// Create the thread that will read from the pipe. It is created
// suspended so its priority can be lowered before it starts.
hThread = CreateThread( NULL, // security attributes
0, // initial stack size
(LPTHREAD_START_ROUTINE) PipeThread,
NULL, // argument
CREATE_SUSPENDED, // creation flag
&dwThreadID ); // new threads ID
if( ! hThread )
{
ShowErrorMsg();
return( FALSE );
}
// lower the threads priority and let it run
SetThreadPriority( hThread, THREAD_PRIORITY_BELOW_NORMAL );
ResumeThread( hThread );
return( TRUE );
UNREFERENCED_PARAMETER(hWnd);
UNREFERENCED_PARAMETER(lpCreateStruct);
}
To retrieve the pipe handle, the child calls GetStdHandle. This command finds the handle that the parent told CreateProcess to deposit in the childs stdin device slot. The child creates its secondary pipe-reading thread in a suspended state to adjust the threads priority. Because the primary thread responds to user input, we set the secondary thread to a lower priority. This difference ensures that the child will respond quickly to keyboard and menu input.
The paint procedure (not shown here) is long but straightforward. It simply reads the current values of iShape, iSize, and iColor; creates the pens and brushes it needs; and draws an ellipse, a rectangle, or a triangle.
Reading from the Pipe Child_OnCreate designates PipeThread as the main procedure for the secondary thread. The new thread immediately enters a while loop that ends when the global bTerminate flag becomes TRUE. The flag changes when you choose Exit from the childs menu, when the parent sends an IDM_TERMINATE command, or if the child encounters an error reading from the pipe. When the while loop finally does end, the thread posts a WM_DESTROY message to the program window. The secondary thread exits, the primary thread receives the destroy command, and the program ends.
/*
PIPE THREAD
The WM_CREATE handler starts a thread with this procedure
to manage the pipe connection. This thread waits for
messages to arrive through the pipe and acts on them.
-*/
LONG PipeThread( LPVOID lpThreadData )
{
while( ! bTerminate ) // read from pipe until terminate flag = true
DoRead();
// when bTerminate is TRUE, time to end program
FORWARD_WM_DESTROY( hwndMain, PostMessage );
return( 0L ); // implicit ExitThread()
UNREFERENCED_PARAMETER(lpThreadData);
}
The DoRead procedure is responsible for reading from the pipe and for making three decisions. The first is to determine if the read is successful or, if not, to report an error. Second, assuming a successful read, the IDM_TERMINATE command is checked to determine if the thread should terminate. And, third, if the read is successful and it is not a terminate message, the shape, color, and size variables are read from the piped message and the child window is updated so a new shape will be drawn.
/*
DO READ
Read from the pipe and set the figure to be drawn.
*/
void DoRead( void )
{
FIGURE figure;
DWORD dwRead;
BOOL bTest;
// read from the pipe
bTest = ReadFile( hPipeRead, // place to read from
&figure, // buffer to store input
sizeof(figure), // bytes to read
&dwRead, // bytes read
NULL );
if( bTest )
{ // the read command succeeded
if( figure.iShape == IDM_TERMINATE ) // is new command Terminate?
bTerminate = TRUE; // set flag to end this thread
else
{ // thread continues; draw new shape
// copy the new shape attributes to global variables
iShape = figure.iShape;
iColor = figure.iColor;
iSize = figure.iSize;
// force the parent window to repaint itself
InvalidateRect( hwndMain, NULL, TRUE );
UpdateWindow( hwndMain );
}
}
else // the read command failed
{
ShowErrorMsg( ); // tell user what happened
bTerminate = TRUE; // let the child end
}
return;
}
The while loop in the secondary thread does one thing: It calls DoRead over and over. The DoRead procedure performs one ReadFile command, interprets the message, and ends. Each call to DoRead retrieves one more message. Because all anonymous pipes use the waiting mode, the thread blocks on each call to ReadFile until data arrives. ReadFile may return immediately if the pipe is full or if an error occurs. For example, it will return immediately if the Parent program has already exited and the pipe handle has become invalid.
If the read command succeeds, the child must determine whether it has received a command to terminate or to draw a new shape. If the command fails, the child notifies the user by displaying the systems error message. The child also assumes the connection has been broken and exits.
Although this pipe, like all anonymous pipes, writes and reads in byte mode, the Child program still manages to always retrieve one message at a time, even if several messages are waiting. Because the parent always writes exactly sizeof(FIGURE) bytes into the pipe, the child knows where one message ends and the next begins.
The Child programs About_DlgProc and ShowErrorMsg procedures duplicate the corresponding procedures in the Parent program almost exactly.