The NamedPipe Demo: Multiple Communication Channels

 An anonymous pipe serves the needs of the AnonPipe demo’s Parent and Child programs perfectly well. They communicate in only one direction, use only one instance of the pipe at a time, and both run on the same machine. The NamedPipe version, however, allows the parent to create any number of children and communicate with several of them at once. Rather than creating a new anonymous pipe for each client, this version creates two instances of a single named pipe.

WARNING

Although the NamedPipe version of the program works fine under Windows NT, it will not compile correctly under Windows 98.

If you launch many child processes, only the first two will connect with the parent. The others will block, waiting for one of the existing connections to break.

Choosing Terminate from the parent’s menu causes all the currently connected children to quit. When the link with one child breaks, that instance of the pipe becomes available for one of the waiting children. In Figure 15.3, the parent process has launched three children. Two are connected and have drawn the currently selected shape, while the third is waiting for an available pipe instance.

NOTE

The NamedPipe demo is included on the CD that accompanies this book, in the Chapter 15 folder.

Figure 15.3: Parent process with three children, only two of which are connected to instances of the named pipe

The Parent Process

To use a named pipe, both processes must agree on a name string for the pipe they will share. The name string belongs in the string table of both the parent and the child. We’ve set the string in a shared resource file, Global.STR, and modified both resource scripts to include it. The shared header file, Global.H, adds a new constant to identify the common string resource. Notice that the name string in Global.STR contains double the expected number of backslashes:

/*—————————————————————————————————————————————————————————————————————
   GLOBAL.STR
      Contains a string that should be included in the string
      tables of both the parent and child halves of the
      PROCESS demo program.
—————————————————————————————————————————————————————————————————————*/
IDS_PIPE,  “\\\\.\\pipe\\procdemo”     // name of pipe object

The resource compiler uses the backslash character to signal the beginning of an ASCII code sequence. Each pair of backslashes inserts a single literal backslash in the resource string.

The new Parent.H header defines the constant NUM_PIPE_INSTANCES, giving it the value of 2. To have the parent create more instances of its pipe and connect with more children simultaneously, modify the definition.

TIP

Figure 15.3 shows three child instances where only two have connected—due to the specified NUM_PIPE_INSTANCES limit. When the parent is instructed to close the child processes, the unconnected child will remain open and will assume one of the freed connections.

Creating the Named Pipe The anonymous pipe parent destroys its pipe each time the child process terminates. If you launch a new child, the parent creates a new pipe. A named pipe, however, often lives through several connections with different clients. The named pipe parent creates its pipe instances only once, during initialization. The parent calls its MakePipeInstance procedure twice. Each successive call produces a handle to the program’s pipe object and a new thread to support the new instance.

Parent_OnCreate also produces two other important objects, both events. Each event object broadcasts a signal to as many threads as happen to be listening.

/*————————————————————————————————————————————————————————————————————————
   PARENT_ONCREATE
      Create all the pipe instances and the two event objects used
      to synchronize the program’s threads
———————————————————————————————————————————————————————————————————————————*/
BOOL Parent_OnCreate( HWND hWnd, LPCREATESTRUCT lpcs )
{
   int i;
   int iNumInstances = 0;              // counts instances created
      // Create all the instances of the named pipe. The
      // MakePipeInstance command also starts up a new
      // thread to service each pipe instance.
   for( i = 1; i <= NUM_PIPE_INSTANCES; i++ )
      if( MakePipeInstance() )        // make one instance
         iNumInstances++;             // if successful, increment counter
   if( iNumInstances != NUM_PIPE_INSTANCES )  // did we make all of them?
   {
      char szBuffer[128];
      wsprintf( szBuffer, “Created only %i instances\n\r”, iNumInstances );
      MessageBox( hwndMain, szBuffer, “Parent Message”,
                  MB_ICONEXCLAMATION | MB_OK );
      return( FALSE );                // creation failed
   }
      // Create the event object used for signaling the pipe
      // instance threads when the user makes a command.
   hCmdEvent = CreateEvent( NULL,     // default security attributes
                            TRUE,     // manual reset event
                            FALSE,    // initially not signaled
                            NULL );   // no name
   if( hCmdEvent == NULL )
   {
      ShowErrorMsg();                 // event creation failed
      return( FALSE );
   }
      // Create the event that coordinates the pipe threads when
      // the program terminates all linked children. The threads
      // block on this event until all the clients have received
      // the IDM_TERMINATE message.
   hNotTerminatingEvent = CreateEvent( NULL,     // default security attributes
                                       TRUE,     // manual reset event
                                       TRUE,     // initially signaled
                                       NULL );   // no name
   if( hNotTerminatingEvent == NULL )
   {
      ShowErrorMsg( );                // event creation failed
      return( FALSE );
   }
   return( TRUE );
   UNREFERENCED_PARAMETER(hWnd);
   UNREFERENCED_PARAMETER(lpcs);
}

