1. Computing
VB 6 and DOS
Using VB 6 with DOS requires some new thinking
Solution

A recent programming job required a DOS solution to be programmed using VB 6.

You remember DOS? ... The operating system responsible for the PC revolution? A DOS environment is still the best way to interface some systems. (The contract specifications required an interface to an all-Java environment.) But VB 6 assumes that it is in a Windows environment. In fact, it's actually easier to create 'DOS' based systems in VB.NET than using VB 6. But VB 6 is capable of creating DOS systems. Here's how it's done!


When you start a new project using VB.NET, one of the choices presented to you is Console Application. In fact, many of the best books about VB.NET and .NET foundation make the console interface to VB.NET their main focus because Windows isn't really assumed anymore in the object oriented .NET world. Windows is treated as just one of the supported .NET environments. (This is a significant break with the past for Microsoft! Look for more and more software from Microsoft that, well, "runs in Windows" but isn't necessarily "Windows" anymore.) A good example is Jesse Liberty's groundbreaking introduction to VB.NET, Learning Visual Basic .NET.

Console in VB.NET

When you start a new project using VB 6, however, the template for a console app is missing! The "Standard EXE" starts with a Windows form, the "ActiveX EXE" and "ActiveX DLL" are classes.

So you try the Help system. Microsoft continually updates Help so your version might be different than mine (I have October 2001, the last one provided by Microsoft for Visual Studio 6), but the Visual Basic Documentation in my version has no entries for DOS or Console. In fact, I couldn't find anything in Help spelling out the steps.

So how do you just create a plain old app that runs in DOS?

Creating something that 'looks like' DOS is pretty simple. One way to define a DOS app is just to assume that it's a VB 6 Windows App that lacks a form. To create this type of DOS app, follow these steps:

  1. Create a Standard EXE Project
  2. Right click the project in Project Explorer and select Add and then Module from the context menus that pop up
  3. When a new module has been added, select the form that was added automatically by the VB 6 template, right click, and select Remove Form1
VB 6 Console Project

Ordinarily, your project will automatically start by displaying Form1. (VB 6 coders know that the initial Form Load event is a good place to put some types of startup code.) But when you delete Form1 from the project, Visual Studio defaults to Sub Main() instead. So you should also start the module that was added with a Sub Main() subroutine. You create an EXE the same way using the File menu.

Many DOS programs require parameter input on the command line when they run. But how do you emulate that when you're testing? To solve this problem, simply right click the Project and select Properties. Then select the Make tab and enter whatever you want the program to be able to read input a command line parameter. The illustration shows both running as an EXE and the dialog to input the parameter for testing.

Command Parameter

But now things get a bit more fun!

Keep in mind that what you have so far is really a Windows program, not actually DOS as we once knew it. 'Real' DOS was 16 bit software. The new Windows console is a 32 bit Windows session. It's mainly just a different design for a standard window. So popping open a MsgBox or another form from your code isn't a problem.

But if you expect this app to do what 'real' DOS would do, you're in for a surprise. For example, how do you send things to the DOS StdOut or read things from DOS StdIn (this is what you do if you "redirect" something from one DOS command to another as in the command DIR > DirList.txt to create a directory list in the file DirList.txt.)

Since the command window in 32-bit Windows application is not actually DOS, the traditional DOS interfaces aren't available. A VB 6 console application "detaches" from the VB 6 session that launched it, and without some extra coding, you can't write to the DOS window or accept input from the DOS window in your VB 6 program.

