Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Capturing the Output of a MS-DOS Program

See how the Shell function really works and how you can replace it with a more flexible CShell class that support synchronous execution of an external program, as well as redirection of the shelled program's output to a file.


Have you ever heard of redirected input or console applications? Have you ever had the need to launch MS-DOS programs, wait for them to terminate, and then dump their output to screen? Let's see how to accomplish all this leveraging Win32 and Visual Basic.

Outside the exciting and pretty ideal world of the just-released software products, many developers have still to cope with some legacy MS-DOS program. In most cases, you have a Windows application that launches a MS-DOS program, waits for it to terminate and then has the need to capture its output, possibly as a string. Is all this doable with Visual Basic? But of course.

In this article, I'll describe a CShell class that is meant to be an enhanced wrapper for the glorious Shell command. It exposes an Execute method that runs650 368 2939 a batch-mode, non-interactive program and returns any character sent out to the standard output device during execution.

Redirecting Output
Working with MS-DOS, how many times you entered a command like this to get the list of all the executables in the root of your disk C:?

C:\> dir *.exe >Programs.txt

The final effect of such a command was to open an I/O channel on a file device and work with it the same way it would have done with the screen. We need just to reproduce this behavior from within a Win32 application.

The first approach that comes to mind suggests to employ the VB's shell function to execute the program's command line using the MS-DOS redirection operator > to send everything to a file. Let's see how this should work. The shell function has the following syntax:

processID = Shell(pathname[, windowstyle])

The first argument is the command line to execute, while the second argument denotes the style of the window in which the program must run. The returned value is the ID of the process created, if any. Notice that the returned value is the process ID and not the process handle (HPROCESS). You can obtain a HPROCESS from the ID using the OpenProcess() function. Shell starts any program asynchronously. In other words, the calling program continues to run as soon as the new process is started and nothing knows about its termination.

Under the Hood of the Shell Function
The Shell function utilizes the CreateProcess() function to start a new process. CreateProcess is the standard Win32 way to create a new process. It's a really powerful function even if its prototype is a bit baffling:

Declare Function CreateProcess Lib "kernel32" _
  Alias "CreateProcessA" ( _
    ByVal lpApplicationName As String, _
    ByVal lpCommandLine As String, _
    lpProcessAttributes As Any, _
    lpThreadAttributes As Any, _
    ByVal bInheritHandles As Long, _
    ByVal dwCreationFlags As Long, _
    lpEnvironment As Any, _
    ByVal lpCurrentDirectory As String, _
    lpStartupInfo As STARTUPINFO, _
    lpProcessInformation As PROCESS_INFORMATION _
) As Long

The program name you pass to Shell is in turn passed to CreateProcess as its lpApplicationName argument. The redirection operator you might think to put in the command line is ignored by CreateProcess that simply has another way to achieve the same result. Thus you should forget a syntax like

Shell "MyProg.exe >File.txt"

and begin concentrating on the features CreateProcess() exposes.

Arranging a Call to CreateProcess
The following is a typical way to call into CreateProcess. As you can see, many arguments are just zeroes:

bResult = CreateProcess( _
    szProgram, _
    vbNullString, _
    ByVal 0&, _
    ByVal 0&, _
    True, _
    ByVal 0&, _
    vbNullString, _
    si, _

The function returns a boolean value denoting the success of the operation. The first two arguments indicate the program name and its command line. You aren’t strictly required to split program name and command line but you could pass all through the single lpApplicationName parameter. In most cases you don’t need to take care of all the remaining arguments until the last two. (You can leave them to take the default values, as shown above.)

The core of CreateProcess – at least for the purposes of this article – are the arguments of type STARTUPINFO and PROCESS_INFORMATION.


   hProcess As Long
   hThread As Long
   dwProcessId As Long
   dwThreadId As Long
End Type

   cb As Long
   lpReserved As String
   lpDesktop As String
   lpTitle As String
   dwX As Long
   dwY As Long
   dwXSize As Long
   dwYSize As Long
   dwXCountChars As Long
   dwYCountChars As Long
   dwFillAttribute As Long
   dwFlags As Long
   wShowWindow As Integer
   cbReserved2 As Integer
   lpReserved2 As Byte
   hStdInput As Long
   hStdOutput As Long
   hStdError As Long
End Type

Any process has its own handle and its own ID. When a process starts it generates at least one thread. Alos threads are identified via handles and IDs. All this information is returned through the parameter of PROCESS_INFORMATION.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date