The Parent program uses one event to notify its pipe threads whenever the user makes a new choice from the menu. In response, the pipe threads send the new command to their clients. This is a manual reset event, so all listening pipes will unblock when the signal arrives. (Automatic reset events unblock only one thread on each signal.)

The other event coordinates the threads while they are sending termination commands to multiple clients. It, too, is a manual reset event. This one, however, begins life already in its signaled state. You’ll see why in the code for the pipe instance threads.

Because no other processes have any reason to use either event object, the events do not need names.

Creating a New Named Pipe Instance To create a new instance of a named pipe, use the MakePipeInstance procedure. It begins by loading the resource string that names the pipe object. The subsequent call to CreateNamedPipe sets the pipe’s attributes.

/*————————————————————————————————————————————————————————————————————————
   MAKE PIPE INSTANCE
      Create a new instance of the named pipe.
   Return   TRUE if the procedure creates a new instance;
            FALSE if an error prevents creation.
————————————————————————————————————————————————————————————————————————*/
BOOL MakePipeInstance ( void )
{
   char   szPipe[MAX_BUFFER];      // name of pipe
   int    iLen;                    // return value
   HANDLE hPipe;                   // handle to new pipe
   HANDLE hThread;                 // handle to new thread
   DWORD  dwThreadID;              // ID of new thread
      // get name to use for sharing pipe
   if( ! LoadString( hInst, IDS_PIPE, szPipe, sizeof(szPipe) ) )
      return( FALSE );
      // Create a new instance of the named pipe. This command will
      // fail if two instances already exist
   hPipe = CreateNamedPipe( szPipe,               // name
                            PIPE_ACCESS_OUTBOUND, // open mode
                            PIPE_TYPE_BYTE | PIPE_READMODE_BYTE |
                            PIPE_WAIT,
                            NUM_PIPE_INSTANCES,   // max instances
                            0,                    // out buffer size
                            0,                    // in buffer size
                            0,                    // time-out value
                            NULL );               // security attributes
   if (hPipe == INVALID_HANDLE_VALUE)
   {
      ShowErrorMsg( );                            // creation failed
      return( FALSE );
   }
   hThread = CreateThread( NULL,           // security attributes
                           0,              // initial stack size
  (LPTHREAD_START_ROUTINE) PipeInstanceThread,
                  (LPVOID) hPipe,          // argument for thread proc
                           CREATE_SUSPENDED,     // creation flag
                           &dwThreadID );        // new thread’s ID
   if( ! hThread )
   {
      ShowErrorMsg( );
      CloseHandle( hPipe );
      return( FALSE );                    // thread creation failed
   }
      // lower the thread’s priority and let it run
   SetThreadPriority( hThread, THREAD_PRIORITY_BELOW_NORMAL );
   ResumeThread( hThread );
      // let go of the handle, for which we have no further use
   CloseHandle( hThread );
   return( TRUE );
}

Because the parent and child send information in only one direction, the program uses a one-way outbound pipe. For clarity, specify all three mode flags even though the pipe’s particular characteristics—byte mode and wait mode—are the default values. We do not need the message mode because the parent’s messages are always the same length. NUM_PIPE_INSTANCES prevents CreateNamedPipe from producing more than two handles to this pipe.

