Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

A Windows Shell for Legacy MS-DOS Applications


June 1997/A Windows Shell for Legacy MS-DOS Applications

Sometimes the best way to use an old DOS-based program in a Windows environment is to do as little as possible.


There are thousands of MS-DOS programs in active use, even today. I often encounter situations in which it makes sense to use an MS-DOS application as part of a Windows program, or conversely, to have a modern Windows front-end for an MS-DOS program. As an example of the former, a developer might want to incorporate the uuencode/uudecode or pkzip/pkunzip utilities into a Windows application.

While it's easy to see the value of hooking an MS-DOS program to a Windows program [1] , linking the two to form one process can be difficult. The source code for the MS-DOS program may be unavailable, and rewriting the original code can be an unjustifiably expensive procedure. Fortunately, there's another way. One of the best approaches to making Windows and DOS programs communicate involves the use of anonymous pipes, which are typically used to direct communications between parent and child processes [2]. This article gives an example of DOS-Windows communications using anonymous pipes. In it I also describe some of the problems I encountered, and workarounds. Some of the code I use in this article (specifically for the CPipedProcces::CreateProc(char* ExeFileName) function) is modeled on the example discussed in the Microsoft Developer Studio on-line help topic devoted to anonymous pipes.

Setting Up Pipes

There are three steps in establishing communications using anonymous pipes. Normally you need two types of pipes, one for sending strings to an MS-DOS program and one for receiving responses from this program. The following example focuses on setting up a pipe for sending strings to an MS-DOS program. With some modification these steps will work for creating the pipe for receiving as well. (For more information see the Microsoft Developer Studio on-line help.)

Step 1. Create a pipe. I usually think of a pipe as a simple garden hose with two end-connectors to connect this hose to a faucet. I have no idea what these connectors are called in the case of the garden hose, but in the case of anonymous pipe they are called handles. You can use them as file handles with the WriteFile and ReadFile functions to write to or read from pipes, respectively. This feature is the key to setting up the communication.

Step 2. Set a read handle to the pipe to be stdin. This basically means that you screw one of the ends of the hose to the faucet "stdin."

Step 3. Run the MS-DOS program as a new child process which inherits handles created by the parent process.

From this point onward, the MS-DOS program will be reading information not from the console (or keyboard), but from the pipe. The other (unattached) end of the pipe will be used for sending information to the MS-DOS program.

The solution for reading from the MS-DOS program is similar. You create a pipe and attach one end to stdout. If the MS-DOS program uses stderr, a pipe should be created for that stream as well. This approach will work as long as the MS-DOS program uses stdin, stdout, and stderr.

Code

The procedure that creates and uses the anonymous pipes is implemented as a class, named CPipedProcess. The header file for this class appears in Listing 1. Function CreateProc(char* ExeFileName) starts an MS-DOS executable file with the name ExeFileName. This function creates three pipes and runs an MS-DOS program using the function CreateChildProcess. (Again, for more details on implementing these functions, see Microsoft Developer Studio on-line help.)

Functions WriteToPipe and ReadFromPipe implement the procedure of sending strings to the MS-DOS program and receiving responses from this program respectively. ProcessCommand simply contains the WriteToPipe / ReadFromPipe pair. In my original program this function also contains the code for setting a text field in the status bar (for informational purposes) and processing error messages. Function CloseChildProcess simply closes all the handles, and function ErrorExit closes the child process with the error message.

A Few Caveats

Listing 2 contains the implementation of all these functions except WriteToPipe and ReadFromPipe. The code for these functions is found in Listing 3. At this point, I'll warn that the MSDEV on-line example contains the following code in WriteToPipe:

/* Close the pipe handle so the child stops reading. */

if (! CloseHandle(hChildStdinWrDup))
   ErrorExit("Close pipe failed\n");

