Al is the author of several books, including DOS 6: A Developer's Guide (M&T Books,1993) and Commando Windows Programming (forthcoming from Addison-Wesley). He can be reached at 310 Ivy Glen Court, League City, TX 77573 or on CompuServe at 72010,3574.
If you have a DOS program, chances are you've thought of porting it to Windows. However, rewriting your entire application to work with Windows can be difficult and expensive. To ease that burden, Microsoft's Visual C++ provides QuickWin, a library that allows you to port many text- and graphics-based DOS programs to Windows. But, it's not a panacea--some programs require major changes to work with QuickWin. To examine the QuickWin library, I decided to dust off Turtle, a Turtle-graphics program I developed in a previous DDJ article; see "Programming with Phar Lap's 286|DOS-
Extender," (DDJ February, 1992). Turtle uses Microsoft C with Phar Lap's 286|DOS Extender. Since QuickWin allows programs to access more than one Mbyte (thanks to Windows), it shouldn't make much difference that the program originally used a DOS extender. However, Turtle uses some techniques that won't work with QuickWin. In this article, I'll discuss what it took to port Turtle (a moderately complicated Microsoft C program) to QuickWin.
In its simplest form, QuickWin creates a single text window inside a multiple-document interface (MDI) frame window. This window is a surrogate for the DOS console device. All output to stdout and stderr appears in the window. Keyboard input appears as data in the stdin stream. When your program exits, QuickWin leaves the window on the screen until the user closes it.
Simple programs that use stdin and stdout for I/O work well with QuickWin. However, there are some restrictions:
Table 1 shows QuickWin's enhanced calls (all are defined in STDIO.H). Some calls pertain to the QuickWin environment. The _wabout() function, for example, sets the text that appears in the about box. The _wsetexit() function controls how QuickWin responds when the program exits. By default, QuickWin retains the program's windows until the user closes them explicitly. With _wsetexit() you can override this behavior so that your windows close upon exit.
Other QuickWin calls allow you to create new text and graphic windows. You can also change the size, position, and text capacity of any text window. If you use _wopen(), QuickWin returns a file handle that corresponds to a new text window. You pass this handle to other calls (like _wsetsize()) to refer to the window. You use file handles with calls like read() and write(), not calls like fprintf(), which take a stream pointer. To get a stream pointer for a window, use _fwopen() instead of _wopen(). You can get a file handle from a stream pointer using _fileno().
Both _wopen() and _wfopen() take three arguments. The first two are structures that determine the window's title, text capacity, size, and position. The third argument determines the access mode to the pseudofile.
By default, a QuickWin window can hold 2048 characters (a little more than a 25x80 display). However, you can change this limit when you create a window or by using _wsetscreenbuf(). You can set the text capacity by using a specific number, or you can use one of two special constants: _WINBUFDEF or _WINBUFINF. The _WINBUFDEF constant sets the default buffer size (2048); _WINBUFINF sets no limit on the window's text capacity. If the window contains more text than it can display, QuickWin automatically provides and manages scroll bars.
You can close a window using _wclose(). If you pass _WINNOPERSIST as an argument, QuickWin removes the window from view when you close it. If you want the window to remain, you can pass _WINPERSIST, instead. If you want your program to have a custom look, you might want to close the stdout window by specifying _wclose
(_fileno(stdout),_WINNOPERSIST).
Graphics windows are not quite as sophisticated as text windows. You can open a new graphics window by calling _wgopen(). This call returns a file handle. You then have to activate the new window with _wgsetactive(). You can close a graphics window with _wgclose(). Closing a graphics window always causes it to disappear. Like text windows, you can close the default graphics window so that you can open a new one with your own title by specifying _wgclose(_wgetactive()). You can't control the size or position of graphics windows. You can set any legal video mode using the standard _setvideomode() call. However, the window may be larger than the virtual graphics screen, and this can be disconcerting. For example, if you fill a 640x480 graphics mode with a color, it will look odd if the window is 700x500 pixels. The extra pixels form a border that doesn't fill.
Turtle is a graphics language similar in spirit to Logo. The original Turtle program only supports the VGA's 320x200x256 mode. Since it uses Phar Lap's 286|DOS Extender, Turtle takes advantage of DLLs to provide an extensible command set. Although most Turtle commands are built in, you can add additional commands with a DLL. The default Save and Load commands are in a DLL.
Turtle's commands are straightforward. You should be able to figure out its operation by referring to the online help. If you want more details about the original Turtle program, see the article in the February 1992 issue. To avoid confusion, I'll refer to the original version as Turtle 1.0 and the new Windows version as Turtle 2.0. The source code for the QuickWin version of Turtle is available electronically; see "Availability," on page 5. The source files are the same as those for Turtle 1.0 (also available electronically), although there are many significant changes.
Porting Turtle to QuickWin presents several immediate challenges. The original program switches between a text and graphics screen. To do this, Turtle 1.0 directly accesses video memory. It also reads and sets the text screen's cursor position. None of these are possible under QuickWin. But thanks to Windows, sometimes less is more. Since QuickWin uses different windows for graphics and text, the new Turtle adopts this model for output. All the cursor-positioning code and most of the direct screen accesses vanish with separate windows. The gotext() function, which switches to text mode, becomes an empty stub; gograph() initializes the graphics window the first time you call it. Subsequent calls to it do nothing. The Show command becomes unnecessary with this scheme, so Turtle no longer supports it. The only other direct screen accesses occur when you save or load the screen to a file or buffer. Since these operate on the graphics window, we can use Microsoft's standard _putimage() and _getimage() calls to effectively read and write the graphics window.
Huge pointers. While _putimage() and _getimage() will work to read and write the graphics screen, it presents a problem for the new version of Turtle. Turtle 1.0 operates on a 320x200x256 screen. Therefore, it always deals with blocks of memory exactly 64,000 bytes long. The Microsoft calls require more than 64K to store an entire 320x200x256 screen, due to some overhead in the image (more about that later). Although QuickWin programs can malloc() large amounts of memory, they still suffer from the 64K-per-object limit. Therefore, the new Turtle has to resort to huge pointers to deal with screen buffers. You can learn the exact size of a screen buffer from the _imagesize() function.
Help windows. Turtle 1.0's help system uses getch() to pause between help screens. QuickWin, of course, doesn't support any console I/O calls. Again, less is more--the new Turtle opens a separate window to contain the help information. Turtle sets the help window's buffer size to _WINBUFINF, so the window will retain all text sent to it. You can use the window's scroll bars to browse the help information. Subsequent help commands simply bring the existing help window to the foreground. Turtle 2.0 doesn't need getch(). That's good, because I couldn't find a direct way to replace getch() or kbhit().
The help system causes each command to print its own help to the stdout stream. Therefore Turtle needs a way to redirect stdout to the help window. Ordinarily, you would use the dup() and dup2() calls to redirect a stream. However, you can only pass QuickWin's special file pointers to a few functions. Unfortunately, dup() and dup2() are not among them. Turtle resorts to the method shown in Figure 1 to change the stdout stream.
DOS shell. Another problem is that the original Turtle could run a DOS shell and a text editor, and display a DOS directory. QuickWin doesn't allow any of these operations. However, under Windows, these are not very important. You can always switch to a DOS window or an editor by using Windows. Turtle 2.0 simply deletes these commands.
Interrupting execution. Turtle can execute script files. While a script file is running under Turtle 1.0, you can press Control-Break to interrupt execution. Under QuickWin, you can't catch Control-Break. If Turtle gets stuck in an endless loop, you're simply out of luck. However, the new Turtle will break out of all running scripts if you enter a Q (upper or lower case) while Turtle is waiting for input (the %i special variable).
DLLs. Even though Windows supports DLLs, QuickWin doesn't allow you to call the Windows API. Therefore, I didn't even attempt to port Turtle's DLL system to QuickWin. Since the system depends on some Phar Lap-specific functions, it probably would have been difficult to port anyway. The new Save and Load commands, of course, have to move to TCMDS.C.
The new Turtle program sports several minor changes. The Delay command, for example, couldn't use the special Phar Lap interrupt calls. Some variables and functions that Turtle no longer needs were deleted. However, some things from the old program still remain, but are unused. For example, Turtle 1.0 uses large model, but Windows can't load multiple copies of programs that use large model. In an ordinary Windows application, you could prevent multiple copies from running, but not in QuickWin. It was a simple matter to change Turtle to use small model. Only the screen buffers need huge pointers, anyway. This prevents the new Turtle from using fread() and fwrite() inside the Save and Load commands, however.
Some changes were not strictly necessary, but were worth the small effort required. By default, when your program exits, QuickWin retains your windows until the user closes them. By calling _wsetexit(_WINEXITNOPERSIST), QuickWin will close your windows when you exit, which is a more natural method for Turtle.
By default, while Turtle is running, other Windows programs can't execute. (Remember, Windows multitasking is cooperative.) To make Turtle more polite, the XCI module calls _wyield() periodically while processing scripts. This allows other programs to run while Turtle is performing lengthy operations.
Since Turtle can no longer directly access graphics memory, it doesn't depend on the 320x200x256 mode anymore. As an experiment, I added a mode command that allows you to try different graphics modes. However, Turtle always assumes 256-color mode, regardless of the mode you are using.
Turtle certainly looks good as a Windows application; see Figure 2. By using the standard QuickWin menus, you can copy text or graphics to the clipboard, and manage the child windows as you would expect in a MDI application. Of course, the help menu only explains QuickWin's operation--you can't customize it. Also, Microsoft doesn't document this, but under QuickWin, _putimage() and _getimage() operate on standard Windows bitmaps. That means that Turtle can now read and write BMP files! This was certainly an unexpected bonus; however, QuickWin may not set the color palette correctly for some files.
Prior to Visual C++, QuickWin did not support graphics. While the graphics support is welcome, it is also a major weak point in the package. When a window is larger than the virtual graphics screen it represents, the effect is disconcerting. The user (but not the program) can tell QuickWin to scale the graphic to fill the window, but this slows processing and can distort the image. You can't control the placement or size of graphics windows from within your program.
Considering its complexity, Turtle was surprisingly easy to port to QuickWin. Of course, tweaking the design--creating separate text and graphics windows, for example--helped ease the transition. These changes improved Turtle's look and feel, too. If Turtle had originally been a true DOS program instead of a DOS-extended program, the DLL use would not have been a problem. Also, the break handling was a disappointment. The workaround--detecting the Q key on input--isn't a very good solution. If your program sticks in a loop that doesn't do input, you won't have any recourse but to close the application.
Turtle is probably about as complex a program as you would want to port to QuickWin. Even though the port was successful, there were a few quirks. If you just want to get your DOS program over to Windows as soon as possible, QuickWin works well. However, if you plan on continuing development of your program under Windows, QuickWin is essentially a dead end. Although you can make certain improvements (separate output windows, for example) to a QuickWin program, you can't call the Windows API. Once you move your program to QuickWin, you can't make many Windows-oriented improvements.
By using a custom DEF file, you can bind a DOS version of your program with the QuickWin version. Then you will have a single executable (made from the same source code) that runs under DOS or Windows. Consult the Microsoft Visual C++ documentation for more details about binding DOS programs to Windows applications.
Certainly QuickWin is not a long-term answer to developing Windows applications. Still, it will make your DOS program behave better under Windows (especially in standard mode) and give it a better look and feel. Your QuickWin program will have access to more memory than a DOS program. Nevertheless, QuickWin is no DOS extender (although Windows actually is). QuickWin is probably too limited to port most real DOS applications to Windows, but it's useful for writing quick programs that you would ordinarily write for DOS.
Microsoft Visual C++ Version 1.0 Reference Manual. Microsoft Corp., 1993.
Williams, Al. "Programming with Phar Lap's 286|DOS-Extender." Dr. Dobb's Journal (February, 1992).
Williams, Al. Commando Windows Programming. Reading, MA: Addison-Wesley, 1993.
Function Description
_wopen() Opens text window; returns file handle.
_fwopen() Opens text window; returns file pointer.
_wgopen()* Opens graphics window.
_wclose() Closes text window.
_wgclose()* Close graphics window.
_wsetexit() Sets exit behavior.
_wgetexit() Gets exit behavior.
_wsetsize() Sets text window's position and size.
_wgetsize() Gets window's position and size.
_wsetscreenbuf() Sets text window's screen-buffer size.
_wgetscreenbuf() Gets text window's screen-buffer size.
_wsetfocus() Makes window active.
_wgetfocus() Gets current active window.
_wgsetactive()* Sets active graphics window.
_wggetactive()* Gets active graphics window.
_wmenuclick() Simulates menu action.
_wyield() Yields processing time to other applications.
_wabout() Sets custom message for About box.
_inchar()* Reads a character in a graphics window.
*Graphics-only functions
FILE old_stdout; /* Not a pointer */
FILE *helpwin; /* set by fwopen() somewhere else */
old_stdout=*stdout; /* save stdout */
*stdout=*helpwin;
printf("This goes to help window");
/* go back to old stdout */
*stdout=old_stdout;
Copyright © 1993, Dr. Dobb's Journal