Creating Named Pipes

Many Windows NT system objects may be assigned name strings to identify them. While the named pipe operations can be created (compiled) only under Windows NT, applications using named pipes can be run under Windows 98, where they perform essentially the same as anonymous pipe objects. The advantage to using named pipe objects is that names allow other processes to locate objects more easily. Unnamed objects are known only by their handles, and handles are valid only in the process where they originate. Any process that knows the name of an object, however, can ask the system to search its object name hierarchy. Given a name, the system can find any object on any connected machine.

If a pipe has a name, the client program does not need to wait for the server to pass it a handle. Instead, the client can acquire a handle by calling CreateFile or CallNamedPipe. In either case, the client needs to know only the pipe’s name string. A parent might pass the string to a child process on the command line, or any process might pass it to any other through a shared file or a DDE conversation. Most often, however, two processes sharing a named pipe have been written by the same developer, so they simply agree on a name string in advance.

The following commands work only with named pipes. Do not create an anonymous pipe if you need the functions these commands provide.

CallNamedPipe         GetNamedPipeInfo

ConnectNamedPipe         ImpersonateNamedPipeClient

CreateFile            RevertToSelf

CreateNamedPipe         SetNamedPipeHandleState

DisconnectNamedPipe      TransactNamedPipe

GetNamedPipeHandleState      WaitNamedPipe

To create named pipes, you use the CreateNamedPipe command:

HANDLE CreateNamedPipe(
   LPTSTR lpszPipeName,            // string naming new pipe object
   DWORD  fdwOpenMode,             // access, overlap, and write-through
   DWORD  fdwPipeMode,             // type, read, and wait modes
   DWORD  dwMaxInstances,          // maximum number of instances
   DWORD  dwOutBuf,                // outbound buffer size in bytes
   DWORD  dwInBuf,                 // inbound buffer size in bytes
   DWORD  dwTimeout,               // time-out interval in milliseconds
   LPSECURITY_ATTRIBUTES lpsa );   // access privileges

CreateNamedPipe Parameters

Because named pipes have more features, CreateNamedPipe takes more parameters.

The first parameter points to the string you provide to name the new object. The system stores this name in its hierarchical tree of system object names. Pipe name strings should follow this form:

\\.\pipe\<pipename>

The first backslash designates the root node of the system’s object name hierarchy. The other three backslashes separate the names of subsequent nodes. The dot (.) stands for the local machine. Although pipes can connect with clients on other network servers, a new pipe object always appears on the local server where it was created.

Under the server name is a node called pipe, holding the names of all the pipe objects on the local machine. Within the name string, the substring <pipename> is the only section the programmer chooses. This substring may be as long as 256 characters and is not case-sensitive because object names are not sensitive to case.

Servers and clients both use the dot (.) to represent the local server, but a client wishing to open a pipe on a remote server must know the server’s name. One way to learn remote server names is to enumerate them with the WNetOpenEnum, WNetEnumResource, and WNetCloseEnum functions, but enumeration is slow.

TIP

To find out about a better method for enumerating remote server names, see “Distinguishing Pipes from Mailslots” later in this chapter.

The CreateNamedPipe, CreateFile, WaitNamedPipe, and CallNamedPipe functions require a pipe’s name string as a parameter.

Access, Write-through, and Overlapping The next parameter after the name string, fdwOpenMode, combines flags to set several pipe attributes. The most important attribute is the access mode, which determines the direction information flows through the pipe. fdwOpenMode must include one of the following three access flags:

The other two flags in this parameter are optional:

For efficiency, when a pipe extends to a remote machine, the system normally does not send every message immediately. Instead, it tries to accumulate several short messages in a buffer and send them across the pipe in a single operation. If too much time passes and the buffer remains only partly full, the system sends the buffer anyway. The FILE_FLAG_WRITE_THROUGH flag prevents the system from buffering; each new message is sent immediately, and write commands do not return until their output has been transmitted. Turn off the buffering if you expect to send messages only infrequently.

