Site hosted by Angelfire.com: Build your free website today!

Simple Console I/O in Visual Basic

We usually think of Visual Basic as a tool for writing graphical user interface applications.  Most of the time this is precisely what we want.  Sometimes though it can be handy to make an old-style console application though.

This discussion doesn't cover communicating with a shelled external program.  It describes a way to create a VB program that is meant to be run from the command prompt and communicates with the user via console I/O operations.

There is a lot about this elsewhere of course, but since I want to build on it elsewhere in another topic I thought it might be worth presenting the information here.

Console Programs: Background

Console programs have a few standard characteristics:

Many of you are quite familiar with those characteristics from your MS-DOS, Unix, or other command line work.  The same rules hold true from a console program written in VB 6 (or for that matter 5).

Console Subsystem?

If you haven't tried creating true console programs in VB you may not have encountered this one before.  The VB compiler normally produces a standard EXE project's executable as a Windows subsystem program.

To avoid the undesired effects this causes in a command line program, we'll need to post-process the EXE created when we do a VB compile.  The easiest way is to edit the EXE by using the LINK.EXE utility that comes with VB.

No Forms, No Events

Since we're creating a console program, we can't really use VB forms.  Since we will be writing most code in standard code modules instead, for the most part we can't use VB events either then.

While there are exceptions to this, from here I am making the assumption we're writing straight-line code that never pauses except on blocking operations like simple I/O, Sleep() API calls, or blocking method calls on ActiveX objects.

Command Line Parameters

This is one of the easiest things we have to handle.  Easy, because VB happens to already have this mechanism in place.  Just call the Command$() function:

Sub Main()
  Dim strCmds() As String

  strCmds = Split(Command$(), " ")

This gives us a simple form of command line parsing that assumes the parameters are simple values separated by spaces.  Obviously one can perform more complex parsing on the string returned by Command$() as well.

Return Codes

In most cases a VB program will just exit out of the bottom of Sub Main() and let the return code default to zero.  Sometimes though it is useful to return other values, typically when the program is run from a .BAT or .CMD file, or executed by another program or a WSH script.

Returning other values can be done through a simple Win32 API call: ExitProcess(uExitCode) .

This will of course immediately terminate your program, so use it carefully.  The value to be returned is an unsigned Long.

Sub Main()
  If DoTheWork(Command$()) Then
    ExitProcess 0 'Normal completion.
  Else
    ExitProcess 1 'Signal unusual result.
  End If
End Sub

Console Streams

The console streams are the input and output (normally text) streams that are used to communicate with the outside world.  In most cases this is the console window that is executing your program.

There is one for input, and two for output.  The reason for two outputs is primarily to permit the programs normal output to be redirected to a file or piped to the input of a follow-on program, while allowing errors to still be reported back to the console.

Performing the console I/O requires the use of several API calls.  One alternative is to use capabilities provided by the FileSystemObject ActiveX object, that is part of scrrun.dll, the Microsoft Scripting Runtime.

modConIO.bas

I have written a simple VB code module called modConIO that defines many of the API calls, constants, and so forth that will make simple synchronous console I/O easier.  This module makes several Public items available:

Public Const ERROR_SUCCESS As Long = 0
Public Const ERROR_HANDLE_EOF As Long = 38

Public Declare Sub Sleep Lib "kernel32" _
                            (ByVal dwMilliseconds As Long)
Public Declare Sub ExitProcess Lib "kernel32" _
                            (ByVal uExitCode As Long)

Public hStdIn As Long
Public hStdOut As Long
Public hStdErr As Long

Public Sub SetHandles()
Public Function ConWrite    (ByVal Handle As Long, _
                             ByVal Text As String, _
                             ByRef BytesWritten As Long) As Long
Public Function ConWriteLine(ByVal Handle As Long, _
                             ByVal Text As String, _
                             ByRef BytesWritten As Long) As Long
Public Function ConRead     (ByRef Buffer As String, _
                             ByVal Bytes As Long) As Long
Public Function ConReadLine (ByRef Buffer As String, _
                             ByVal Bytes As Long) As Long

The Public routines here are wrappers for the raw I/O APIs.

You call SetHandles() once before doing any console I/O.  Use the handle variables in your calls to ConWrite() and ConWriteLine().  Calls to the read functions use hStdIn internally.

The four I/O functions here return a success or error result value.