The first call to CreateNamedPipe sets the pipe’s maximum number of instances, and subsequent creation commands will fail if they specify a different number. The zero values for the buffer sizes instruct the system to allocate message space dynamically as needed.

For each pipe handle, MakePipeInstance also creates a new thread. The pipe instance thread waits for the user to choose commands from the parent menu and writes the new command into its pipe. We might equally well have stored the pipe handles in an array and created a single thread to write to all the instances on each new command. Again, as with the anonymous pipe child, the program sets secondary threads to a lower priority, reserving normal priority for only the primary thread—the one thread that responds directly to the user.

This time, pass a parameter to the thread’s starting function. A thread function always receives a 32-bit parameter when it starts up, but until now the programs have not used it. Each instance thread, however, requires a different pipe handle, so hPipe becomes the fourth parameter of CreateThread.

Launching the Child When you choose the Start command from the parent’s File menu, the program calls StartProcess. In this version, the Start command is always enabled, permitting you to launch any number of children.

As an exercise in using the command line, the parent passes to each child an identifying number. The first child is 1, the second is 2, and so on. To pass arguments to a child on its command line, ignore the first parameter of CreateProcess and pass the entire command line, including the program name, in the second parameter. The command-line parameter string looks like this:

child.exe 1

Use the first parameter, lpszImage, when you have no arguments to pass or do not want the system to search for the child’s .EXE file along the system PATH.

Because children can acquire their own pipe handles with CreateFile, there is no need to arrange for inheritance. Even if the Parent program had created inheritable handles, the children would not inherit any of them because we pass FALSE as the fifth parameter to CreateProcess.

/*————————————————————————————————————————————————————————————————————————
   START PROCESS
      In response to the IDM_START command, create a new
      child process. The user may create any number of children.
————————————————————————————————————————————————————————————————————————*/
void StartProcess()
{
   STARTUPINFO         sui;     // info for starting a process
   PROCESS_INFORMATION pi;      // info returned about a process
   static int iChildNum = 1;    // counts child processes
   char szProcess[MAX_BUFFER];  // name of child process image
   char szCmdLine[MAX_BUFFER];  // child’s command line
   BOOL bTest;                  // return value
      // load name of process image file from resources
   if( ! LoadString( hInst, IDS_PROCESS, szProcess, sizeof(szProcess) ) )
      return;                   // loading string failed
      // fill in the process’s startup information
   sui.cb               = sizeof(STARTUPINFO);
   sui.lpReserved       = NULL;      // must be NULL
   sui.lpDesktop        = NULL;      // starting desktop
   sui.lpTitle          = NULL;      // title for new console window
   sui.dwX              = 0;         // window starting offsets
   sui.dwY              = 0;
   sui.dwXSize          = 0;         // window starting size
   sui.dwYSize          = 0;
   sui.dwXCountChars    = 0;         // console screen buffer size
   sui.dwYCountChars    = 0;
   sui.dwFillAttribute  = 0;         // console text colors
   sui.dwFlags          = 0;         // flags to activate startup fields
   sui.wShowWindow      = 0;         // iCmdShow parameter
   sui.cbReserved2      = 0;
   sui.lpReserved2      = NULL;
      // prepare child’s command-line argument, a window caption string
   wsprintf( szCmdLine, “%s %i”, (LPSTR)szProcess, iChildNum++ );
      // create the drawing process
   bTest = CreateProcess( NULL,            // .EXE image
                          szCmdLine,       // command line
                          NULL,            // process security
                          NULL,            // thread security
                          FALSE,           // inherit handles
                          0,               // creation flags
                          NULL,            // environment block
                          NULL,            // current directory
                          &sui,            // startup info
                          &pi );           // process info (returned)
   if( ! bTest )
   {
      ShowErrorMsg();                      // creation failed
      return;
   }
   WaitForInputIdle( pi.hProcess, 5000 );  // wait for child to start up
   CloseHandle( pi.hProcess );             // we don’t need the handles
   CloseHandle( pi.hThread );
   return;
}

Synchronizing the Threads Whenever you change the shape options by choosing a new figure, size, or color, the program must write new commands in all its pipes. The primary thread receives and processes your command. It needs a way to make all the pipe instance threads transmit the new information. One of the event objects created during initialization serves this purpose.

