This article contains the following executables: TURTLE.ARC
Al is a freelance writer and a consultant on the Space Station Freedom project. His book, DOS 5: A Developer's Guide, is available from M&T Books. He can be reached at 310 Ivy Glen Court, League City, TX 77573.
Traditional graphics programming techniques are difficult to master. Graphics languages such as LOGO, however, simplify many types of graphics programming. In fact, school children routinely use LOGO (developed at MIT) to draw fractals, recursive patterns, and other sophisticated drawings by controlling a graphics "turtle." I present in this article TURTLE, an extensible graphics language (written in Microsoft C 6.0) that's loosely based on LOGO. Because I wanted several graphics buffers available for animation purposes, I turned to the Phar Lap 286|DOS-Extender for access to more memory.
TURTLE challenges 286|DOS-Extender in several areas I consider crucial: the ease with which you can access memory with the extender; the ease with which you can manipulate physical addresses (for example, the screen buffer); the complexity of interrupt handling; the usefulness of the DLL tools; and how much performance penalty the extender incurs.
In addition to examining the Phar Lap 286|DOS-Extender, I'll also provide an in-depth look at TURTLE. For instance, in developing TURTLE, I created a general-purpose, extensible command interpreter based on DLLs. I'll also explore some useful protected-mode techniques. To follow along, you'll need Microsoft C 6.0, the DOS Extender, an 80286 (or better) PC, and a VGA graphics card.
Why a 286 DOS Extender?
Many common DOS extenders can only run on 80386/486 machines. Many PCs have 80286 processors, so this is a serious obstacle for developers who want to use a DOS extender. However, the Phar Lap tool is a 80286-based DOS extender (that also runs on the 80386 or 80486, of course). One interesting point to note about it is its split personality. Like all DOS extenders, it supports most DOS and BIOS calls directly--your programs still use interrupt 10H to write to the screen, or interrupt 21H to call DOS. It also supports calls that you need to interact with the DOS extender. The unusual part of the Phar Lap DOS Extender is that it also supports most of the basic OS/2 API calls.
The benefits are threefold. First, you can run the Microsoft compiler, linker, and debugger under the DOS Extender--they believe they are running under OS/2. Also, you can use almost any language that can create OS/2 applications to create programs. Finally, you can use the API calls yourself, if you wish. You may think you don't need to use OS/2 calls in your programs, but some of them are very useful. In particular, your programs can use Dynamic Link Libraries (DLLs), and threads (although their implementation is different from OS/2's).
TURTLE
I've kept TURTLE simple. It maintains a text screen for entering commands and a graphics screen. You can also enter commands at the top of the graphics screen, but any error messages will destroy part of the graphics screen. Table 1 shows a summary of TURTLE's operation.
Table 1: TURTLE command summary -- arguments are in italics, items in brackets ([]) are optional.
Command Meaning Operators ------------------------------------------------------------------------- setx x Set x-coordinate. (Highest Precedence) sety y Set y-coordinate. * Multiply setxy x y Set both coordinates. / Divide (integer) move dist Move turtle specified % Modulus (integer distance. remainder) home Move turtle to location + Addition (0,0). - Subtract turn heading Turn turtle to new & Logical and (like && heading. in C) show Show graphics screen | Logical or (like || and wait for a key. in C) show ON Turn graphics screen on. = Equality show OFF Turn graphics screen off. # Inequality pencolor color Set color. < Less than background color Set background color. > Greater than penup Make turtle move without <= Less than or equal to drawing. >= Greater than or pendown Make turtle draws as it equal to moves. (Lowest Precedence) clear Clear screen and home turtle. fill Fill region. Constants sto buffer Store screen to buffer Constants are hexadecimal (1-10). if you use the "Ox" prefix (that is, rcl buffer Recall buffer to screen. OxFF). All other prompt string Write string as prompt. constants are decimal. text string Write string on graphics screen. Variables textcolor color Set text color. The variables A-Z can hold set var value Set variable (A-Z) to integer values. You may value. use a variable any place do file Execute file -- resume you can use a constant. current file when it ends. Special variables %c Current color goto file Transfer control to file %h Current heading repeat n file Execute file n times. %i Numeric input from if expr THEN EXIT Exit current program user (no checking is file if expr is nonzero. done -- illegal input is if expr DO file Execute file if expr is returned as zero) nonzero. %r Random integer from if expr GOTO file Transfer to file if expr 0-32,767 is nonzero. %x Current x-coordinate help [command] Get help. %y Current y-coordinate push expr Push expr on stack. pop var Pop top of stack to var. Expression rules delay time Delay time/10 seconds. Expressions must not dos [command] Run DOS command or contain spaces. COMMAND.COM Parentheses can be used if command is absent. freely and override edit [file] Edit file with EDIT precedence. (DOS 5) or program Most expresssions can be named in TURTLEEDIT relative. For instance, environment variable. SETXY -10 +0 will move the cursor ten places to dir [arguments] Run DOS DIR command. the left. This is not cd directory Change directory. the same as SETXY 10 0, quit Exit TURTLE. which moves the cursor to location (10,0).
TURTLE works in the VGA's 320 x 200 X 256 mode. The top-left corner of the screen is at (0,0) and the bottom-right corner is (319,199). TURTLE allows coordinates to range from -999 to 999. Any points that fall outside the screen do not appear. Arguments to TURTLE commands can usually be absolute or relative. For example, SETXY 100 100 moves the current position (or turtle) to (100,100). However, SETXY -10 +0 moves the turtle ten units to the left (the X-axis), and doesn't move it at all on the Y-axis. TURTLE provides 26 global integer variables (A-Z). Any numeric argument can be an algebraic expression (see Table 1). Expressions must not contain spaces -- a space marks the start of a new argument.
To support animation, TURTLE supplies ten screen storage locations in memory. Each storage buffer requires 64,000 bytes. In addition, one storage location stores the current screen when TURTLE is in text mode. These data buffers alone require 704,000 bytes -- more memory than is available with unaugmented DOS.
Of course, a DOS extender isn't the only option available for buffers of this size. EMS, extended memory, XMS, or disk paging would have worked too. However, these would add considerable complexity to the application.
The Command Interpreter
At the heart of TURTLE is the extensible command interpreter, XCI (see Listings One and Two, page 94). This interpreter is generic -- any program could use it. It only supplies four basic commands: DO, HELP, LINK, and QUIT. An application that uses XCI (a client) can also enable a fifth command, GOTO.
The client can directly add commands to XCI's command table. In addition, the client or the user can add commands dynamically from a DLL to XCI. Figure 1 shows the format of an XCI command function. XCI.H (Listing One) defines the type XCICMD for these functions.
Figure 1: Prototype for XCI command function
int command (char *dll, char *startfile, int cases, void far *user, XCICMD( *userfunc) ()); where: dll is the name of a DLL to load. startfile is the name of a file to execute with the DO command. cases is 0 if upper- and lower-case commands are the same, nonzero if case is important. user is a pointer to a user-defined structure. userfunc is a pointer to a command function that will run when XCI begins and ends. The cmd argument to this function (see Table 2) will be 0 when XCI starts and nonzero when it ends.
XCI passes an integer command to an XCICMD function. If the command is zero, the function should perform its duty. If it is one, the function should print detailed help information. If the command is two, the function should print a one-line help message. To save space in the listings, TURTLE often uses the same message for long and short help. You can modify the help text to suit your preference, if you like.
The client can register a function that XCI will call before the program starts and as it is ending. This function can install commands and do other specific client processing. TURTLE uses the startup() function for this purpose. XCI calls the function with the cmd argument equal to 0 when the program starts, and equal to 1 when it ends.
The client specifies several parameters when it starts command(), XCI's main function. These parameters allow the client to link a DLL, execute a file of start-up commands (TURTLE uses TURTLE.CMD), and control XCI's behavior. Figure 2 shows a prototype for command(). The client also can control XCI via several global variables (see Table 2). These variables have defaults -- you may not need to set them.
Figure 2: Prototype for XCI's command() function
XCICMD function (int cmd, char *string, void *data); where: cmd is one of the following: O, execute function; 1, provide short help; 2, provide help screen. The cmd parameter has special significance for startup functions (see Table 2). string is the command line (not including the command's name). data is a pointer to a user-defined structure. This structure must contain any data the application commands need to function.
Table 2: XCI global variables
Variable Meaning ------------------------------------------------------------------------- char*xci_prompt; String used to prompt user for input (default: "?"). void (*xcif_prompt)(); Pointer to a function to print a prompt string: The string is passed to the function as an argument. void (*xcif_prehelp(); Pointer to a function to call before handling a help command: By default, this function does nothing. An application may want to switch screens or do other processing here. void (*xcif_posthelp)(); Pointer to a function to call after a help command. char *(*xcif_input)(); Pointer to the input function (normally fgets()).
The two key data structures in XCI are the cmds array and the instack pointer. XCI dynamically allocates the cmds array. It contains a list of commands and function pointers for each command. When XCI calls a command file (via the DO command), it maintains a linked list of files via the instack pointer. During the processing for a DO command (the dofunc() function in Listing Two), XCI makes an entry in the linked list that contains the filename and an fseek() offset in the file. It then closes the file before opening the new one. In this way, XCI avoids exceeding the DOS open file limit. At the end of a file, XCI unravels the linked list to reopen the previous file for processing.
The adddll() function (in listing Two) links in a DLL. This function uses the DOS-Extender/OS/2 function DosGetModHandle() to decide if the DLL is already present. It then loads the DLL using DosLoadModule(). Finally, XCI calls DosEnumProc() to find the names of the functions the DLL exports. XCI expects these names to be C functions (not Pascal functions), and therefore strips off the first character of the function name before entering it into the cmds array. This is necessary because the compiler prefixes C function names with an underscore.
For our personal use, adding commands with a DLL is not very important. Yet many sophisticated products allow users to add custom code to do specialized processing (user algorithms, user blocks, exits, and so on). The use of DLLs allows users to do this in a very straightforward manner. You can export functions from your code that they can use in their DLLs. DLLs under the DOS-Extender don't work exactly like OS/2 DLLs. See the text box entitled "DOS-Extender DLLs" for more details.
The TURTLE Program
Due to space limitations, many of TURTLE's files are not included in this article, but are available electronically. (See "Availability," page 3.) Therefore, I'll simply refer to their filenames when appropriate. However, the XCI command interpreter, which the TURTLE program depends upon heavily, is included. TURTLE.C (Listing Four, page 99) simply sets up XCI and turns control over to it. XCI then calls various functions within TURTLE in response to certain commands and events. Most of the commands are in TCMDS.C. TEXPR.C parses algebraic expressions in command arguments. Also, any source file #include TURTLE.H (Listing Three, page 99) for global definitions.
The SAVE and LOAD commands reside in a DLL, TSAVE.C. An end user of the TURTLE program could rewrite this DLL to load and save other graphics formats by replacing this DLL. The end user would not need the source or object code for TURTLE.
Accessing Physical Addresses
TURTLE uses the Microsoft C graphics functions for simplicity. It also uses direct access to the screen buffer to save and restore screens. In a normal DOS application, this is simple. The text buffer is at segment B800H, and the graphics buffer is at A000H. How can we access these segments with the DOS-Extender?
The setptr() function in TCMDS.C uses the DOS-Extender function DosMapRealSeg() to address the video buffers. It creates a protected-mode segment at the specified address and of a specified length. Using this function, our programs can directly access any real-mode address. Once setptr() initializes the pointers to the screen buffers, we can't tell that they are special pointers. We use them just like any other pointer in a normal C program.
Unsupported Calls
Although the Phar Lap DOS-Extender supports most DOS and BIOS calls transparently, there are a few functions that it does not directly support. TURTLE uses interrupt 15H function 86H in its DELAY command. The Phar Lap DOS-Extender doesn't support any interrupt 15H functions directly, but this isn't a problem. Phar Lap provides a DosRealIntr() function that can call almost any real-mode interrupt. The delaycmd() function in TCMDS.C uses this call. There are similar functions for making far calls to real-mode code.
Handling Interrupts
Interrupt handling is the one area where a DOS-extended program is different from a normal DOS program. Interrupts fall into three classes in a DOS extended application: interrupts that occur while in real mode; interrupts that occur while in protected mode; and processor exceptions (such as GP fault). The DOS extender allows you to have real- or protected-mode interrupt handlers. You also can have a real-mode handler that services protected-mode interrupts and vice versa.
Handling the DOS Ctrl-C interrupt was the most difficult part of writing the XCI module. If the screen contents were not important, it would have been best to use the signal() function from the C library. XCI hooks interrupt 16H, the BIOS keyboard interrupt, because signal() allows DOS to corrupt the screen by printing a ^C on the screen when a break occurs. The new interrupt 16H handler (xci_int16() in Listing Two) does not allow DOS to see any break characters. Instead, it sets the broke flag to signal XCI that the user wants to return to the top command level.
We can't install xci_int16() as a normal protected-mode interrupt handler because DOS will call INT 16H from real mode. But we can install a protected-mode handler that receives interrupts from real and protected mode. This is done using the DosSetPassProtVec() call.
The 286|DOS-Extender has separate vectors for real- and protected-mode interrupts. Calling DosSetPassProtVec() changes both of them. Before the program exits, XCI must restore both interrupt vectors using DosSetRealProtVec(). XCI registers its shut-down code (the xci_cleanup() function) using DosExitList(). This is similar to using atexit() or onexit() with one important exception. A function set up with DosExitList() will execute when the program terminates for any reason. Exit functions using the C library's atexit() or onexit() only run when the program terminates normally.
Versions of the DOS-Extender earlier than 1.4 had a problem using DosSetPassProtVec() with INT 16H. In these early versions, a DOS call triggers an INT 16H after the extender has set the real-mode interrupt, but before it has set the protected-mode vector. This causes an endless loop of mode switches. The latest versions don't have this problem, but TURTLE uses a workaround in any case. It sets the protected-mode vector for INT 16H before calling DosSetPassProtVec().
Inside xci_int16(), we need to call the old INT 16H handler to check for Ctrl-C characters. The DOS-Extender provides the DosChainToRealIntr() function, but does not return control to our program when the real-mode interrupt completes. Luckily, DosRealFarCall() can call an interrupt handler with a little subterfuge.
TURTLE uses DosRealFarCall(oldbreal,&r1,0,-1,r1flags); to call the old interrupt handler (whose address is in oldbreal). The pointer to r1 contains the registers we want to pass to the interrupt handler. Be careful to set the flags in r1 to a legitimate value. If the single-step flag is set, for example, your program will crash. You should usually make sure the interrupt enable flag is clear, too. The zero is a reserved parameter -- it must be zero and doesn't mean anything. The next argument (-1) informs DosRealFarCall() that it should push one word on the stack before calling the real-mode routine. The number of words is negative, so the DOS-Extender expects the real-mode code to clean up the stack before returning (like a Pascal function does). The last zero in the call is the one word to push on the stack.
You may be wondering why this works. When an interrupt occurs, the CPU will push the flags and the far return address on the stack. The IRET instruction at the end of the interrupt service routine will restore the flags and return to the calling routine. If DosRealFarCall() pushes a word on the stack, the IRET will restore that value to the flags and return to the proper address.
286 Pointers
Writing programs for a 286 DOS extender doesn't provide you with the same luxuries as writing for a 386 extender. In particular, with the 286, segments cannot exceed 64 Kbytes in length. You still have to resort to huge pointers to handle data larger than 64 Kbytes. Of course, for our TURTLE program, this isn't an issue -- the large graphics buffers are 64,000 bytes long.
Compiling
Compilation of the program is similar to compilation of a real-mode program. The Microsoft C compiler thinks it is creating an OS/2 program and DLLs.
The makefile (Listing Five, page 99) builds TURTLE.EXE and TSAVE.DLL, the two main portions of the program. It also creates TURTLE.LIB. This file is an import library for TURTLE DLLs. By linking with an import library, a DLL can reference routines that reside in TURTLE.EXE. The DOS-Extender will provide the correct address at runtime. If you were distributing TURTLE to end users, they could write their own DLLs by using TURTLE.LIB and a header file to declare the appropriate functions and types.
Note that protected-mode programs use the -Lp compiler switch to link with the protected-mode libraries. TURTLE.EXE can't run on an 8086-based computer, so the -G2 compiler switch creates 286-specific code for better performance.
The makefile compiles the TSAVE DLL with the -ML option. The -Gs option is also necessary because the DLL's data segment will differ from TURTLE.EXE's stack segment. DLLSTART.ASM provides an entry point for DLL initialization.
Once you create TURTLE.EXE you can run it by using the RUN286 program supplied with the DOS-Extender. You also can use the BIND286 utility to patch TURTLE.EXE to load RUN286 automatically when you execute it from the DOS prompt. If you own the distribution kit, you can also bind in a complete copy of the DOS-Extender, making TURTLE.EXE a complete stand-alone program.
Gauging Performance
Overall, the Phar Lap 286|DOS-Extender was a good choice for the TURTLE program. It provided enough memory to store screens, and the DLL system makes TURTLE easily extensible. Try some of the example programs (also available electronically). You'll see that performance suffers little, if any, compared to real-mode programs using the Microsoft graphics library.
Interactive programs such as TURTLE are hard to time, so you may want to experiment with TIMING.C (see Listing Six, page 100). TIMING.C will compile in real or protected mode. It exercises the VGA and writes large files to disk (similar to the TURTLE program). Table 3 shows the average times for TIMING.C to run with different DOS extender parameters and in real mode.
Table 3: Results from TIMING.C program: All times are in seconds, averaged over five runs. All tests ran on 386 DX/25 with no memory or disk cache.
Real Mode Dos-Extender Dos-Extender w/ w/no options -XFER 32 option --------------------------------------------------- Graphics: 19 22 22 File 12 24 14
Accessing the video RAM directly wasn't much more trouble with the DOS-Extender than with a normal program. The most troublesome aspect was the interrupt handling.
Unless you change the options to RUN286, most of TURTLE runs in extended memory. This leaves plenty of conventional memory free to run an external editor in real mode (for the EDIT command). Calling a real-mode program from TURTLE was as simple as it would be in a regular DOS program.
Bibliography
286|DOS-Extender Reference Manual, Cambridge, Mass.: Phar Lap Software Inc., 1991.
Berentes, Drew. Apple Logo. Blue Ridge Summit, Penn.: Tab Books, 1984.
Duncan, Ray. IBM ROM BIOS. Redmond, Wash.: Microsoft Press, 1988.
Williams, Al. DOS 5: A Developer's Guide. Redwood City, Calif.: M&T Publishing, 1991.
Products Mentioned
Phar Lap 286|DOS-Extender Phar Lap Software Inc. 60 Aberdeen Avenue Cambridge, MA 02138 617-661-1510 $495
DOS-Extender DLLs
DLLs under 286|DOS-Extender are slightly different from their OS/2 or Windows counterparts. The primary difference is because the DOS-Extender doesn't multitask. With OS/2 or Windows, a DLL may have to serve several clients simultaneously. This requires the DLL to allocate private data for each client and worry about concurrency problems. With the DOS-Extender, we don't have these concerns. The DLL is more like an overlay--the DOS-Extender loads it for our program's exclusive use at runtime.
Another difference stems from the way 286|DOS-Extender loads DLLs. With OS/2 or Windows, you may specify that a DLL loads only when your program uses it. If the program doesn't use the code in the DLL, it doesn't take the time and space to load it. The DOS-Extender loads any DLLs that you link with your program immediately. The only way to achieve true dynamic loading is to manage the process manually, as XCI does for its LINK command.
--A.W.
_PROGRAMMING WITH PHAR LAP'S 286|DOS-EXTENDER_ by Al Williams[LISTING ONE]
<a name="006a_0018"> /***************************************************************** * XCI.H Header for XCI command interpreter -- Al Williams * *****************************************************************/ #ifndef XCI_HEADER #define XCI_HEADER /* type for command functions */ #define XCICMD void far /* Pointer to command function */ typedef void (far * XCICMDP)(int cmd,char far *line,void *udata); /* Various hooks */ extern char *xci_prompt; /* string to prompt with */ extern FILE *xci_infile; /* input file */ extern int xci_exitflag; /* set to exit XCI */ extern int xci_defaultbrk; /* default break handling */ void (*xcif_prompt)(); /* function to prompt with */ void (*xcif_prehelp)(); /* function to call before help */ void (*xcif_posthelp)(); /* function to call after help */ char *(*xcif_input)(); /* function to get input */ /* main function prototype */ int command(char *dll,char *startfile,int caseflag, void far *ustruc,XCICMDP userfunc); /* add command (not from DLL) */ int addcmd(char *cmdnam,XCICMDP fn); #endif <a name="006a_0019"> <a name="006a_001a">[LISTING TWO]
<a name="006a_001a"> /********************************************************** * XCI.C An extensible command interpreter for the * * Phar Lap 286 DOS Extender -- Al Williams * **********************************************************/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <malloc.h> #include <dos.h> #include <phapi.h> #include <setjmp.h> #include "xci.h" /* Table of commands (dynamically allocated) */ static struct cmdtbl { char far *cmd; XCICMDP f; } *cmds=NULL; /* Number of commands in table */ static unsigned int nrcmds=0; /* Case sensitive? */ static int truecase=0; /* default hook function prototypes */ void xci_prompter(); /* func to prompt */ char *xci_input(); /* func to get input */ void xci_preposthelp(); /* pre & post help command */ /* default prompt string -- can be changed by client */ char *xci_prompt="? "; /* default routines -- can be changed by client */ void (*xcif_prompt)()=xci_prompter; void (*xcif_prehelp)()=xci_preposthelp; void (*xcif_posthelp)()=xci_preposthelp; char *(*xcif_input)()=xci_input; /* flag set when break detected */ static int broke; /* Jump to top level command loop */ jmp_buf cmdloop; /* default command function prototypes */ XCICMD dofunc(int cmd,char *s,struct udata *data); XCICMD linkfunc(int cmd,char *s,struct udata *data); XCICMD quitfunc(int cmd,char *s,struct udata *data); XCICMD helpfunc(int cmd,char *s,struct udata *data); /* default commands (client must enable goto if desired) */ static char *defcmd[]= { "quit", "help", "link", "do" }; /* addresses of default commands */ static XCICMDP deffunc[]={quitfunc,helpfunc,linkfunc,dofunc}; /* non-zero if running a script via DO */ static int interactive=0; /* stack of file positions for nested DO commands */ /* Files are closed and reopened to avoid DOS file limit */ static struct fstack { char *fp; /* file name */ long pos; /* position in file */ struct fstack * next; /* next fstack record */ } *instack; /* default stdin handle */ static FILE *baseio; /* Current input file */ FILE *xci_infile; /* Set to 1 when someone wants to exit */ int xci_exitflag=0; /* Default break action */ int xci_defaultbrk=1; /* Break vectors */ PIHANDLER oldbreak; REALPTR oldbreal; PIHANDLER old1b; REALPTR old1breal; /* Bios segment (you can't call DosGetBIOSseg from ISR) */ USHORT biosseg; /* ^Break handlers */ void _interrupt _far xci_int1b(REGS16 r) { union REGS rr; unsigned int *keyhead,*keytail; if (!xci_defaultbrk) { /* Chain to old break handler (never returns) */ DosChainToRealIntr(old1breal); } keyhead=MAKEP(biosseg,0x1A); keytail=MAKEP(biosseg,0x1C); broke=1; /* purge keyboard buffer */ *keyhead=*keytail; /* push ^C at head */ rr.h.ah=5; rr.x.cx=3; int86(0x16,&rr,&rr); } void _interrupt _far xci_int16(REGS16 r) { REGS16 r1; unsigned ah=r.ax>>8; _enable(); if (xci_defaultbrk&&(ah==0||ah==0x10||ah==1||ah==0x11)) { do { r1.ax=0x100; r1.flags=0; /* Simulate interrupt to old INT 16H handler */ DosRealFarCall(oldbreal,&r1,0,-1,r1.flags); if ((r1.flags&64)&&(ah==1||ah==0x11)) { r.flags=r1.flags; return; } } while (r1.flags&64); /* If break character -- replace it with a carriage return */ if ((r1.ax&0xff)==3||r1.ax==0x300) { unsigned int *keyhead; keyhead=MAKEP(biosseg,0x1A); keyhead=MAKEP(biosseg,*keyhead); *keyhead='\r'; broke=1; } } DosChainToRealIntr(oldbreal); } /* XCI Clean up */ /* Note: DosExitList requires this to be a pascal function */ void pascal far xci_clean(unsigned int reason) { /* restore interrupt vectors */ DosSetRealProtVec(0x16,oldbreak,oldbreal,NULL,NULL); DosSetRealProtVec(0x1b,old1b,old1breal,NULL,NULL); /* Exit handler must call DosExitList with EXLST_EXIT to proceed with the termination */ DosExitList(EXLST_EXIT,NULL); } /* default functions */ void xci_prompter(char *s) { printf("%s",s); } char *xci_input(char *inbuf,unsigned int siz,FILE *input) { return fgets(inbuf,siz,input); } void xci_preposthelp() { } /* Main command routine */ /* dll is initial DLL to load startfile is initial file to DO cases is 1 if case sensitivity is required userfunc is pointer to user function called at start and end */ int command(char *dll, char *startfile, int cases,void far *user,XCICMDP userfunc) { int i; char inbuf[129],*p; if (!cmds) { /* first time (not done for recursive calls) */ DosGetBIOSSeg(&biosseg); /* Due to a bug in versions prior to 1.4, you must set the INT 16H ProtVec before using PassToProtVec... */ DosSetProtVec(0x16,xci_int16,&oldbreak); DosSetPassToProtVec(0x16,xci_int16,NULL,&oldbreal); DosSetPassToProtVec(0x1b,xci_int1b,&old1b,&old1breal); /* set up exit handler */ DosExitList(EXLST_ADD,xci_clean); truecase=cases; xci_infile=stdin; /* install default commands */ cmds=(struct cmdtbl *)malloc(4*sizeof(struct cmdtbl)); if (!cmds) return 1; nrcmds=4; for (i=0;i<nrcmds;i++) { cmds[i].cmd=defcmd[i]; cmds[i].f=deffunc[i]; } /* load default DLL (if specified) */ if (dll&&*dll) if (adddll(dll)) printf( "Warning: unable to load default command DLL\n"); /* call user function */ if (userfunc) userfunc(0,NULL,user); /* execute default DO file */ if (startfile&&*startfile) dofunc(0,startfile,user); /* set jump buffer for future longjmp's */ setjmp(cmdloop); } /* initilization done -- begin main processing */ while (1) { char *token,*tail; /* if someone wants to quit then quit */ if (xci_exitflag) { /* call user function */ if (userfunc) userfunc(1,NULL,user); /* reset some things in case we are called again */ /* restore interrupt vectors */ DosSetRealProtVec(0x16,oldbreak,oldbreal,NULL,NULL); DosSetRealProtVec(0x1b,old1b,old1breal,NULL,NULL); DosExitList(EXLST_REMOVE,xci_clean); xci_infile=stdin; interactive=0; instack=NULL; free((void *)cmds); cmds=NULL; return 0; } /* If interactive then prompt */ if (!interactive) (*xcif_prompt)(xci_prompt); /* get input from user or file */ *inbuf='\0'; (*xcif_input)(inbuf,sizeof(inbuf),xci_infile); /* If break detected then go to top level */ if (broke) { struct fstack *f; broke=0; /* free fstack entries */ for (f=instack;f;f=f->next) free(f->fp); instack=NULL; interactive=0; xci_infile=stdin; longjmp(cmdloop,1); } /* If end of do file, return. If end of console, ignore */ if (!*inbuf&&feof(xci_infile)) { if (interactive) { return 0; } clearerr(xci_infile); continue; } /* got some input -- lets look at it */ i=strspn(inbuf," \t"); /* skip blank lines and comments */ if (inbuf[i]=='\n') continue; if (inbuf[i]=='#') continue; /* eat off \n from line */ p=strchr(inbuf+i,'\n'); if (p) *p='\0'; /* get a token */ token=strtok(inbuf+i," \t"); if (!token) continue; /* this should never happen */ /* do we recognize the command? */ i=findcmd(token); /* NO: error */ if (i==-1) { printf("Unknown command %s\n",token); continue; } /* YES: compute command's tail (arguments) */ tail=token+strlen(token)+1; tail+=strspn(tail," \t"); /* execute command */ cmds[i].f(0,tail,user); } } /* Find a command -- search backwards so new commands replace old ones */ static int findcmd(char *s) { int i,stat; for (i=nrcmds-1;i>=0;i--) { if (!(truecase? strcmp(s,cmds[i].cmd) : stricmp(s,cmds[i].cmd))) return i; } return -1; } /* Add a DLL to the command input table returns 0 if successful */ static adddll(char *dll) { char cmdnam[33],*p; HMODULE h=0; unsigned ord=0; p=strrchr(dll,'\\'); /* check to see if module is already loaded */ if (!DosGetModHandle(p?p+1:dll,&h)) { printf("%s already loaded\n",p?p+1:dll); return 1; } /* Load module if possible */ if (DosLoadModule(0,0,dll,&h)) return 1; /* find all exported functions in module */ while (!DosEnumProc(h,cmdnam,&ord)) { PFN fn; /* Get function's address */ DosGetProcAddr(h,cmdnam,&fn); /* add command -- skipt 1st character (it is a _) */ if (addcmd(cmdnam+1,(XCICMDP) fn)) return 1; } return 0; } /* add a command -- returns 0 for success */ addcmd(char *cmdnam,XCICMDP fn) { struct cmdtbl *ct; /* make more room in table */ ct=(struct cmdtbl *) realloc(cmds,(nrcmds+1)*sizeof(struct cmdtbl)); if (!ct) return 1; cmds=ct; /* add name and function */ cmds[nrcmds].cmd=strdup(cmdnam); if (!cmds[nrcmds].cmd) return 1; cmds[nrcmds++].f=(XCICMDP) fn; return 0; } /* currently executing file name */ static char curfile[67]; /* Command to transfer execution from one file to another Only works from inside a file, and must be enabled by client program: addcmd("GOTO",gotocmd); */ XCICMD gotofunc(int cmd,char *s,struct udata *data) { FILE *f; if (cmd==2) { printf("Execute commands from an ASCII file\n"); return; } if (cmd==1||!s||!*s) { printf("goto executes commands from an ASCII file\n" "Usage: goto FILENAME\n"); return; } /* open file */ f=fopen(s,"r"); if (!f) { printf("Can't open %s\n",s); perror(s); return; } if (!interactive) { printf("Use goto only from command files\n" "Use do to execute a file\n"); return; } /* register as current file */ strcpy(curfile,s); fclose(xci_infile); xci_infile=f; } /* Do a command file */ XCICMD dofunc(int cmd,char *s,struct udata *data) { FILE *ifile; struct fstack recall; if (cmd==2) { printf("Do commands from an ASCII file\n"); return; } if (cmd==1||!s||!*s) { printf("Do executes commands from an ASCII file\n" "Usage: do FILENAME\n"); return; } /* open file */ ifile=fopen(s,"r"); if (!ifile) { printf("Can't open %s\n",s); perror(s); return; } if (interactive) { /* store current file name so we can resume later */ if (!(recall.fp=strdup(curfile))) { printf("Out of memory\n"); fclose(ifile); return; } /* store position in current file and close it */ recall.pos=ftell(xci_infile); fclose(xci_infile); } else { /* no current file, so remember this handle but don't close it */ baseio=xci_infile; recall.fp=NULL; } /* add recall to linked list of nested files */ recall.next=instack; /* make new file current */ strcpy(curfile,s); xci_infile=ifile; /* mark nesting level */ interactive++; /* make recall the head of the fstack linked list */ instack=&recall; /* call command recursively */ command(NULL,NULL,0,data,NULL); /* close useless file */ fclose(xci_infile); /* restore old file */ if (instack->fp!=NULL) /* is it a file? */ { /* open it */ xci_infile=fopen(instack->fp,"r"); if (!xci_infile) { /* serious error! file vanished! reset to top level */ printf("Error opening %s\n",instack->fp); xci_infile=baseio; interactive=0; /* bad error if nested */ } else { /* reposition old file */ fseek(xci_infile,instack->pos,SEEK_SET); /* make it current */ strcpy(curfile,instack->fp); } /* release memory used for file name */ free(instack->fp); } else { /* reset to console */ xci_infile=baseio; } /* fix up linked list */ instack=instack->next; interactive--; } /* Link a dll */ XCICMD linkfunc(int cmd,char *s,struct udata *data) { if (cmd==2) { printf("Add user-defined commands\n"); return; } if (cmd==1||!s||!*s) { printf("Add user-defined commands via a DLL\n" "Usage: link DLLNAME\n"); return; } if (adddll(s)) { printf("Unable to load dll: %s\n",s); } } /* Quit */ XCICMD quitfunc(int cmd,char *s,struct udata *data) { if (cmd==0) { xci_exitflag=1; return; } /* long and short help message */ printf("Exits to DOS\n"); } /* provide general help (scan from end to 0 call with cmd==2) or specific help find command and call with cmd==1 */ XCICMD helpfunc(int cmd,char *s,struct udata *data) { int i,j=0; if (cmd==2) printf("Get help\n"); if (cmd==1) printf( "Use the help command to learn about the available" " commands\nUse HELP for a list of help topics" " or \"HELP topic\"" " for help on a specific topic.\n"); if (cmd) return; /* call user's prehelp */ (*xcif_prehelp)(); /* if specific command... */ if (s&&*s) { /* find it and ask it about itself (command==1) */ i=findcmd(s); if (i==-1) printf("No such command: %s\n",s); else cmds[i].f(1,NULL,NULL); } else /* No specific command -- do them all (command==2) */ for (i=nrcmds-1;i>=0;i--) { char buf[22]; /* might be a lot of commands -- pause on screenfulls */ if (!(++j%25)) { printf("--More--"); j=0; if (!getch()) getch(); putchar('\n'); } /* print header */ strncpy(buf,cmds[i].cmd,20); strcat(buf,":"); printf("%-21.21s",buf); /* ask command for short help */ cmds[i].f(2,NULL,NULL); } /* call user's post help */ (*xcif_posthelp)(); } <a name="006a_001b"> <a name="006a_001c">[LISTING THREE]
<a name="006a_001c"> /***************************************************************** * TURTLE.H Header for TURTLE.EXE -- Al Williams * *****************************************************************/ #include <graph.h> typedef unsigned long ulong; typedef unsigned int uint; /* graphics buffer */ extern char _gbuf[64000]; /* Application data (passed to XCI commands) */ struct udata { char *gbuf; /* pointer to graphics buffer */ char tbuf[4000]; /* text buffer */ char *gptr; /* pointer to graphics screen */ char *tptr; /* pointer to text screen */ struct xycoord graphxy; /* x,y of graphic screen */ struct rccoord textxy; /* x,y of text screen */ int color; /* color */ long backcolor; /* background color */ /* store[10] & store[11] are for internal use */ char *store[12]; /* screen storage */ unsigned int mode:1; /* draw or move */ unsigned int textgraph:1; /* if 1, don't exit graphic mode */ int heading; /* turtle heading */ /* X and Y are stored as reals too to combat rounding errors */ double realx; double realy; /* 26 variables A-Z */ long vars[26]; /* text color */ int tcolor; }; /* Application data structure */ extern struct udata appdata; <a name="006a_001d"> <a name="006a_001e">[LISTING FOUR]
<a name="006a_001e"> /***************************************************************** * TURTLE.C Main program for TURTLE.C -- Al Williams * * TURTLE assumes large model -- see the MAKEFILE for compile * * instructions. * *****************************************************************/ #include <stdio.h> #include <graph.h> #include <dos.h> #include <phapi.h> #include "turtle.h" #include "xci.h" /* XCI client's application data (see TURTLE.H) */ struct udata appdata; int installcmds(void); /* XCI startup command -- install commands */ XCICMD startup(int cmd, char far *dummy) { if (cmd) return; if (installcmds()) { printf("Out of memory\n"); exit(1); } } /* Reset things before normal exit */ void preexit() { _setvideomode(_DEFAULTMODE); } /* MAIN PROGRAM */ main() { void turtleprompt(); /* register exit routine */ atexit(preexit); /* Set some graphics things */ _setvideomode(_TEXTC80); _setactivepage(0); _setvisualpage(0); appdata.tcolor=appdata.color=15; appdata.backcolor=0x003f0000L; /* blue background */ /* clear screen */ clearcmd(0,"",&appdata); /* Print banner */ printf("TURTLE VGA by Al Williams\n" "Type HELP for help\n"); /* Take over XCI prompt function */ xcif_prompt=turtleprompt; command("TSAVE.DLL","TURTLE.CMD",0, &appdata,(XCICMDP) startup); } /* XCI prompt -- if in graphics mode keep input on top line */ void turtleprompt(char *s) { union REGS r; if (appdata.textgraph) { /* don't do newline in graphic mode */ if (*s=='\n') { printf(" "); return; } /* but do clear the line */ r.h.ah=2; r.h.bh=0; r.x.dx=0; int86(0x10,&r,&r); r.x.ax=0x0a00|' '; r.x.bx=appdata.tcolor; r.x.cx=40; int86(0x10,&r,&r); /* clear to end of line */ } printf("%s",s); } <a name="006a_001f"> <a name="006a_0020">[LISTING FIVE]
<a name="006a_0020"> ###################################################### # Makefile for TURTLE # # Use NMAKE to compile # ###################################################### all : turtle.exe tsave.dll turtle.exe : turtle.obj tcmds.obj xci.obj texpr.obj cl -AL -Lp turtle.obj tcmds.obj xci.obj \ texpr.obj c:\run286\lib\graphp.obj \ LLIBPE.LIB GRAPHICS.LIB implib turtle.lib turtle.exe turtle.obj : turtle.c xci.h turtle.h cl -AL -Ox -G2 -c turtle.c tcmds.obj : tcmds.c xci.h turtle.h cl -AL -Ox -G2 -c tcmds.c texpr.obj : texpr.c turtle.h cl -AL -Ox -G2 -c texpr.c xci.obj : xci.c xci.h cl -AL -Ox -G2 -c xci.c tsave.dll : tsave.c dllstart.asm turtle.h xci.h turtle.lib cl -ML -Gs -Lp -Ox -G2 tsave.c dllstart.asm turtle.lib <a name="006a_0021"> <a name="006a_0022">[LISTING SIX]
<a name="006a_0022"> /****************************************************************** * TIMING.C - simple non-rigorous benchmark for 286|DOS Extender * * Compile with: * * CL -AL -Lp -G2 -Ox timing.c graphp.obj llibpe.lib graphics.lib * * (protected mode) * * OR: * * CL -AL -G2 -Ox timing.c graphics.lib * * (real mode) * ******************************************************************/ #include <stdio.h> #include <graph.h> #include <time.h> #define time_mark time_it(0) #define time_done time_it(1) main() { printf("Timing graphics operations\n"); time_mark; gtest(); time_done; printf("Timing file operations\n"); time_mark; ftest(); time_done; exit(0); } /* Function to mark times */ int time_it(int flag) { static clock_t sttime; unsigned s; if (!flag) { sttime=clock(); } else { s=(clock()-sttime)/CLK_TCK; printf("Elapsed time: %d seconds\n",s); } return 0; } /* Graphics test -- must have VGA */ int gtest() { int i,x,y; _setvideomode(_MRES256COLOR); for (i=1;i<11;i++) { _setcolor(i); for (y=0;y<199;y++) for (x=0;x<319;x++) _setpixel(x,y); } _setvideomode(_DEFAULTMODE); return 0; } /* File test -- assumes 320K free on current drive */ char filedata[64000]; int ftest() { FILE *tfile; int i,j; for (j=0;j<10;j++) { tfile=fopen("~~TIMING.~@~","w"); if (!tfile) { perror("TIMING"); exit(1); } for (i=0;i<5;i++) fwrite(filedata,sizeof(filedata),1,tfile); if (fclose(tfile)) { perror("TIMING"); } unlink("~~TIMING.~@~"); } return 0; }
Copyright © 1992, Dr. Dobb's Journal