The BytesWritten parameters return the number of bytes actually written, though for console I/O these results can typically be ignored.

The Bytes parameters are used to request a certain number of bytes of input, though typically you ask for more than you actually get back.  ConRead() will return once an end-of-line is encountered or when Bytes of input have been received.  ConReadLine() will request input in Bytes-sized chunks until it encounters an end-of-line.

The API calls for Sleep() and ExitProcess() are Public in this module for convenience.  Sleep() gets used by ConReadLine() too, but ExitProcess() is meant to be called from your own code.

You can download this module at modConIO.

Simple Examples

Here are a couple of very simple programs, and their operation should be quite clear:

TinyConversation

Option Explicit

Sub Main()
  Dim lngWritten As Long
  Dim strWord As String

  SetHandles
  ConWriteLine hStdOut, "Hello!", lngWritten
  ConWriteLine hStdOut, "What's the word?", lngWritten
  ConReadLine strWord, 80
  ConWriteLine hStdOut, "So, the word is " & strWord, lngWritten
End Sub

That's a pretty simple program, I'm sure you'll admit.

TinyEOF

Option Explicit

Sub Main()
  Dim lngCount As Long
  Dim lngWritten As Long
  Dim strLine As String

  SetHandles
  lngCount = 0
  Do While ConReadLine(strLine, 80) <> ERROR_HANDLE_EOF
    lngCount = lngCount + 1
  Loop
  ConWriteLine hStdOut, "I saw " & CStr(lngCount) & " lines.", lngWritten
End Sub

Another incredibly trivial program.  This one reads lines of input until EOF.  To signal EOF from the console the easiest method is to simply type CTRL-Z and press ENTER.

One reason for this example is to demonstrate redirection.  I created a small junk.txt file and then entered:

TinyEOF<junk.txt

This produced a simple line count as a result, written back to the console.

Compiling a Console Program

This part isn't too bad.  Even easier if you use one of the 3rd party VB IDE add-ins that "hooks" the compilation process to keep VB from compiling your project as a Windows subsystem program.

Most of us don't have one of these though.  So what do we do?

Well, the simplest thing is to compile your program as an EXE via the usual File|Make menu selection in the VB IDE.  Then you need to open a command prompt window, set the approriate PATHs to reach LINK.EXE and your compiled EXE, and then enter:

LINK /EDIT /SUBSYSTEM:CONSOLE MYNEW.EXE

I got tired of doing this, especially since I usually ended up typing a long pathname to reach either LINK.EXE, my compiled VB program, or both.

LinkConsole.vbs

Instead I wrote a small VBScript that I just copy into the project folder of any VB console project I work on.

Option Explicit
'LinkConsole.vbs
'
'This is a WSH script used to make it easier to edit
'a compiled VB6 EXE using LINK.EXE to create a console
'mode program.
'
'Drag the EXE's icon onto the icon for this file, or
'execute it from a command prompt as in:
'
'        LinkConsole.vbs 
'
'Be sure to set up strLINK to match your VB6 installation.

Dim strLINK, strEXE, WSHShell

strLINK = _
  """C:\Program Files\Microsoft Visual Studio\VB98\LINK.EXE"""
strEXE = """" & WScript.Arguments(0) & """"
Set WSHShell = CreateObject("WScript.Shell")

WSHShell.Run _
  strLINK & " /EDIT /SUBSYSTEM:CONSOLE " & strEXE

Set WSHShell = Nothing
WScript.Echo "Complete!"

This can be run from the command line using CScript just fine.  I usually just drag and drop, to handle this job quickly.  I typically:

  1. Save my source in the IDE.
  2. "Make" (compile) the project to an EXE.
  3. Switch to an Explorer window I keep open to the project folder.
  4. Drag the new EXE's icon onto the LinkConsole.vbs icon.

Voila!  All ready to run.

Just don't forget to alter this script to point to the location where you keep your copy of the LINK.EXE utility.

You can download this script at LinkConsole.

Debugging Console Programs in the IDE

Lots of options here.  The simple ones include such things as making a form that simulates the console and detecting whether or not execution is stand-alone or via the IDE's Run option.

Other people get a little more tricky, and open a console window from inside of the program and direct their I/Os there when run via the IDE.

Karl Peterson offers an approach over at Karl E. Peterson's Classic VB Code.  Heck, Karl pretty much obsoletes most of what I've covered here with his downloadable goodies.

 

June 2005