When you pick a new option, the procedures that store the command also pulse the event. The PulseEvent command combines the SetEvent and ResetEvent commands into one operation. The event remains signaled just long enough to unblock all waiting threads and then immediately returns to its unsignaled state.

The ChangeShape procedure incorporates one other modification. When it receives the IDM_TERMINATE command, it saves the old shape value in a global variable, iPrevShape. After all the connected children quit, the parent restores the iPrevShape value to the figure.iShape field. If children are waiting, they will connect immediately to the newly released pipes; if figure.iShape still held IDM_TERMINATE, the children’s first command would shut them down. The iPrevShape variable allows newly connected children to draw immediately whatever shape was last selected.

/*—————————————————————————————————————————————————————————————————————
   CHANGE SHAPE
      Record a shape command from the user. If the user has chosen
      a new shape, send the updated FIGURE structure to the child.
—————————————————————————————————————————————————————————————————————*/
void ChangeShape ( int iCmd )
{
   if( iCmd != figure.iShape )       // new shape?
   {
      // After sending a terminate command, you need to
      // restore the last shape drawn so that newly
      // connected clients can still draw whatever the
      // user last chose.
      if( iCmd == IDM_TERMINATE )
         iPrevShape = figure.iShape; // save old shape command
      figure.iShape = iCmd;          // record new shape command
      PulseEvent( hCmdEvent );       // tell threads shape has changed
   }
   return;
}

The ChangeSize procedure doesn’t have much to do except for recording the new size and then, like the ChangeShape procedure, calling the PulseEvent function to tell the thread that a change has occurred.

/*—————————————————————————————————————————————————————————————————————
   CHANGE SIZE
      Record a size command from the user. If the user has chosen
      a new size, send the updated FIGURE structure to the child.
—————————————————————————————————————————————————————————————————————*/
void ChangeSize( int iCmd )
{
   if( iCmd != figure.iSize )        // new size?
   {
      figure.iSize = iCmd;           // record it
      PulseEvent( hCmdEvent );       // tell threads shape has changed
   }
   return;
}

Again, the ChangeColor procedure simply records the new color, and then calls the PulseEvent function to tell the thread that a change has occurred.

/*—————————————————————————————————————————————————————————————————————
   CHANGE COLOR
      Record a color command from the user. If the user has chosen
      a new color, send the updated FIGURE structure to the child.
—————————————————————————————————————————————————————————————————————*/
void ChangeColor ( int iCmd )
{
   if( iCmd != figure.iColor )       // new color?
   {
      figure.iColor = iCmd;          // record it
      PulseEvent( hCmdEvent );       // tell threads shape has changed
   }
   return;
}

Connecting with Clients The threads that run each pipe instance begin life at the PipeInstanceThread procedure. Each new thread enters an endless loop waiting for clients to connect with its instance of the pipe. When a client does connect, a smaller nested loop begins.

While the connection lasts, the thread waits for command signals from the event object. Each time the event pulses, the thread unblocks, copies the current contents of the global figure variable into its pipe, and resumes its wait for a new command.

If the write operation fails for any reason, the thread assumes its client process has terminated. The thread calls DisconnectNamedPipe, returns to the top of its outer loop, and issues ConnectNamedPipe to wait for a new client. The loop also maintains a connection count in the global variable iNumConnections. Each time a thread succeeds in connecting, it increments the counter. When the connection breaks, it decrements the counter. The Parent_OnInitMenu procedure reads the counter to decide which menu options should be enabled. If the parent has no listening clients, all the shape option commands are disabled.

The outer loop of PipeInstanceThread begins with a while (TRUE) command, so the loop can never break. The pipe threads stop running when the primary thread reaches the end of WinMain and the system calls ExitProcess. At the customary W4 warning level, using a constant for a conditional expression causes the compiler to complain. The #pragma commands surrounding the procedure suppress the warning.

// Tell the compiler not to complain about the “while (TRUE)” loop
#pragma warning (disable :4127)