This statement will deprive you of the possibility of using the pipe in the future, so if you need to continue communications with the child process, you should not implement this action. The same is true for ReadFromPipe. Moreover, if you try to read from an empty pipe, your program will hang. In other words, if the child process has not written anything, ReadFile should not be called. This is a weak point in the ReadFromPipe function. In addition, if the length of the string being read is divisible by the size of the buffer, calling the ReadFile function after all the information has been read will hang the program because the pipe will be empty. However, based on experiments, if the buffer size is large enough, problems are unlikely.

If you do not use CloseHandle (hChildStdinWrDup) in the WriteToPipe function, be sure that the command you send to the child process ends with "\r\n". Otherwise WriteToPipe will be waiting for this string, and the program will hang.

Another problem crops up when a DOS program writes to stdio, and stdio is attached to a standard 512-byte buffer. In this case the pipe can be empty even while there's output data in the buffer. A Windows program that tries to read from the pipe will hang. This problem can be fixed in the DOS source by adding fflush after each printf or setvbug(stdout, NULL, _IONBF, 0) at the beginning of the program.

Example

Listing 4 presents an example of how to use CPipedProcces. This example is largely self-explanatory, except for the following cycle:

while(-1 == Response.Find("*****")) {
tmpString.Empty();
 RunProc -> ReadFromPipe(tmpString);
 Response += tmpString;
}

The reason for this cycle is that if the output of the MS-DOS program is big enough, the parent process, or the Windows application, may not be able to read it all at once. The actual size of "big enough" depends on the machine on which you are running your program. In my tests, the parent process could read hundreds of bytes, even when running on a 486. However, if you expect output on the order of kilobytes, it is safer to use a cycle similar to the one above. The reason? If the MS-DOS program's output is very large, the program might write just a part of this output before it temporarily stops writing, to perform some other task, such as calculations. If the final part sent from the MS-DOS program to the pipe is less than BUFFSIZE, the ReadFromPipe function which is part of ProcessCommand will stop reading, and will return. When the MS-DOS program goes back to write the rest of the output to the pipe, the parent process will not receive this information because it has stopped reading. The simplest solution is to keep the program reading until the cycle described above provides a unique message that says the MS-DOS program has finished. In my example, the MS-DOS function sends five stars ("*****") after it has finished.

Summary

Using the approach described above, you can create a Windows shell for a legacy MS-DOS program without rewriting the program, and even without having the source code. The main restrictions are as follows:

  • The MS-DOS program must use stdin as an input and stdout and/or stderr as outputs.
  • If the MS-DOS program returns a large output, it should return a unique string after it has finished to signal completion.

An example application using this Windows shell approach is Raima Studio (rstudio). Raima Studio is available from Raima Corporation's web site at http://www.raima.com. It has been available and in use for several months, and the communication portion based on the anonymous pipes approach has been stable, indicating its potential in different applications.

Notes

[1] I tackled getting Windows programs to talk to MS-DOS programs while working with utilities for Raima Corporation's Velocis Database Server. Several Raima utilities for 32-bit Windows operating systems were originally written as console applications. Since an important feature of Raima database technology is its cross-platform capabilities — namely the ability to run on Windows, Novell, Unix, and OS/2, and to port easily between these operating systems — development of platform-specific code must be as efficient as possible. The anonymous pipes approach allowed us to quickly develop fully functional 32-bit Windows prototypes of the console applications.

[2] Background and a detailed description of using anonymous pipes can be found in the Microsoft Developer Studio on-line help, Win32 SDK: Win32 Overview, "Creating a Child Process With Redirected Input and Output," Listing 1.

Dmitri Klementiev has a Ph.D. in math from Moscow Institute of Physics and Technology (Russia). He has spent more than 12 years in mathematical and computer modeling, eight of them at Moscow Institute of Control Science (Russia) developing mathematical and computer models for control optimization. In the last year he has been a software engineer with Raima Corporation, coding utilities for Velocis Database Server (embedded database engine). He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.