The UI Thread
The UI thread, the main thread of the GUI Application, handles user input and renders UI elements. It starts the Worker thread when the user clicks the Start button, and updates the progress bar when it gets a
The Worker Thread
The Worker thread launches the Code Analyzer process and passes it a command-line string containing the input directory, Progress Message pipe name, and other parameters. The Worker thread is the pipe server for the Progress Message named pipe. It creates a synchronous named pipe by calling the
CreateNamedPipe API and specifying
PIPE_WAIT mode. It establishes itself as the pipe server by calling
ConnectNamedPipe, which blocks until a client connects to the pipe; the Code Analyzer is waiting to connect as explained below.
The Worker thread then performs these steps repeatedly:
ReadFileon the pipe; this blocks until a message arrives or the client disconnects.
- Deserialize the progress message. Serialization is explained below.
- Send the deserialized progress data to the GUI Application's main window using the
SendMessageWindows API and a custom Windows message.
SendMessageblocks until the UI thread has processed the message, so passing a pointer to the progress data is thread-safe. Alternatively, the Worker thread could notify the UI by calling
PostMessage, passing data in a heap-allocated buffer for thread safety.
This loop exits when the client disconnects from the pipe. The Worker thread then waits for the Code Analyzer process to exit by calling the
WaitForSingleObject API. The Worker thread gets the Code Analyzer's exit code, notifies the UI thread via a custom Windows message, and exits.
The Code Analyzer Thread
The Code Analyzer thread is the main thread of the Code Analyzer program. It gets the name of the progress message pipe via a
CreateProcess parameter, and connects as a client by calling the
Connect() method of the
NamedPipeClientStream .NET class. It analyzes files one by one, periodically writing a progress message to the named pipe. When it finishes, it closes its connection to the named pipe, sets a success or failure exit code, and exits.
One problem I grappled with was how to connect the client and server to the pipe, since both API functions block and must be done in a certain order. Before a client can connect, a pipe server must make the pipe available by calling
ConnectNamedPipe. How could I get the Code Analyzer to connect as the client after the GUI Application calls
ConnectNamedPipe given that the latter blocks until a client connects? My solution to this Catch 22 was to use an overloaded
Connect method of the .NET
NamedPipeClientStream class that takes a parameter for "the number of milliseconds to wait for the [pipe] server to respond." When the Code Analyzer calls this
Connect method, it waits for a few seconds if necessary for the GUI Application to make the pipe available.
The GUI Application creates the pipe by calling
CreateNamedPipe and indicates with the
PIPE_READMODE_MESSAGE flags that data will be in message form. Each progress message has several elements, such as percent complete, elapsed time, subdirectory path, etc., and must be serialized in a way that can be deserialized later. The Code Analyzer serializes each element as a string using .NET
StreamWriter objects. The GUI Application calls
ReadFile to read the pipe's contents into a buffer and parses the buffer to deserialize each progress message element. The Code Analyzer writes only one progress message at a time; it waits for the pipe to drain before writing the next progress message, but rarely waits because UI updates are quicker than code analysis.
Using an Asynchronous Named Pipe for Stop Notification
A user can click the GUI Application's Stop button at any time (that is, asynchronously). I used an asynchronous named pipe to send the stop request to the Code Analyzer as shown in Figure 3. A second thread in the Code Analyzer called the Stop Request Handler monitors the pipe for a "STOP" message.
The UI Thread
The UI thread creates the Stop Message Named Pipe by calling
FILE_FLAG_OVERLAPPED to make it asynchronous. The UI thread establishes itself as the pipe server by calling
ConnectNamedPipe, which is non-blocking for asynchronous pipes, then starts the Worker thread. If the user clicks the Stop button, the UI thread notifies the Code Analyzer by writing "STOP" to the Stop Message Named Pipe.
The Worker Thread
The Worker Thread functions as described in the previous section.
The Stop Request Handler Thread
Writing code that reads from an asynchronous named pipe may sound daunting, but .NET makes it easy. The Code Analyzer creates a
NamedPipeClientStream for the Stop Message Named Pipe and begins an asynchronous read operation by calling its
BeginRead method. If data arrives on the pipe, .NET reads it to a buffer and calls
ListenForStopMsgCallback on the Stop Request Handler Thread. If the message is the string "STOP,"
ListenForStopMsgCallback sets the
StopRequested flag to
true, notifying the Code Analyzer to stop.
The Code Analyzer Thread
The Code Analyzer should stop processing files if "STOP" arrives on the Stop Message Named Pipe. The Code Analyzer thread periodically checks the
StopRequested flag and, if
true, stops analyzing files and terminates with a suitable exit code.
This article shows how a Windows application can communicate with a .NET console application using named pipes. The devil is in the details, especially when writing software. Code is provided, along with notes, demonstrating the techniques discussed in this article. Writing this code was a fun challenge, particularly the multi-threaded pipe access, and I found that .NET made it easier to work with named pipes. I hope you find this article helpful if you're considering using named pipes in your applications.
I wish to thank Bob Zeidman for his support and inspiration for this article. Thanks also to Bob and my colleagues Nik Baer, Larry Melling, and Michael Everest for their technical and editorial reviews and suggestions.
Chappell, D. (2010, March). Introducing Windows Communication Foundation in .NET Framework 4. Retrieved May 11, 2011, from MSDN.
Fisher, S. (1988, October 17). IBM's OS/2 Named-Pipe Support Quells LAN Incompatibility Fears. InfoWorld , 10 (42), pp. 1, 8.
Lewandowski, S. M. (1997). Interprocess Communication in UNIX and Windows NT. Brown University, Computer Science. Department of Computer Science, Brown Univerity.
Microsoft. (2010, 9 7). Dynamic Data Exchange (DDE). Retrieved 4 19, 2911, from MSDN.
Microsoft. (2011, 1 27). Pipes. Retrieved 4 20, 2011, from MSDN.
Microsoft. (2011, 4 5). Windows Sockets 2. Retrieved 4 19, 2011, from MSDN.
Microsoft. (2005, 7 19). Windows Version History. Retrieved 4 20, 2011, from Microsoft Support.
Ritchie, D. M. (1980). The Evolution of the Unix Time-sharing System. In J. M. Tobias (Ed.), Language Design and Programming Methodology 1979, Proceedings of a Symposium held in Sydney, Australia; Published in Lecture Notes in Computer Science. 79, pp. 25-36. Springer-Verlag.
Ritchie, D. (1977). The Unix Time Sharing System – A Retrospective. Retrieved 5 11, 2011, from cm.bell-labs.com.
Jim Zamiska is a software engineer and project manager with a background in Microsoft technologies.