Adding to the complexity of this procedure is the task of coordinating the threads when they all disconnect their clients in response to a Terminate command from the user. Several potential problems arise along the way. First, the parent should not write its IDM_TERMINATE command to the pipe and then disconnect immediately because DisconnectNamedPipe destroys any data still lingering in the pipe’s buffer. The command could be lost before the child had a chance to read it. When passed a pipe handle, FlushFileBuffers blocks until the receiving program clears the pipe by reading all its contents. Only a program with write access to its pipe may call FlushFileBuffers. The command fails when passed a read-only handle.

As each thread disconnects from its terminated client, it returns to the top of the loop and waits for a new connection. As soon as it connects, it sends the client an initial message to make it draw something right away. But if other threads are still terminating their clients, the global figure variable still contains the IDM_TERMINATE command. The thread will terminate its newly connected client by mistake. We need a way to prevent any thread from sending that initial message to a new client until after all the old clients have been disconnected. The hNotTerminatingEvent object solves the problem.

Near the top of the outer loop you’ll find a WaitForSingleObject command that every thread passes before writing its first message to a new client. Most of the time, the event remains signaled and all threads pass by quickly. As soon as one thread sends a termination command, however, it calls ResetEvent to turn off the signal, indicating that a termination sequence has started. Now when any thread finds a new client, it will block before sending the first message. The last thread to terminate its client resets the figure.iShape command to iPrevShape, the value it last held before the termination began. The last thread also calls SetEvent to restore the event signal and unblock the other waiting threads. A thread knows when it is the last to terminate its client because the iNumConnections counter reaches 0.

LONG PipeInstanceThread ( HANDLE hPipe )
{
   BOOL bConnected;    // true when a client connects to the pipe
      // This loop runs endlessly. When a client disappears, the
      // loop simply waits for a new client to connect. This thread is
      // terminated automatically when the program’s primary thread exits.
   while( TRUE )
   {
         // wait for a connection with some client
      ConnectNamedPipe( hPipe, NULL );

If other threads are terminating their clients, then figure.iShape still holds the IDM_TERMINATE command. The thread blocks here until the last client is terminated. The last terminating thread resets figure.iShape to its previous value.

      WaitForSingleObject( hNotTerminatingEvent, INFINITE );
         // now the connection is made and a command message is ready
      iNumConnections++;                  // update global variable
      SendCommand( hPipe );               // give client its first command
         // send another message each time the Command event signals
      bConnected = TRUE;
      while( bConnected )
      {
         WaitForSingleObject( hCmdEvent, INFINITE ); // wait for signal
         if( ! SendCommand( hPipe ) )      // send new shape command
            bConnected = FALSE;
            // The connection failed – probably we just sent IDM_TERMINATE or
            // the user exited from the client. Show no error message.
      }
      FlushFileBuffers( hPipe );    // wait for child to read message
      DisconnectNamedPipe( hPipe ); // break connection
      iNumConnections—;            // update global variable

The following if condition coordinates threads when they are all terminating their clients. When a thread discovers it has just sent the IDM_TERMINATE command, it sets the hNotTerminatingEvent object to the nonsignaled state. Other threads will block until the last thread to disconnect restores the signal. The last thread also replaces the IDM_TERMINATE command with IDM_RECTANGLE so all the threads will have a useful command to send in the first message to their new clients.

      if( figure.iShape == IDM_TERMINATE ) // did we just terminate?
      {                                    // have all connections
         if( iNumConnections > 0 )         // been terminated?
         {                                 // NO; block other threads
            // while terminating proceeds
            ResetEvent( hNotTerminatingEvent );
         }
         else                              // YES
         {
            figure.iShape = iPrevShape;    // restore previous command
            SetEvent( hNotTerminatingEvent );   // unblock threads
         }
      }
   }
   return( 0L );
}

Last, the conditional expression warning is reenabled.

// allow the “conditional expression constant” warning again
#pragma warning (default :4127)

Writing to the Pipe Within the PipeInstanceThread loops, the program repeatedly calls SendCommand to write the current values from figure into the pipe. If the connection with the client breaks, the procedure returns FALSE. The connection may break in either of two ways, and each has different consequences for the program’s behavior.