Because they involve physical devices, read and write operations are usually slow. The second optional flag, FILE_FLAG_OVERLAPPED, allows read and write commands to return immediately while the action they initiate continues in the background.

When a Windows NT program reads from a file, for example, it may choose simply to start the read process, name a procedure to be called when the read operation ends, and then continue executing while the system reads in the background. When the read operation finally ends, the system schedules an asynchronous procedure call and invokes the callback function named in the read command. The callback function then processes the newly retrieved information. Making the system do your reading and writing in the background is called asynchronous I/O or overlapping I/O. Pipes also support overlapping I/O. Overlapping I/O is more difficult to program because you need to write a callback function, but it’s also more efficient.

Type, Read, and Wait The fdwPipeMode parameter combines flags to designate another set of pipe features: the read mode, the type, and the wait flag. The type and the read mode are closely related; they might be better named the write mode and the read mode. Together, they control how information in the pipe is organized and interpreted. Both offer a choice between byte and message modes.

The pipe type (write mode) flags are PIPE_TYPE_BYTE and PIPE_TYPE_MESSAGE. The read mode flags are PIPE_READMODE_BYTE and PIPE_READMODE_MESSAGE

The information in a byte-mode pipe is read and written in the normal binary manner and understood as being nothing more than a series of bytes.

Sometimes, however, it is more convenient to divide the information in a pipe into discrete messages, where the output from each separate write command constitutes a new message. A message-mode pipe automatically and transparently prefaces each new message with an invisible header specifying the length of the message. The header enables a read command to stop automatically when it reaches the end of one message. The recipient recovers messages one at a time, exactly as they were written.

If one program sends to another a long series of integers, for example, it would probably use a byte-mode pipe, because the receiver does not care how many integers were written at a time. Everything it retrieves is simply another integer. But if a program were sending commands written in a script language, the receiver would need to retrieve the commands one at a time, exactly as written, in order to parse them. Because each command might be a different length, the two programs would use a message-mode pipe.

The write mode and read mode are designated independently, but not all combinations make sense. Specifically, you cannot combine PIPE_TYPE_BYTE and PIPE_READMODE_MESSAGE. A byte-type pipe writes bytes without message headers, so the receiver cannot recover message units. On the other hand, you can combine PIPE_TYPE_MESSAGE with PIPE_READMODE_BYTE. In this case, the sender includes message headers but the receiver chooses to ignore them, retrieving the data as a series of undifferentiated bytes. (The receiver still does not see the invisible message headers.)

Besides the flags to set the type and read mode for a pipe, the fdwPipeMode parameter accepts one other flag for the wait mode. The wait mode determines what happens when some condition temporarily prevents a pipe command from completing. For example, if you try to read from an empty pipe, some programs might want to forget about reading and move onto the next instruction, but other programs might need to wait for a new message before proceeding.

By default, pipes cause reading threads to block and wait, but you can prevent blocking by adding the PIPE_NOWAIT flag to fdwPipeMode. (The default flag is PIPE_WAIT.) The wait mode affects write commands as well as read commands. A program that tries to write when the pipe buffer is full normally blocks until another program makes room by reading from the other end. The wait mode also affects a server trying to connect with a client. If the ConnectNamedPipe command finds no ready clients, the wait mode determines whether the command waits for a client to connect or returns immediately.

Pipe Instances A server program may wish to open pipes for more than one client, but it may not know in advance how many clients it will have. It would be inconvenient to invent a new pipe name for each new client. How would all the clients know in advance what name to use when they open their end of the pipe? To circumvent this problem, Win32 permits the server to create the same pipe over and over.

Each time you call CreateNamedPipe with the same name, you get a new instance of the same pipe. Each new instance provides an independent communication channel for another client. The server might begin by creating the same pipe four times. It would receive four different handles, and it could wait for a different client to connect to each one. All the clients would use the same pipe name to request their own handles, but each would receive a handle to a different instance. If a fifth client tried to connect, it would block until the server disconnected one of the first four instances.

