Reading and Writing through a Pipe

All the choices you make to create a pipe—named or anonymous, byte or message, blocking or nonblocking—prepare for the moment when you actually send a message through the pipe. One program writes to its handle, and the other program reads from its handle. This most basic transaction typically involves two functions: WriteFile and ReadFile.

BOOL WriteFile(
   HANDLE hFile,                   // place to write (pipe or file)
   CONST VOID *lpBuffer,           // points to data to put in file
   DWORD dwBytesToWrite,           // number of bytes to write
   LPDWORD lpdwBytesWritten,       // returns number of bytes written
   LPOVERLAPPED lpOverlapped );    // needed for asynchronous I/O
BOOL ReadFile(
   HANDLE hFile;                   // source for reading (pipe or file)
   LPVOID lpBuffer;                // buffer to hold data retrieved
   DWORD dwBytesToRead;            // number of bytes to read
   LPDWORD lpdwBytesRead;          // returns number of bytes read
   LPOVERLAPPED lpOverlapped );    // needed for asynchronous I/O

Bytes to Read or Write

The number of bytes to read or write need not be as large as—but should not be larger than—the size of the buffer. If you call ReadFile on a message-mode pipe, however, and give dwBytesToRead a value smaller than the size of the next message, ReadFile reads only part of the message and returns FALSE. A subsequent call to GetLastError discovers an error code of ERROR_MORE_DATA. Call ReadFile again or PeekNamedPipe to read the rest of the message. When WriteFile writes to a nonblocking byte-mode pipe and finds the buffer nearly full, it still returns TRUE, but the value of *lpdwBytesWritten will be less than dwBytesToWrite.

Blocking

Depending on the pipe’s wait mode, both WriteFile and ReadFile may block. WriteFile might need to wait for a full pipe to empty out from the other end; an empty pipe causes ReadFile to block waiting for a new message.

Asynchronous I/O

The final parameter of both commands points to an OVERLAPPED structure that provides extra information for performing asynchronous or overlapping I/O. Asynchronous I/O allows the command to return immediately, even before the read or write operation is finished. Asynchronous commands do not automatically modify the position of the file pointer, so the OVERLAPPED structure includes an offset pointing to the place in the file where the operation should begin.

The structure also contains a handle to an event object. A thread in the reading program can wait for the event’s signal before examining what ReadFile placed in the retrieval buffer. You must supply an OVERLAPPED structure when using file handles that were created with the FILE_FLAG_OVERLAPPED attribute.

Another method of performing asynchronous I/O involves the ReadFileEx and WriteFileEx commands. Instead of signaling completion with an event, these commands invoke a procedure you provide to be called at the end of each operation.

With respect to pipes, overlapping I/O is a useful strategy for dealing with multiple clients connected to different instances of a single pipe. Synchronous I/O is easier to program, but slow read and write commands might hold up other waiting clients. A server can create a separate thread for each client, but that involves more overhead than the situation actually requires.

A single thread can read and write simultaneously to different pipe instances with asynchronous I/O because each command always returns immediately, leaving the thread free while the system finishes in the background. With WaitForMultipleObjects, a thread can arrange to block until any pending operation is completed. The efficiency of asynchronous I/O can make a big difference over slow network connections. Also, it is easier to protect program resources when you have fewer threads to synchronize.

© 1998 SYBEX Inc. All rights reserved.