By David Reid
Have you ever accidentally tried to execute
a program written for Microsoft Windows from the DOS command line?
If so, the program probably responded with the message
This program requires Microsoft Windows
In addition, you may have encountered other Windows programs that,
when you run them from the DOS command line, display the message
This program cannot be run in DOS mode
Finally, there are some Windows programs, such as the Microsoft Windows SETUP program, that you can execute under either DOS or Windows. However, like SETUP, these programs may have radically different appearances and capabilities depending on whether you run them from DOS or Windows.
In this article, we'll discuss Windows program stubs. Then,
we'll show you how to use the STUB statement in your program's
module-definition (DEF) file to control what happens when a user
runs your Windows programs from DOS.
As you know, Windows programs reside in files with the EXE extensionthe
same extension DOS uses for executable files. However, the mechanisms
by which Windows and DOS load and execute EXE files are radically
different. The problem is that DOS will load and attempt to execute
any file with the EXE extension, whether or not the data in the
file corresponds to meaningful CPU instructions. If you need proof
of this, create the bogus executable file CRASH.EXE by using the
statement
DIR > CRASH.EXE
Then (if you dare) instruct DOS to run the CRASH.EXE program. Doing so will almost guarantee to lock up your PC.
After you restart your computer, you might wonder how Windows
programs manage to avoid this pitfall. The first portion of any
Windows program actually contains a DOS programa stubright
where DOS expects it to be. Typically, stubs are very shortjust
long enough to display a message and terminate. When you tell
DOS to run the EXE file, the stub is the only executable code
that DOS seesthe Windows executable code resides safely
in the remainder of the file.
As you probably know, every Windows program you create requires a DEF file. The linker builds the final executable file based on the information you specify in this file. One of the items you can specify in a DEF file is the name of the DOS executable file you want your Windows program to use as the Windows loading program, or stub. Figure A shows SAMPLE.DEF, the DEF file for the Windows program SAMPLE.
Figure A - This is a typical Windows module-definition file.
NAME SAMPLE DESCRIPTION 'Sample Windows Program' EXETYPE WINDOWS STUB 'WINSTUB.EXE' CODE MOVEABLE DISCARDABLE DATA MOVEABLE MULTIPLE HEAPSIZE 1024 STACKSIZE 5120 EXPORTS WndProc @1 AboutDlg @2
To use the STUB statement, simply follow the STUB keyword with the name of a DOS executable file in single quotes. For example, the STUB statement in Figure A tells the linker to use the file WINSTUB.EXE as the DOS stub for the Windows program SAMPLE.
Incidentally, you can include drive and path specifiers in the stub's name if the EXE file resides outside of the current working directory. Furthermore, you can use only EXE files as stubsyou can't use COM files. Moreover, there's no restriction on the size of the stub other than the amount of memory DOS has available to run the stub program.
For instance, if you want to, you could specify DOS 5's
QBASIC.EXE program as the stub for a Windows program:
STUB 'C:\DOS\QBASIC.EXE'
The linker will copy all 248KB of QBASIC.EXE to the beginning
of the executable file. When you run the resulting EXE file from
DOS, you'll see the standard QBASIC interface on the screen.
If you omit the STUB statement from the DEF file, the linker uses a default stub that's only a few lines of assembler code. If you use Debug, CodeView, or some other debugger to examine a Windows program containing the default stub, you'll see the assembler code shown in Figure B starting at offset 0x40 in the executable file.
Figure B - This is the assembler code for the default Windows stub.
MOV DX,0010h PUSH CS POP DS MOV AH,09h INT 21h MOV AX,4C01h INT 21h NOP NOP DB 'This program must be run under Microsoft Windows.' DB 0Dh, 0Ah, '$'
The default stub, although very short, represents a complete DOS
program. It uses interrupt 0x21 function 0x09 to display the message
This program must be run under Microsoft Windows.
then terminates with the return value 1.
In contrast, when you use the STUB statement to tell the linker
to use an existing EXE file as the stub, the linker adds the entire
stub program to the beginning of the Windows executable file.
Needless to say, you'll normally want to use very small
stub programs to avoid unnecessarily increasing the size of your
Windows executable files.
In most cases, the default provides all the functionality your Windows programs need: It tells the user that the EXE file is a Windowsnot a DOSprogram. However, sometimes you may need a Windows program to perform some alternate processing task when you run it from DOS.
For instance, suppose your Windows program needs to add commands
to the AUTOEXEC.BAT, CONFIG.SYS, or WIN.INI file before it will
run properly. You could place the code that makes these changes
in a substitute stub, thus reducing the overhead of the Windows
portion of the program.
Another use of stubs is creating "self-starting" Windows programs. In other words, instead of having WINSTUB.EXE chastise the user for attempting to run the program under DOS, why not simply start Windows for the user, then automatically run the program? After all, isn't obeying the user's command (running the program) more user-friendly than explaining why you can't obey the command?
Now let's build a simple stub that will make any Windows program self-starting. Inside this program, we'll begin by displaying a prompt to ask the user whether to run Windows automatically. If the user wants to launch Windows, we'll transfer all command-line arguments to a new command line, then use the system() function to pass this new command line to a secondary copy of the command processor, where we'll run Windows.
To begin, launch Borland C++ 4.0. When the main window of the Integrated Development Environment (IDE) appears, choose New from the File menu. In the editor window that appears, enter the code from Listing A.
Listing A: RUNME.C
#include <string.h> #include <process.h> #include <stdio.h> void _ExceptionInit(void) {} char CommandLine[128] = "WIN"; void main( int argc, char **argv ) { char c; int i; puts("Requires Windows. Run Windows now (Y or N)?"); c = getchar(); if((c == 'y') || (c == 'Y')) { for(i = 0; i < argc; i++ ) { strcat( CommandLine, " " ); strcat( CommandLine, argv[i] ); } system( CommandLine ); }
When you finish entering the code, choose Save from the File menu. In the Save File As dialog box, enter RUNME.C in the File Name entry field.
Next, right-click on the editor window and choose Edit Local Options from the pop-up menu. In the Options dialog box, expand the Linker topic, select the General sub-topic, and then deselect the Include Debug Information check box. Click OK to save this change.
When the editor window reappears, right-click on it again and choose Target Expert from the pop-up menu. In the TargetExpert dialog box, choose Application in the Target Type list box, choose DOS Standard in the Platform list box, choose Small from the Target Model combo box, and deselect all the Standard Library check boxes except Runtime. Click OK to save this change.
When you're ready to build the stub application, choose Build All from the Project menu. When the compiler finishes building the application, click OK. Then you can add it as a stub for any Windows application you're building.
Most of the logic in RUNME concerns building the new command line from the information in the argv array. Although Windows programs don't use command-line arguments nearly as often as DOS programs do, you should still pass them on to the Windows program if the user supplies them. To build the command line properly, you need to shift all command-line arguments one position to the right and add the command WIN to the beginning of the command line.
For example, suppose the name of your Windows program is TESTPROG
and the user types the following command at the DOS command line:
TESTPROG A: /S
To start Windows and run TESTPROG without losing the two command-line
arguments, the command line you pass to system() must
look like this:
WIN TESTPROG A: /S
Fortunately, argv[0] always contains the full path specification
for the current program's EXE file. Therefore, you don't
need to hardcode the program's name anywhere in RUNME.C.
Once you've compiled RUNME and generated the file RUNME.EXE,
you can use the statement
STUB 'RUNME.EXE'
in the DEF file of any Windows program.
Also, we included additional logic in the RUNME program to prompt
the user with
Requires Windows. Run Windows now (Y or N)?
This extra step will probably please many users, since it takes
anywhere from 10 to 30 seconds (depending on the speed of your
machine) to get back to DOS if it turns out the user didn't
really want to start Windows.
To test RUNME, let's build a minimal Windows application and use RUNME.EXE as the stub program. To begin, close the RUNME.C editor window by double-clicking on its System menu icon.
Now, choose New Project... from the Project menu. In the New Project dialog box, enter \RUNME\RUNME.IDE in the Project Path And Name entry field, choose EasyWin from the Target Type list box, and select the Class Library check box from the Standard Libraries group.
To change the initial files that the IDE adds to the project, click the Advanced button. In the Advanced Options dialog box, select the CPP Node radio button and the DEF check box, and then click OK. When the New Project window reappears, click OK.
Double-click on the name runme [.cpp] in the Project
window. When the editing window for this file appears, enter
#include <iostream.h> void main() { cout << "RUNME Works!";}
When you finish, choose Save from the File menu.
Next, double-click on the name runme [.def] in the Project
window. When the editing window for this file appears, enter
EXETYPE WINDOWS STUB 'RUNME.EXE' CODE PRELOAD MOVEABLE DISCARDABLE DATA PRELOAD MOVEABLE DISCARDABLE HEAPSIZE 4096 STACKSIZE 8192
Choose Save from the File menu, and then double-click on this editor window's System menu icon to close it. To build the application, choose Build All from the Project menu.
When the IDE finishes building the application, click OK, choose Exit from the File menu to quit the IDE, and then exit Windows. When the DOS prompt reappears, make the RUNME program's directory the current directory, and enter RUNME.
When the program asks if you want to run Windows now, enter Y.
After loading Windows, you'll see the RUNME application's
screen appear with the text RUNME Works! in the upper-left corner.
Double-click on this window's System menu icon to close
it.
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.