The dwMaxInstances parameter of the CreateNamedPipe command sets an upper limit on the number of instances one pipe will support before CreateNamedPipe returns an error. The PIPE_UNLIMITED_INSTANCES flag indicates no upper limit. In that case, the maximum number of instances is limited only by system resources. The value of dwMaxInstances may not exceed the value of PIPE_UNLIMITED_INSTANCES. (Winbase.H defines the value of dwMaxInstances as 255.)

Buffer Sizes The dwOutBuf and dwInBuf parameters set the initial size of the buffers that store anything written to the pipe from either end. For an outbound pipe (PIPE_ACCESS_OUTBOUND), only the output buffer matters; for an inbound pipe, only the input buffer size is significant.

The limits set by the buffer size parameters are flexible. Internally, every read or write operation on a pipe causes the system to allocate buffer space from the kernel’s pool of system memory. The buffer size value is interpreted as a quota limiting these allocations. When the system allocates buffer space for a write operation, it charges the space consumed to the write buffer quota. If the new buffer size fits within the quota, all is well. If it does not, the system allocates the space anyway and charges it to the process’s resource quota. To avoid excessive charges to the process quota, every WriteFile operation that causes the buffer to exceed its quota blocks. The writing thread suspends operation until the receiving thread reduces the buffer by reading from it.

In estimating buffer sizes, you’ll need to take into account the fact that each buffer allocation is slightly larger than you expect because it includes an internal data structure of about 28 bytes in addition to the message contents. The exact size is undocumented and may vary from version to version.

To summarize, the system allocates buffer space dynamically as needed, but threads that frequently exceed their buffer size may block excessively. The AnonPipe and NamedPipe demos, discussed later in this chapter, leave the buffer size at 0 and suffer no apparent harm. Programs that send frequent messages or that expect the buffers to back up occasionally will benefit from increased buffer sizes.

Time-out Period The dwTimeout value matters only when a client calls WaitNamedPipe to make a connection, and it matters then only if the client accepts the default time-out period. The default period is the number the server sets in the dwTimeout parameter of CreateNamedPipe, but the client may set a different period in WaitNamedPipe.

Security Attributes The final parameter, a pointer to a SECURITY_ATTRIBUTES structure, should look very familiar by now. The values in it determine which operations the new handle allows you to perform on its object, and they also determine whether child processes may inherit the new handle. As usual, if you leave the field NULL, the resulting handle has full access privileges and cannot be inherited.

NOTE

Anonymous pipes always have the characteristics that are the default state for named pipes: PIPE_TYPE_BYTE, PIPE_READMODE_BYTE, PIPE_WAIT, no overlapping I/O, and network buffering enabled.

CreateNamedPipe Return Values

CreateNamedPipe returns a valid handle if it succeeds. If an error occurs, it returns the value (HANDLE)0xFFFFFFFF (a.k.a., INVALID_HANDLE_VALUE).

WARNING

Applications that are compiled under Windows 98 and use CreateNamedPipe will always return failure (INVALID_HANDLE_VALUE).

You may have noticed that many of the functions we’ve described seem to have rudimentary error returns. CreateThread, CreateMutex, CreateProcess, CreatePipe, and CreateNamedPipe, for example, all might fail for a variety of reasons. The system might be low on memory, or a particular mutex might already exist, or a network connection might fail, or a parameter might be invalid. Yet all of these creation functions indicate errors only by returning either FALSE or an invalid handle.

Better diagnostics are available. The system keeps a large set of error messages in a single collective message table and identifies each message with a different number. Whenever a command fails, it stores an error message number for the active thread. Immediately after a function fails call GetLastError to retrieve the message number. To translate the number into an explanatory string suitable for showing in a message box, call FormatMessage.

Even functions from the Windows 3.1 API sometimes set error codes under Win32. Microsoft’s online help file regularly identifies error-setting commands in the descriptions of their return values: “To get extended error information, use the GetLastError function.” The AnonPipe and NamedPipe demos, described later in this chapter, construct a procedure for displaying the appropriate message after any error. Look for ShowErrorMsg in the listings.

© 1998 SYBEX Inc. All rights reserved.