In fact, just defining what "DOS" really is in an XP world can become really confusing. In Windows 98 and earlier, DOS was started using the executable file COMMAND.COM. In Windows NT and later, it was changed to CMD.EXE. You can read a great page discussing this issue at What Do You Mean, "DOS"?. If you search the web for information about this, you will find lots of pages that talk about "DOS Mode". (Hint: There isn't one for Windows 2000, NT and XP.) You can also find lots of advice about how "Windows" programs "won't run in DOS" because they have to be "converted" using the VB Link command or some other Microsoft utility. (Hint: They run just fine in a Win32 "command prompt" window.) So there is a lot of, well ... not wrong but misleading information out there waiting to trip you up. Before you decide to code up a routine in DOS and VB 6, thinking it will be easier than a Windows app, think again.

StdIn and StdOut

Many DOS "command" functions require calls to Win32 API interfaces to make them work at all. The example used earlier, using StdIn and StdOut, requires both a knowledge of how VB 6 creates a new Windows session for the DOS window and the use of Win32 API's. Because it's quite long, the complete text of the example program that accepts a selection from a Win32 console window and displays the name of the computer is at the end of this article. If you "don't do" Win32 API programming, you may have difficulty with this example. Try this article to learn about Win32 API programming.

The program starts by creating a Win32 console window to work in and ends by destroying it again. These are the functions that do the trick. Check the program source at the end of the article to see how they're used.

Private Declare Function AllocConsole _
    Lib "kernel32" () _
    As Long
Private Declare Function FreeConsole _
    Lib "kernel32" () _
    As Long

To write to StdOut, just use the WriteConsole function and to read from StdIn, use the ReadConsole function.

Private Declare Function WriteConsole _
    Lib "kernel32" Alias "WriteConsoleA" ( _
    ByVal hConsoleOutput As Long, _
    ByVal lpBuffer As Any, _
    ByVal nNumberOfCharsToWrite As Long, _
    lpNumberOfCharsWritten As Long, _
    lpReserved As Any) _
    As Long
Private Declare Function ReadConsole _
    Lib "kernel32" Alias "ReadConsoleA" ( _
    ByVal hConsoleInput As Long, _
    ByVal lpBuffer As String, _
    ByVal nNumberOfCharsToRead As Long, _
    lpNumberOfCharsRead As Long, _
    lpReserved As Any) _
    As Long

Another way to simply execute programs as you might in DOS is to use the Shell function. An example of something you might want to do would be to call the console command ping.exe to see if a web URL is active. In this case, another Win32 API call, WaitForSingleObject is used because Ping operates asynchronously. We don't want to close the session until Ping completes. Here's how that might be done.

Shelling to Ping
Private Declare Function OpenProcess _
    Lib "kernel32" ( _
    ByVal dwDesiredAccess As Long, _
    ByVal bInheritHandle As Long, _
    ByVal dwProcessId As Long) _
    As Long
Private Declare Function WaitForSingleObject _
    Lib "kernel32" ( _
    ByVal hHandle As Long, _
    ByVal dwMilliseconds As Long) _
    As Long
Private Declare Function CloseHandle _
    Lib "kernel32" ( _
    ByVal hObject As Long) _
    As Long
Private Const SYNCHRONIZE = &H100000
Private Const INFINITE = &HFFFF

Sub Main()
    ShellAndWait ("C:\WINDOWS\system32\ping.EXE www.google.com")
End Sub

Private Sub ShellAndWait(CommandLine As String)
    Dim ShellId As Long
    Dim ShellHandle As Long
    ShellId = Shell(CommandLine, vbNormalFocus)
    ShellHandle = OpenProcess(SYNCHRONIZE, 0, ShellId)
    If ShellHandle <> 0 Then
        WaitForSingleObject ShellHandle, INFINITE
        CloseHandle ShellHandle
    End If
End Sub

To get the most recent version of the Shell and common controls DLLs, download and install Internet Explorer.

The FileSystemObject object is another tool that is very useful in scripts. The FSO object model is contained in a type library called Scripting, which is located in the file Scrrun.Dll. Check Microsoft Scripting Runtime in the References dialog to add it to your program. You can then use the Object Browser to view its objects, collections, properties, methods, and events, as well as its constants.

The bottom line is that DOS and VB 6 is a "mixed bag" at best. The way to survive is to hunt around in a whole variety of tools and objects until you (finally!!!) find the one that will work for you.


'
' Writing and Reading from the Win32 Console
'
Option Explicit
Private Declare Function AllocConsole _
    Lib "kernel32" () _
    As Long
Private Declare Function FreeConsole _
    Lib "kernel32" () _
    As Long
Private Declare Function GetStdHandle _
    Lib "kernel32" ( _
    ByVal nStdHandle As Long) _
    As Long
Private Declare Function ReadConsole _
    Lib "kernel32" Alias "ReadConsoleA" ( _
    ByVal hConsoleInput As Long, _
    ByVal lpBuffer As String, _
    ByVal nNumberOfCharsToRead As Long, _
    lpNumberOfCharsRead As Long, _
    lpReserved As Any) _
    As Long
Private Declare Function SetConsoleMode _
    Lib "kernel32" ( _
    ByVal hConsoleOutput As Long, _
    dwMode As Long) _
    As Long
Private Declare Function SetConsoleTextAttribute _
    Lib "kernel32" ( _
    ByVal hConsoleOutput As Long, _
    ByVal wAttributes As Long) _
    As Long
Private Declare Function SetConsoleTitle _
    Lib "kernel32" Alias "SetConsoleTitleA" ( _
    ByVal lpConsoleTitle As String) _
    As Long
Private Declare Function WriteConsole _
    Lib "kernel32" Alias "WriteConsoleA" ( _
    ByVal hConsoleOutput As Long, _
    ByVal lpBuffer As Any, _
    ByVal nNumberOfCharsToWrite As Long, _
    lpNumberOfCharsWritten As Long, _
    lpReserved As Any) _
    As Long
'Computer System Information
Private Declare Function GetComputerName _
    Lib "kernel32" Alias "GetComputerNameA" ( _
    ByVal lpBuffer As String, nSize As Long) _
    As Long

Private Const STD_INPUT_HANDLE = -10&
Private Const STD_OUTPUT_HANDLE = -11&
Private Const STD_ERROR_HANDLE = -12&

'SetConsoleTextAttribute color values
Private Const FOREGROUND_BLUE = &H1
Private Const FOREGROUND_GREEN = &H2
Private Const FOREGROUND_RED = &H4
Private Const FOREGROUND_INTENSITY = &H8
Private Const BACKGROUND_BLUE = &H10
Private Const BACKGROUND_GREEN = &H20
Private Const BACKGROUND_RED = &H40
Private Const BACKGROUND_INTENSITY = &H80
'SetConsoleMode (input)
Private Const ENABLE_LINE_INPUT = &H2
Private Const ENABLE_ECHO_INPUT = &H4
Private Const ENABLE_MOUSE_INPUT = &H10
Private Const ENABLE_PROCESSED_INPUT = &H1
Private Const ENABLE_WINDOW_INPUT = &H8
'SetConsoleMode (output)
Private Const ENABLE_PROCESSED_OUTPUT = &H1
Private Const ENABLE_WRAP_AT_EOL_OUTPUT = &H2
'GetComputerName
Public Const MAX_COMPUTERNAME_LENGTH = 31
' Global Variables
Private hConsoleIn As Long ' console input handle
Private hConsoleOut As Long ' console output handle
Private hConsoleErr As Long ' console error handle

Private Sub Main()
    Dim UserInput As String
    Dim CName As String
    'Create an instance of the Win32 console window
    AllocConsole
    SetConsoleTitle "About Visual Basic"
    'Get handles
    hConsoleIn = GetStdHandle(STD_INPUT_HANDLE)
    hConsoleOut = GetStdHandle(STD_OUTPUT_HANDLE)
    hConsoleErr = GetStdHandle(STD_ERROR_HANDLE)
    SetConsoleTextAttribute hConsoleOut, _
    FOREGROUND_RED _
    Or FOREGROUND_GREEN _
    Or FOREGROUND_BLUE _
    Or FOREGROUND_INTENSITY _
    Or BACKGROUND_BLUE
    ConsolePrint _
        "Writing and Reading from the Win32 console" _
        & vbCrLf
    ' Makes the text white
    SetConsoleTextAttribute hConsoleOut, _
    FOREGROUND_RED Or FOREGROUND_GREEN _
    Or FOREGROUND_BLUE
    Do While vbTrue
        ConsolePrint "Enter 1 for the computer name" _
            & vbCrLf & "Enter 2 to Quit-> "
        UserInput = ConsoleRead()
        Select Case UserInput
            Case 1
                CName = ReadCName()
                ConsolePrint CName & vbCrLf
            Case 2
                FreeConsole
                End
            Case Else
                ConsolePrint "Enter 1 or 2" & vbCrLf
        End Select
    Loop
End Sub
Private Sub ConsolePrint(MsgOut As String)
    WriteConsole _
    hConsoleOut, _
    MsgOut, _
    Len(MsgOut), _
    vbNull, _
    vbNull
End Sub

Private Function ConsoleRead() As String
    Dim MsgIn As String * 256
    Call ReadConsole( _
    hConsoleIn, _
    MsgIn, _
    Len(MsgIn), _
    vbNull, _
    vbNull)
    'Trim the NULL charactors and CRLF.
    ConsoleRead = _
        Left$(MsgIn, InStr(MsgIn, Chr$(0)) - 3)
End Function

Private Function ReadCName() As String
    Dim sz As Long
    Dim s As String
    sz = MAX_COMPUTERNAME_LENGTH + 1
    s = String$(sz, 0)
    Dim dl As Long
    dl = GetComputerName(s, sz)
    If dl <> 0 Then
        ReadCName = Mid(s, 1, sz)
    Else
        ReadCName = "Invalid Call"
    End If
End Function

©2014 About.com. All rights reserved.