The AnonPipe Demo: One-Way Communication

 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 parent’s menu, and the resulting shape appears in the child’s 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 Developer’s 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 problem—if you are not already ahead of me—is 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

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 parent’s 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 client’s window. Here
      // we initialize it to the program’s startup defaults.
   figure.iShape = IDM_RECTANGLE;      // draw a rectangle
   figure.iSize  = IDM_SMALL;          // don’t 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 application’s 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 user’s 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 handles—the reading end and the writing end of the pipe.

Unfortunately, that’s 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 child’s 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. That’s 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 child’s 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 it’s best to pass all three handles through STARTUPINFO.)

To review, the StartProcess procedure performs these steps:

  1. Loads the string that names the child program’s executable file (Child.EXE).

  2. Creates the pipe with inheritable handles.

  3. Modifies the write-only handle with DuplicateHandle so it cannot be inherited.

  4. Puts the read-only handle in a STARTUPINFO variable.

  5. Creates the child process, which both inherits the read-only handle and receives a copy of the handle as its own stdin device. (The child inherits no other handles from this parent.)

  6. Closes the parent’s read-only handle to the pipe. The child has its own copy now, and the parent doesn’t need it.

The STARTUPINFO structure allows the parent to decide how and where the child’s 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,             // don’t create new handle
                            0,
                            FALSE,            // not inheritable
                            DUPLICATE_SAME_ACCESS );
   if( ! bTest )                              // duplication failed
   {
      ShowErrorMsg( );
      CloseHandle( hPipeRead );
      CloseHandle( hPipeWrite );
      return;
   }
      // fill in the process’s 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 handles—yes
                          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 process’s 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 child’s 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 system’s 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 child’s 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 system’s internal message table to describe the most recent error. (The most recent error value is maintained separately for each thread.) Given the flags we’ve 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 Child

The CreateProcess command in the parent’s 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 program’s 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 thread’s ID
   if( ! hThread )
   {
      ShowErrorMsg();
      return( FALSE );
   }
      // lower the thread’s 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 child’s stdin device slot. The child creates its secondary pipe-reading thread in a suspended state to adjust the thread’s 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 child’s 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 system’s 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 program’s About_DlgProc and ShowErrorMsg procedures duplicate the corresponding procedures in the Parent program almost exactly.

© 1998 SYBEX Inc. All rights reserved.