The program disconnects most gracefully when the user chooses Terminate and the parent breaks the links itself. In that case, SendCommand returns FALSE immediately after sending the message, and the program seeks new replacement clients immediately. But if a client closes its handle to the pipe’s other end, the server does not know. If the user chooses Exit from the menu of a connected child process, the parent discovers the break only when it later tries to send a new message and WriteFile returns an error value. Furthermore, the broken pipe remains technically connected until the server calls DisconnectNamedPipe. As a consequence, if you end one of the connected child programs while a third child is blocked on WaitNamedPipe, the waiting child remains blocked until you issue another command.

Using a two-way duplex pipe would smooth this transition because the client could send the server a termination message before it exits and the server could disconnect immediately. To accommodate that arrangement, the server would probably dedicate two threads to each pipe instance: one to send and one to receive, or perhaps one thread to do all the writing for all instances and another to do the reading.

/*—————————————————————————————————————————————————————————————————————
   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 we have lost contact with the child.
—————————————————————————————————————————————————————————————————————*/
BOOL SendCommand ( HANDLE hPipe )
{
   BOOL bTest;                     // return value
   DWORD dwWritten;                // number of bytes written to pipe
   // pass the choices to the child through the pipe
   bTest = WriteFile( hPipe,       // named 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 we don’t need to do anything special about the error. If, however, 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_DATA ) )      // pipe close in progress
      {
         ShowErrorMsg();                       // unpredictable error
      }
   }

SendCommand returns FALSE on errors to indicate that the connection has failed; SendCommand also returns FALSE after it tells a child to quit because that also makes a connection fail.

   return( ( bTest ) && ( figure.iShape != IDM_TERMINATE ) );
}

The Child Process

The child process requires fewer changes. One change is visible in Figure 15.3 (shown earlier). Because the parent now creates multiple children, we distinguish each with a different number in its window caption. The parent passes each child its own number through the command-line parameter of CreateProcess. The child’s window caption also states whether the child is connected to a pipe or waiting for a connection.

BOOL CreateMainWindow ( void )
{
   char szAppName[MAX_BUFFER];
   char szBuffer[MAX_BUFFER];
   char *pToken;
      // load the relevant strings
   LoadString( hInst, IDS_APPNAME, szAppName,  sizeof(szAppName) );

You use the command-line string from the parent to create the window’s caption. The basic caption has the form, Child 1 %s. The identifying number comes from the parent through the command line. We use wsprintf to insert the phrase waiting or connected into the title as appropriate.

   strcpy( szTitle, “Child ” );        // begin with “Child ”
   strtok( GetCommandLine(), “ ” );    // move past first word
   pToken = strtok( NULL, “ ” );       // get first argument
   if( pToken )                        // is there one?
   {
      strcat( szTitle, pToken );       // append it
   }
   strcat( szTitle, “ %s” );           // append a wsprintf format mark

During initialization, each child retrieves its assigned number with GetCommandLine. This command returns a pointer to a string containing all the command-line arguments separated by spaces. The first item on the command line should be the name of the program (Child.EXE), so calling strtok twice extracts the child’s sequence number, the second item on the command line. The program constructs a base string of the form “Child 1 %s” and calls wsprintf to replace the %s marker with a string describing the program’s current status, which is initially “waiting” but may change to “connected.”

      // The global szTitle now contains the base caption.
      // Insert a current status marker in it.
   wsprintf( szBuffer, szTitle, (LPSTR)“ (waiting)” );
      // create the parent window
   hwndMain = CreateWindow( szAppName, szBuffer,
                            WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            NULL, NULL, hInst, NULL );
      // return FALSE for an error
   return( hwndMain != NULL );
}

Creating a Thread and Connecting to the Pipe Because CreateMainWindow has already set the window caption, the new Child_OnCreate procedure no longer loads a caption from the resource string table. Because the child has not inherited a pipe handle, this version also omits the original call to GetStdHandle. Instead, the new thread function, PipeThread, now coordinates all the pipe actions.

