In the March 1995 issue of Borland
C++ Developer's Journal, we showed how you could
create a simple Windows program stub (Windows programming - Creating a Windows program stub). Every Windows application contains a small
section of executable code that the application will execute if
you try to run it from DOS instead of running it from Windows.
This program, which appears inside the application and is called
a Windows stub, is responsible for displaying the
This program requires Microsoft Windows
message at the DOS command prompt.
In the March article, we showed you how to create a Windows stub
that automatically launched Windows and the application when you
tried to run the application from DOS. Unfortunately, the version
we showed you was a little larger than necessary. In this article,
we'll present an improved version of RUNME, the Windows
stub that launches Windows and your application automatically.
Although RUNME works, it's very inefficient. For starters, the executable file RUNME.EXE is over 9,600 bytes long, which means that your Windows program will grow by at least that much if you use this program as a Windows stub. In addition, the system() function in RUNME invokes a secondary copy of COMMAND.COM, which reduces the amount of memory available to Windows by a few thousand bytes more. By manually optimizing RUNME for size and using a COMMAND.COM feature that's undocumented, however, we can eliminate both of these problems.
The primary reason RUNME is larger than it needs to be is that it calls several functions from the standard libraries, as you can see by examining the code in Figure A. If you eliminate the calls to puts(), getchar(), strcat(), and system(), the program won't need to make any direct calls to other library functions.
Figure A - We've reproduced the source code for RUNME.C here for your reference.
#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 ); } }
Eliminating the calls to puts() and getchar() is fairly painless if you're willing to give up the "Run Windows now (Y or N)?" prompt and run Windows every time. Likewise, you can eliminate the strcat() function calls by creating a simple loop to copy the command-line arguments to the CommandLine buffer.
Unfortunately, eliminating the system() function call
isn't quite so simple. To simulate the behavior of the
system() function, you'll need to use the undocumented
0x2e interruptthe "back door" into the primary
COMMAND.COM processor.
Although Microsoft considers the 0x2e interrupt "reserved" and avoids any discussion of it, other sources, such as Undocumented DOS (published by Addison Wesley), explain this interrupt's purpose and usage. Since using interrupt 0x2e in our Windows stub can save memory, we'll examine it briefly.
Interrupt 0x2e allows you to directly pass commands that you normally use with the system() function or at the DOS command line to the primary copy of the command processor, COMMAND.COM. In addition, interrupt 0x2e can perform one task the system() function can't: It lets you change environment variables in the primary command processor's environment block.
To use this interrupt, you put the far address of the command-line string in the register pair DS:SI and then issue the 0x2e interrupt. However, you need to remember two additionaland very importantdetails. First, the command-line string must be in a special, non-ASCII format. Second, the interrupt call destroys the contents of all the CPU registers except CS and IP.
The command-line string must be a counted string (the first byte contains the length of the string) and must end with a carriage return. You can't pass a standard, null-terminated C/C++ string to this interrupt and expect pleasant results.
Since the interrupt destroys most of the registers, you'll want to save the SS and SP registers before calling the interrupt. Upon returning from the interrupt, you'll need to restore these registers using the previous values.
Making these changes eliminates nearly 2KB from the stub program's
final size. In addition, if you don't call the system()
function (and, therefore, don't create a secondary copy
of the command processor) you save approximately another 2KB of
memory at runtime.
To create the smaller version of RUNME, launch Borland C++ 4.0. When the main window of the IDE appears, choose New from the File menu. In the editing window that appears, enter the code from Listing A.
Listing A: RUNME2.C
void _ExceptionInit(void) {} char CommandLine[132] = "#WIN "; unsigned _cs * saveSS; unsigned _cs * saveSP; void main( int argc, char **argv ) { int argumentIndex, destPosition = 5; for(argumentIndex = 0; argumentIndex < argc; argumentIndex++ ) { while ( argv[0][destPosition - 5] ) CommandLine[destPosition] = argv[0][destPosition++ - 5]; CommandLine[destPosition++] = 0x20; } CommandLine[--destPosition = '\r'; CommandLine[0] = (char)--destPosition; asm { mov si, offset CommandLine push bp push ds mov cs:saveSS, ss mov cs:saveSP, sp int 0x2e mov ss, cs:saveSS mov sp, cs:saveSP pop ds pop bp } }
When you finish entering the code, choose Save from the File menu. In the Save File As dialog box, enter RUNME2.C in the Path And File Name entry field.
As with RUNME.C, right-click on the editing window, and choose Edit Local Options from the pop-up menu. In the Options dialog box, expand the Linker topic, select the General subtopic, and then deselect the Include Debug Information check box. Click OK to save this change.
When the editing window reappears, right-click 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 smaller stub application,
choose Build All from the Project menu. When the compiler finishes
building the application, you can make this program a stub program,
using the procedure we described in the previous section. Just
as with the RUNME program, you can use RUNME2 with any Windows
program by including the statement
STUB 'RUNME2.EXE'
in the program's module-definition file.
The default Windows stub does what you expect: It tells the user
that the EXE file is a Windows application. However, if you want
to automatically launch Windows and the application when a user
tries to run the program from DOS, you can use the RUNME2 program
we've shown here as your Windows stub.
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.