Bob Withers has a BS in computer science from Oakland University in Rochester, MI and has been active in data processing for 20 years. He began programming for micros in 1985 and has focused increasingly on C and 0S/2. Currently, he works for Texas Instruments as a member of its Group Technical Staff. He can be contacted at 649 Meadowbrook St., Allen, TX 75002.
In this article I'll discuss the anonymous pipe interprocess communication (IPC) feature of OS/2 and present a short example program to demonstrate its use. Anonymous pipes allow stream communication to occur between two related processes. Anonymous pipes are different from named pipes because anonymous pipes are identified solely by file handles. An OS/2 anonymous pipe can only be used for communication between two processes which share the same command subtree. In other words, the anonymous pipe handles must be inherited from a parent process.
An anonymous pipe is created via the DosMakePipe() API function, which accepts pointers to two file handles and returns handles for reading and writing to the pipe. You can pass DosMakePipe() an additional, third parameter to specify the pipe's size in bytes. OS/2 implements the pipe as a circular buffer. When a process writes to the pipe, the data is placed in the buffer. If the buffer becomes full, the writing process is blocked until data is removed from the buffer via the read handle.
Since anonymous pipes are identified only by file handles handles which must be inherited by a child process it's reasonable to ask how the child process knows which handles to use. The answer is that unless some other form of communication has occurred between parent and child, it doesn't. In fact, the child doesn't even know it has inherited file handles. Though a parent process can use several methods to advise a child of the pipe's handles, it's more common to leave the child process in the dark, so to speak. In general, a parent redirects a child's STDIN, STDOUT, and/or STDERR streams to an anonymous pipe, altering the normal processing of the child without its ever knowing. This is how the OS/2 command shell, CMD.EXE, supports command line redirection and is the approach taken in my sample program ROUTEMSG.
The Sample Program
Listing 1 shows the source code for a short OS/2 utility named ROUTEMSG, and Listing 2 shows the makefile used to compile and link it. The purpose of ROUTEMSG is to capture the output of a child process and write it to an ASCII file, as well as display it on the video screen. ROUTEMSG executes a child process and redirects its STDOUT and STDERR streams to an anonymous pipe. I find this utility very handy for running large makefiles. All compiler messages are captured to an ASCII file, leaving me free to switch to another screen group while the compile(s) run in the background. After the make is complete, I can check the ASCII file for error messages and make corrections. Having the compiler output also appear on the video display allows me to switch back to the session running the compile to monitor its progress. Following is the command line syntax for running ROUTEMSG:
ROUTEMSG <filename> <pgmname> [<args> ...]wherefilename - the name of the ASCII file used to capture the child's output
pgmname - the name of the program to run
args - optional command line arguments used by <pgmname>
ROUTEMSG Details
Letters (A) through (M) below expand on comments referenced in Listing 1.(A) The program begins by checking receipt of the minimum required command line arguments, namely the <filename> and <pgmname> arguments.
(B) The OS/2 DosOpen() API function opens <filename> as output and instructs the operating system to either create it or truncate any existing file. Note that the NOINHERIT flag is explicitly set to prevent the child process from inheriting this file handle, thereby maintaining the default number of handles available to the child. Allowing the child to inherit unneeded handles could cause the child process to fail due to a lack of "real" handles for its own use.
(C) The DosMakePipe() API creates the anonymous pipe and returns read and write handles in the fh_piperead and fh_pipewrite variables respectively.
(D) The STDOUT and STDERR handles inherited from the parent process, most likely the command shell CMD.EXE, are not needed and are closed.
(E) Handles for the STDOUT and STDERR processes are redirected to the pipe's write handle via the DosDupHandle API function. The DosDupHandle() service creates a new handle by which an existing stream may be referenced. The second parameter points to a variable of type HFILE (Handle of a FILE) which contains the newly assigned file handle. If this variable is initialized to OxFFFF, OS/2 selects an unused handle and assigns it. Otherwise, as in the sample, the initialized value is used as the new file handle. I use this mechanism to force DosDupHandle() to reassign the pipe write handle to the STDOUT and STDERR handles.
(F) I use the DosSetFHandState API to modify the pipe read handle so that the child process doesn't inherit it. The reasoning is the same as described in item (B).
(G) Since ROUTEMSG does not intend to write to the pipe, the handle obtained for this purpose can now be closed. The pipe won't be destroyed, however, since there are still open handles which reference it, namely STDOUT, STDERR, and fh_piperead.
(H) The child program name is formatted, if necessary, to include a .EXE extension since the OS/2 API used to create child processes requires filename extensions.
(I) The command line to be passed to the child process is formatted. OS/2 requires the program name followed by a NUL byte and then followed by the program's command line arguments each separated by one or more spaces and terminated by two NULL bytes.
(J) The DosExecPgm API function now invokes the child program. This call requests asynchronous execution of the child and parent processes. By passing a NULL pointer for the environment strings, I instruct OS/2 to use a copy of the current processes environment for the child.
(K) The "pipe write" file handles redirected earlier to STDOUT and STDERR are no longer needed (they have already been inherited by the child) and are closed. This is more than simply "clean-up". The read handle to the pipe will not be notified that the pipe has no writers until all write handles are closed. Leaving these two handles open in our process will cause ROUTEMSG to block forever waiting for something to be written to the pipe.
(L) We finally get to the meat of the program so far everything has been initialization. This while loop reads data from the pipe and directs it to both the screen and the output file. I use the VioWrtTTY function to display data on the video screen and DosWrite to update the output file. The loop terminates if the DosRead function returns zero bytes read or if 0S/2 detects an error reading the pipe. The significance of zero bytes being returned by DosRead() is that the pipe is empty and no write handles exist.
(M) The remaining open handles are closed and the program is terminated.
Summary
I've presented a brief introduction to 0S/2 anonymous pipes and shown you where this form of IPC is most useful modifying the standard stream processing of a child process. I've tried to point out some aspects of anonymous pipes that aren't immediately obvious after a casual reading of the 0S/2 documentation. In addition, I hope the ROUTEMSG utility is a useful addition to the tool-chests of other 0S/2 developers.