PipeThread first loads the pipe name string both programs share in the Global.STR file, and then it passes the name to CreateFile. The system searches its object name tree and finds the pipe with this name. If an instance of the pipe is available, CreateFile returns a handle to it. Because the original pipe was created with the PIPE_ACCESS_OUTBOUND flag, the client must request GENERIC_READ access rights.

LONG PipeThread ( LPVOID lpThreadData )
{
   char   szBuffer[MAX_BUFFER];   // used for pipe name and window caption
   BOOL   bConnected;             // TRUE when we have a pipe handle
   HANDLE hPipeRead;              // receiving handle to 1-way pipe
      // load the string that contains the name of the named pipe
   if( ! LoadString( hInst, IDS_PIPE, szBuffer, sizeof(szBuffer) ) )
      return( FALSE );
      // This while loop continues until an instance of the named
      // pipe becomes available. bConnected is a global variable,
      // and while it is FALSE the primary thread paints “Waiting...”
      // in the window’s client area.
      // If an unpredictable error occurs, the loop sets the bTerminate
      // flag, this procedure ends quickly, and it kills the program
      // on its way out.
   bConnected = FALSE;

PipeThread embeds the CreateFile command in a while loop that runs until CreateFile returns a valid handle. The loop begins with a WaitNamedPipe command, causing the thread to block waiting for an available instance. WaitNamedPipe does not, however, initiate the connection; CreateFile does that.

   while( ! bConnected && ! bTerminate )
   {
         // wait for a pipe instance to become available
      WaitNamedPipe( szBuffer, NMPWAIT_WAIT_FOREVER );

Between the execution of the WaitNamedPipe command and the execution of CreateFile, the system can schedule another thread that grabs the pipe for itself. If that happens, CreateFile will fail even though WaitNamedPipe returned TRUE. If CreateFile fails during PipeThread, the while loop notices the error and tries again. If CreateFile produces an error message indicating any problem other than busy pipes, the loop sets the bTerminate flag, and the process ends a few lines later.

         // open the named pipe for reading
      hPipeRead = CreateFile( szBuffer,       // name of pipe
                              GENERIC_READ,   // access mode
                              0,              // share mode
                              NULL,           // security descriptor
                              OPEN_EXISTING,  // don’t create new object
                              FILE_ATTRIBUTE_NORMAL, // file attributes
                              NULL );                // file from which
                                                     // to copy attributes
         // check that the pipe’s handle is valid
      if( hPipeRead == INVALID_HANDLE_VALUE )
      {
            // If CreateFile failed simply because other waiting threads
            // grabbed pipe, don’t bother user with error message.
         if( GetLastError() != ERROR_PIPE_BUSY )
         {
               // an unpredictable error occurred; show message
            ShowErrorMsg();
            bTerminate = TRUE;             // break loop; end program
         }
      }
      else
         bConnected = TRUE;                // succeeded in connecting
   }

When the client child successfully links to the server parent, the procedure updates the window caption and enters another loop to wait for messages to arrive through the pipe. The receiving loop ends when the user chooses Exit from the child’s menu or when the child receives an IDM_TERMINATE command from the parent.

      // change window caption to show this window is connected
   wsprintf( szBuffer, szTitle, (LPSTR)“ (connected)” );
   SetWindowText( hwndMain, szBuffer );

To read messages from the parent, the DoRead procedure calls ReadFile. It needs no revisions to work with a named pipe.

      // read messages from the pipe until we receive a terminate command
   while( ! bTerminate )
      DoRead( hPipeRead );
      // when bTerminate is TRUE, end the program
   FORWARD_WM_DESTROY( hwndMain, PostMessage );
   return( 0L );                               // implicit ExitThread()
   UNREFERENCED_PARAMETER( lpThreadData );
}

This chapter described how parent and child processes interact and how pipes allow processes to communicate. The demos discussed here also introduced the GetLastError and FormatMessage functions for signaling to the user when something goes wrong. Later in this part (in Chapter 18), you’ll learn about structured exception handling, a more advanced mechanism for dealing with unexpected failures.

In the next chapter, we’ll look at another aspect of threads, where ATL COM objects are used for multithreading. We’ll also examine how to use ActiveX components.

© 1998 SYBEX Inc. All rights reserved.