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.
DOS extenders are playing an increasing role in PC software development. Upon hearing that Intel's 386/486 C CodeBuilder includes a royalty-free DOS extender which supports DPMI, I was naturally curious. So I decided it was time for CodeBuilder's first check-up and proceeded to place it on the examining table.
To test this package, I developed a disk duplication system. The program has a pleasing user interface, and is a practical, useful example. It stores images of floppy disks entirely in memory -- simple for a DOS-extended program; difficult or impossible for normal programs. I wanted to test CodeBuilder on some points I think are important including Microsoft C compatibility, interrupt handling, physical memory addressing, making DOS/BIOS calls, and the speed penalty for making such calls.
Vital Signs
The CodeBuilder package is a complete development system. It includes a C compiler, a debugger, a linker, a librarian, and a make program. Unlike many DOS extenders, it doesn't have many 386 bells and whistles -- it looks much like Microsoft C, except for the 32-bit integers and nonsegmented pointers. The debugger won't impress many CodeView or Turbo Debugger users, but it is serviceable.
Because the compiler is Microsoft compatible, Intel supplies little documentation for it and almost none for the libraries. Instead, Intel recommends the Microsoft documentation.
One important feature of CodeBuilder is the cost of distributing executables that you generate -- it's free! Unlike many other DOS extenders, Intel allows you to distribute your executables (with the DOS extender built in) at no charge. This will be a decisive factor in many purchasing decisions. However, I expect Intel has set a trend other vendors will follow.
The Project
CopyBuilder 386 (the disk duplication system) requires a lot of memory, directly accesses the screen, makes many DOS and BIOS calls, handles interrupts, and uses a display library I developed under Microsoft C. The program operates on a disk image in memory. The image can be read from a diskette or a disk file. CopyBuilder can write the image to either another diskette or a disk file. The disk files include a title and a checksum.
CopyBuilder includes a simple character-mode interface and help system. When CopyBuilder prompts you for a file name, you can enter the file directly. If you enter a name that ends in a colon or a backslash or a filename that contains wildcards, CopyBuilder presents a menu of matching files. You can select the file from this menu. The default file is always "*.*". The menu will never contain more than 120 files; but if the directory has too many files, you can narrow the search by using wildcards.
The source code for CopyBuilder resides in several modules, some of which are not included here due to space considerations. (See "Source Code Availability" on page 3 for details on obtaining the complete system.) CB386.H (Listing One, page 82) and CB386.C (Listing Two, page 82) contain the main code. FILEIO.C (Listing Three, page 84) and DISKIO.C (not included) handle file and disk I/O, respectively. REBOOT.C (Listing Four, page 86) provides code to disable CONTROL-ALT-DEL. DISPLAY.C (Listing Five, page 86), HISTO.C, and DIRPICK.C (not included) make up a simple display library developed for Microsoft C.
CodeBuilder In Action
Only two routines, read_disk() function (DISKIO.C) and loadit() (Listing Three) allocate memory using malloc(). Both routines read in new images. Because sector_read() and sector_write() use BIOS interrupt 13H for disk I/O, they must allocate a special memory buffer below the 1-Mbyte line (dosbuf). CodeBuilder automatically copies extended memory data to a special buffer below 1 Mbyte for many DOS calls. However, for the BIOS disk services, it does not. You are responsible for placing data buffers where the BIOS can access them. When writing, you must first copy the data to the special buffer. When reading, you must copy the data out of this buffer when the BIOS call completes.
CodeBuilder allows programs to allocate this type of buffer using the standard DOS memmory allocation call (interrupt 21H function 48H). Unlike the normal DOS call, CodeBuilder expects the number of bytes (not paragraphs) in the EBX register. If EAX is equal to 0x00004800, CodeBuilder allocates memory below 1 Mbyte. If EAX is equal to 0x80004800, CodeBuilder allocates extended memory.
CodeBuilder normally aligns structure members on a 4-byte boundary to improve performance. This is a good idea, unless the structure maps to an external entity. In CB386.H, the _bpb structure contains the BIOS Parameter Block (BPB) from the target disk. The structure must mirror the actual BPB on the disk. CB386 uses #pragma align-(_bpb=1) to force CodeBuilder to place each structure member on the next available byte.
Larger than Life DOS Calls
Notice in FILEIO.C (Listing Three) that CB386 calls fread() and fwrite() with 32-bit integer values for the size argument. For instance, when reading a 1.44-Mbyte floppy disk, fread() must read 1.44 Mbytes of data. Of course, CodeBuilder internally breaks the request into multiple DOS calls, each handling smaller pieces of data. The buffer that CodeBuilder uses for DOS I/O is set by the /xiobuf option. For example, /xiobuf=40K, which is the default, reserves 40K of memory for DOS communications.
Accessing Video RAM
DISPLAY.C mainly uses video BIOS calls to manipulate the screen. Two functions, vidsave() and vidrestore(), directly access the screen memory. This is relatively simple because CodeBuilder uses a flat addressing scheme. For a color display, the buffer is at location B800:0000. (The vidmode() function ensures page zero is active.) CodeBuilder maps memory below 1 Mbyte so that this address can be easily converted to a linear address. Simply shift the segment value to the left four places and add the offset. For example char *vptr=(char *)0xB8000 will point vptr to the beginning of screen memory.
CB386 uses the standard signal() and _harderr() functions to trap break interrupts and critical errors. The signal() method is usually unsatisfactory for programs such as CB386 because it allows ^C to appear on the screen when a break occurs. However, the CB386 break handler (breakfunc() in DISP.C) does a longjmp() to reinitialize the main menu. This has the handy side effect of redrawing the screen -- the ^C doesn't last long enough to be a concern.
Interrupts
The interrupt 9 function (ourint9() in REBOOT.C) contains code to prevent the user from rebooting the machine with CONTROL-ALT-DELETE. It does this by hooking the keyboard interrupt (INT 9). If it detects the DELETE key, it looks at the BIOS keyboard status word. If the CONTROL and ALT keys are down, the interrupt handler consumes the DELETE key--the BIOS never sees it. In all other cases, the BIOS receives the keystroke unaltered.
Handling interrupts in a DOS extender is usually very different from handling them under regular DOS. Still, CodeBuilder is extremely different, even for a DOS extender. Programs declare interrupt functions via a pragma. In REBOOT.C you can see the statement #pragma interrupt(ourint9). Further down in the code, the actual definition of ourint9() appears. Notice that the registers are not pushed on the stack, as in Microsoft C. You must call the special CodeBuilder function, _get_stk_frame(), to get a pointer to an _XSTACK structure. The CodeBuilder header file STK.H declares the _XSTACK structure, which is sketchily documented. It contains the interrupt registers, and other related fields.
Normally, CodeBuilder processes interrupts in protected mode, then reissues them in V86 mode for DOS. The opts field in _XSTACK can alter this behavior. (The CodeBuilder documentation incorrectly calls this field stk_opts.) By placing different values in this field, a program can prevent the V86 interrupt or abort the program. In ourint9(), for example, frameopts=_STK_NOINT causes CodeBuilder not to reissue the keyboard interrupt. This prevents the BIOS from gaining control when CB386 detects the CONTROL-ALT-DELETE keys.
Virtual memory can cause special problems for Interrupt Service Routines (ISRs). If an ISR is swapped out when its interrupt occurs, it must be swapped back in before the interrupt can proceed. At best, this delays the interrupt, holding up the entire system. As a worst case, imagine that the ISR controls the disk interrupt. When the ISR must be swapped in, CodeBuilder tries to read it from disk. CodeBuilder reissues the disk interrupt, which triggers another request to swap in the ISR. This is a deadlock situation, and must be prevented.
The lockint9() function in REBOOT.C uses the DPMI functions to lock the interrupt 9 handler in memory. The _dpmi_lockregion() function ensures a block of memory will remain in RAM. CodeBuilder may swap unlocked blocks to disk. Locks always occur on 4-Kbyte pages, so we assume ourint9() won't be larger than 4K and sidestep the issue of computing the length of the function.
The same problem arises when ISRs use global or static data -- you also must lock the data in memory. Intel suggests examining the maps generated by the linker to find the address ranges to lock. Of course, when you recompile, you must reexamine the maps, alter the code, and compile again -- a big time waster. Because lockint9() doesn't use global or static data, this problem doesn't appear.
Environment Problems
One final note about CB386. The DOS menu command uses the standard putenv() function to change the DOS shell's prompt. In earlier versions of CodeBuilder, putenv() did not modify the environment such that child processes got the new values. Intel's latest versions handle this code correctly. If you have the older version, there is no harm -- but the prompt will remain unchanged.
Analysis
The CodeBuilder compiler is very similar to the Microsoft C compiler. However, certain features (ASM, interrupt handling, and so on) are missing or very different, so don't expect to port complex code effortlessly. Intel and Microsoft also implement a completely different set of pragmas. The graphics functions only support EGA and VGA monitors. Also, the presentation graphics library (in Microsoft C 6.0) is not present.
It appears Intel chose to make the compiler as simple to use as possible. CodeBuilder is a 32-bit C compiler, with a simple DOS extender built in to support it. This simplicity means you can learn CodeBuilder in short order, but prevents you from taking advantage of many 386 features that other extenders allow you to use. Of course, many of these features are not available under DPMI host, anyway.
Because CodeBuilder maps DOS memory at its physical address; accessing the screen or other physical memory couldn't be much easier. Flat 32-bit pointers also simplify programming. Of course, with any flat model, you lose many benefits of memory protection -- you can't expect the DOS extender to catch null pointer references, for example.
Calling DOS and BIOS calls with CodeBuilder was straightforward. It would be better if CodeBuilder did not require CB386 to manage a DOS buffer for its BIOS calls. CodeBuilder lifts this restriction on most other calls, however.
Also, to test CodeBuilder's simple benchmark, I wrote a program, TIMING.C (Listing Six, page 88), that will compile in real or protected mode. It exercises the VGA and writes large files to disk. The results, with those for Microsoft C and Phar Lap's 286| DOS Extender are in Table 1. CodeBuilder isn't too fast with the graphics operations and pays a steep penalty for running DOS and BIOS calls in V86 mode. High-performance programs will want to minimize their use of DOS and BIOS calls.
Table 1: Results from TIMING. C
Graphics File ---------------------------------- Real Mode 19 12 286|DOS Extender 22 24 (default options) 286|DOS Extender 22 14 (-XFER 32 option) CodeBuilder 36 62 (default options) CodBuilder 36 62 (/xiobuf=64K option)
Final Notes
I personally found the documentation and the debugger disappointing. Still, CodeBuilder is a fairly new product -- it should improve over time. The compiler seems solid, and the libraries are complete and mostly bug free.
CodeBuilder's simplicity will appeal to those with straightforward code to port or portable code to write. Certainly, its royalty-free distribution license will appeal to everyone. Protected-mode programming veterans may find CodeBuilder lean on 386-specific features. Yet, as you can see, it is capable of creating substantial applications.
Bibliography
Intel Corp. Intel 386/486 C Code Builder Kit Reference Manual, Santa Clara, Calif.: Intel Corp., 1991.
Intel Corp., et al., DOS Protected Mode Interface (DPMI) Specification. Santa Clara, Calif.: Intel Corp., 1990.
Microsoft Corp. Microsoft C Run-Time Library Reference, Redmond, Wash.: Microsoft Press, 1990.
Williams, Al. DOS 5: A Developer's Guide, Redwood City, Calif.: M&T Press, 1991.
Products Mentioned
Intel 386/486 C Code Builder Kit
Intel 5200 N.E. Elam Young Parkway Hillsboro, OR 07124-5961 503-696-8080 $695
_YOUR OWN DISK DUPLICATOR PROGRAM_ by Al Williams[LISTING ONE]
<a name="003f_0011"> /**************************************************************** * CB386.H - include file for CopyBuilder 386 * * See makefile for compile directives -- Al Williams * ****************************************************************/ #define NRTRIES 3 /* number of times to retry disk ops */ /* force codebuilder to not align */ #pragma align(_bpb=1) /* structre of disk BPB */ extern struct _bpb { unsigned char jump[3]; char oemname[8]; unsigned short bytespersec; unsigned char secperclust; unsigned short ressectors; unsigned char nrfats; unsigned short rootsize; unsigned short nrsectors; char media; unsigned short fatsectors; unsigned short secpertrack; unsigned short nrheads; unsigned int hiddensecs; unsigned int hugesectors; unsigned char physdrive; char notused; unsigned char signature; /* should be 0x29 */ unsigned int serno; char label[11]; char type[8]; char pad[512-60]; /* rest of 512 byte sector */ } bpb; /* various globals */ extern int driveno; extern unsigned disksize; extern unsigned sectorct; /* disk image buffer */ extern unsigned char *diskbuf; /* DOS buffer used to communicate with BIOS */ extern char *dosbuf; /* set by critical errors */ extern int critical_err; /* information on buffer */ extern struct _bufinfo { char title[65]; unsigned size; unsigned short copies; char source[13]; /* checksums (stored and computed) */ unsigned short csum, ccsum; short dirty; } bufinfo; extern void (*slbreak)(); /* place to hook break handler */ /* holds disk format command */ extern char fmtcmd[]; /* additional break handler */ extern void (*when_break)(); /* prototypes for break handlers */ void load_break(); void save_break(); /* general prototypes */ int sector_read(int head, int track, int sector, int drive, unsigned char *buf,unsigned count); int sector_write(int head, int track, int sector, int drive, unsigned char *buf,unsigned count); /* disable reboot */ void noreboot(void); void okreboot(void); <a name="003f_0012"> <a name="003f_0013">[LISTING TWO]
<a name="003f_0013"> /**************************************************************** * CB386.C - main file for CopyBuilder 386 * * See makefile for compile directives -- Al Williams * ****************************************************************/ #include <stdio.h> #include <dos.h> #include <ctype.h> #include "cb386.h" #include "display.h" /* current drive number */ int driveno; /* current bios parameter block */ struct _bpb bpb; /* size of disk */ unsigned disksize; /* information about buffer */ struct _bufinfo bufinfo; /* number of sectors */ unsigned sectorct; /* storage for disk image */ unsigned char *diskbuf; /* set when a critical error occurs */ int critical_err; /* DOS buffer for disk BIOS reads and writes */ char *dosbuf; /* text of format command */ char fmtcmd[129]; /* set when a critical error occurs */ int critical_err; /* critical error messages */ static char *cmsgs[]= { "Write protect", "Unknown unit", "Drive not ready", "Unknown command", "CRC error", "Bad Request", "Seek error", "Unknown media", "Sector not found", "Out of paper", "Write error", "Read error", "General failure", "Unknown", "Unknown", "Invalid change" }; /* Critical error handler */ void cerror(int dev,int code) { char msg[81]; int choice; critical_err=code&=0xFF; sprintf(msg,"Critical error: %s (Retry, Fail, Ignore)", cmsgs[code]); choice=prompt(msg,"RIF",ERRCOLOR); if (choice==0) choice=_HARDERR_RETRY; else if (choice==1) choice=_HARDERR_IGNORE; else choice=_HARDERR_FAIL; /* F or ESC */ _hardresume(choice); } /* main program */ main(int argc,char *argv[]) { /* set up critical error handler */ _harderr(cerror); if (argc>2||argv[1][0]=='?'||argv[1][1]=='?') error("CopyBuilder 386 by Al Williams\n" "A diskette duplication system\n" "Usage: CB386 [drive]\n"); /* save current drive/directory */ cdsave(); /* set up video mode and detect monitor type */ vidmode(); /* if mono monitor, set up neutral colors */ if (mono) { TEXTCOLOR=7; SOCOLOR=0x70; ERRCOLOR=1; HELPCOLOR=7; } /* reset title, etc. */ strcpy(bufinfo.title,"<EMPTY>"); strcpy(bufinfo.source,"N/A"); strcpy(fmtcmd,"format"); noreboot(); /* if user asked for a disk on the command line, read it */ if (argc==2) { /* show a display */ disp(); /* read the disk */ read_disk(*argv[1]); } /* goto main menu */ menu(); } /* Non-interactive error routine */ error(char *s) { printf("\n%s\n",s); cdrestore(); exit(1); } /* compute checksum add sixteen bit words and wrap carry around */ checksum() { unsigned char *bufp=diskbuf; int i=bufinfo.size; unsigned short cksum=0; unsigned cksum1; while (i--) { cksum1=cksum; cksum1+=*bufp++; cksum=cksum1&0xFFFF; if (cksum1&0xFFFF0000) cksum++; } bufinfo.ccsum=cksum; return cksum; } /* Format a disk in the indicated drive */ format(int driveno) { char fcmd[80]; int stat; /* build command line */ sprintf(fcmd,"%s %c:",fmtcmd,driveno+'A'); /* save video */ vidsave(); /* execute command */ stat=system(fcmd); vidrestore(); if (stat==-1) advise("Unable to execute format program"); } /* turn the blinking -wait- on at bottom of screen */ wait_on() { int ocolor=color; color=SOCOLOR|0x80; /* make it blink */ goxy(0,24); clreol(); goxy(37,24); printfc("-WAIT-"); curshide(); color=ocolor; } /* turn the blinking -wait- off at bottom of screen */ wait_off() { goxy(0,24); clreol(); } <a name="003f_0014"> <a name="003f_0015">[LISTING THREE]
<a name="003f_0015"> /**************************************************************** * FILEIO.C File oriented I/O routines for CB386 * * See makefile for compile directives -- Al Williams * ****************************************************************/ #include <stdio.h> #include <malloc.h> #include <errno.h> #include "cb386.h" #include "display.h" void (*slbreak)(); /* place to hook break handler */ /* ^C handler when saving -- used in DISKIO.C too */ void save_break() { advise("\aOperation aborted"); if (slbreak) slbreak(); when_break=slbreak; } /* ^C handler when loading -- used in DISKIO.C too */ void load_break() { cleanup(); when_break=slbreak; } /* Clean up an aborted load */ cleanup() { if (diskbuf) free(diskbuf); diskbuf=NULL; memset(&bufinfo,0,sizeof(bufinfo)); strcpy(bufinfo.title,"<EMPTY>"); strcpy(bufinfo.source,"N/A"); } /* save disk image to a file */ m_save() { slbreak=when_break; when_break=save_break; saveit(); when_break=slbreak; slbreak=NULL; } /* load disk image to a file */ m_load() { slbreak=when_break; when_break=load_break; loadit(); when_break=slbreak; slbreak=NULL; } /* actual routine to save data */ saveit() { char fn[66]; FILE *f=NULL; int cnt1; /* if no data to save, forget it */ if (!diskbuf) { advise("No disk image in memory"); return; } /* get a file name */ if (!getfilen(fn,65)) return; critical_err=0; /* check if the file already exists */ if (!access(fn,0)) { if (prompt("\aFile exists. Overwrite? (Y/N)" ,"NY",TEXTCOLOR)<=0) return; } /* if checking for the file caused a critical error, don't even try to open the file */ if (!critical_err) f=fopen(fn,"wb"); /* if the file can't be opened (or wasn't because of a critical error, forget it */ if (!f) { advise("Can't open file for writing"); return; } /* Get a title */ if (ask("Title: ",NULL,TEXTCOLOR,64,fn,NULL)==-1) return; /* only change title if new one was entered */ if (*fn) strcpy(bufinfo.title,fn); /* Turn the wait indicator on */ wait_on(); /* set dirty bit in image. copies/source/csum are meaningless */ bufinfo.dirty=1; /* write signature */ putw('C386',f); /* write size of bufinfo */ putw(sizeof(struct _bufinfo),f); /* write bufinfo */ fwrite(&bufinfo,1,sizeof(struct _bufinfo),f); /* write entire buffer in one swoop */ cnt1=fwrite(diskbuf,1,bufinfo.size,f); /* one | is deliberate here -- we always want to fclose */ if (ferror(f)|fclose(f)|(cnt1!=bufinfo.size)) advise("\aError writing to file"); else bufinfo.dirty=0; wait_off(); } /* routine to actually load file to disk image */ loadit() { char fn[66]; FILE *f; int cnt,cnt1; /* if buffer is full, confirm */ if (!dirtyquery()) return; /* get file name */ if (!getfilen(fn,65)) return; /* open it */ f=fopen(fn,"rb"); /* can't open it, forget it */ if (!f) { advise("Can't open file"); return; } /* Turn wait indicator on */ wait_on(); /* check file type */ cnt=getw(f); if (cnt!='C386') { advise("Not a C386 image file"); return; } /* get size of bufinfo structure */ cnt=getw(f); /* new versions of C386 can't make bufinfo smaller, only larger at the end */ fread(&bufinfo,1,cnt,f); /* kill old image */ if (diskbuf) free(diskbuf); /* allocate new image based on size */ diskbuf=malloc(bufinfo.size); if (!diskbuf) { advise("Insufficient memory"); cleanup(); fclose(f); return; } /* read it all in */ cnt1=fread(diskbuf,1,bufinfo.size,f); /* one | is deliberate here also (see saveit(), above) */ if (ferror(f)|fclose(f)|(cnt1!=bufinfo.size)) { advise("Error reading file"); cleanup(); return; } checksum(); strcpy(bufinfo.source,fn); bufinfo.copies=0; wait_off(); } <a name="003f_0016"> <a name="003f_0017">[LISTING FOUR]
<a name="003f_0017"> /**************************************************************** * REBOOT.C - Disable ^ALT-DEL for CodeBuilder programs * * Al Williams -- August 1991 * ****************************************************************/ #include <stdio.h> #include <i32.h> #include <stk.h> #include <dos.h> #include <conio.h> /* When running DOS we replace the keyboard interrupt (INT 9) */ /* old INT 9 handler */ static void (*oldint9)(); #pragma interrupt(ourint9) /* replacement interrupt handler */ static void ourint9() { int code,temp; /* pointer to CodeBuilder stack frames */ _XSTACK *frame=_get_stk_frame(); /* pointer to BIOS shift status byte */ unsigned char *shift_status=(unsigned char *)0x417; /* read keyboard */ code=inp(0x60); /* DEL is scan code 0x53 -- if *shift_status&0xc==0xc then shift and Alt are down */ if (code!=0x53||(*shift_status&0xc)!=0xc) _chain_intr(oldint9); /* will not allow ^ALT-DEL */ /* consume key from keyboard */ temp=inp(0x61); outp(0x61,temp|0x80); outp(0x61,temp); outp(0x20,0x20); /* Tell CodeBuilder not to reissue interrupt */ frame->opts=_STK_NOINT; return; } /* This function locks the page ourint9 is on so it can't be swapped out. 4K is the minimum size, and surely ourint9 isn't that big.... */ static lockint9(int flag) { static lock=0; if (flag!=lock) { lock=flag; if (flag) _dpmi_lockregion(ourint9,4096); else _dpmi_unlockregion(ourint9,4096); } } /********************** External interfaces ******************/ /* Disable ^ALT-DEL */ void noreboot() { lockint9(1); oldint9=_dos_getvect(9); _dos_setvect(9,ourint9); } /* Enable ^ALT-DEL */ void okreboot() { lockint9(0); _dos_setvect(9,oldint9); } <a name="003f_0018"> <a name="003f_0019">[LISTING FIVE]
<a name="003f_0019"> /**************************************************************** * DISPLAY.C general purpose Microsoft C text display library * * See also DISPLAY.H DIRPICK.C, and HISTO.C -- Al Williams * ****************************************************************/ #include <stdio.h> #include <dos.h> #include <string.h> #include <stdarg.h> #include "display.h" /* global variable sets color of output */ int color=7; /* primary colors for color monitor (see display.h) */ int colors[4]={ 0x1e, 0x70, 0x1c, 0x7e }; /* 1=mono monitor, 0=color, -1=unknown */ int mono=-1; /* set video mode and detect mono monitor this should always be called first */ void vidmode() { union REGS r; if (mono<0) { r.h.ah=0xf; int86(0x10,&r,&r); mono=r.h.al==7; } r.x.ax=mono?7:3; int86(0x10,&r,&r); r.x.ax=0x500; int86(0x10,&r,&r); } /* goto point x,y (from 0-79 and 0-24) */ void goxy(int x,int y) { union REGS r; r.h.ah=2; r.h.dh=y; r.h.dl=x; r.h.bh=0; int86(0x10,&r,&r); } /* clear screen region */ void clears(int x0, int y0,int x1,int y1) { union REGS r; r.x.ax=0x600; r.h.bh=color; r.h.ch=y0; r.h.cl=x0; r.h.dh=y1; r.h.dl=x1; int86(0x10,&r,&r); goxy(0,0); } /* get x,y position */ void getxy(int *x,int *y) { union REGS r; r.h.ah=3; r.h.bh=0; int86(0x10,&r,&r); *x=r.h.dl; *y=r.h.dh; } /* write count characters -- handle \r, \n, backspace, and \a updates the cursor if count==1 (w/o line wrap) otherwise, the cursor doesn't move */ void writecc(int c,int count) { union REGS r; /* PS/2 BIOS tries to print 0 characters! */ if (count<=0) return; /* if bell character... */ if (c=='\a') { /* use function 0eH to do count bells */ while (count--) { r.x.ax=0xe00|'\a'; r.x.bx=0; int86(0x10,&r,&r); } return; } /* if regular character (not \n or \r or bs) */ if (c!='\n'&&c!='\r'&&c!=8) { /* print regular character */ r.h.ah=9; r.h.al=c; r.h.bh=0; r.h.bl=color; r.x.cx=count; int86(0x10,&r,&r); /* if count isn't 1 return else do cursor update NOTE: \n \r always update cursor */ if (count!=1) return; } /* get cursor position */ r.h.ah=3; r.h.bh=0; int86(0x10,&r,&r); /* if \r, zero x coordinate Note that 100 \r's is the same as 1 */ if (c=='\r') r.h.dl=0; /* if \n, increment y coordinate by count */ else if (c=='\n') r.h.dh+=count; /* if backspace back up by count or to start of line */ else if (c==8) r.h.dl-=r.h.dl>count?count:r.h.dl; else /* bump x coordinate. Assume it won't wrap over */ r.h.dl++; r.h.ah=2; int86(0x10,&r,&r); } /* write a string using writec, a writecc macro (see display.h) */ void writes(char *s) { while (*s) writec(*s++); } /* printf using writecc max length 99 */ int printfc(char *fmt,...) { int rc; char outbuf[100]; va_list aptr; va_start(aptr,fmt); rc=vsprintf(outbuf,fmt,aptr); writes(outbuf); return rc; } /* prompt for single key @ coordinates x,y use str as prompt, resp is valid keys, pcolor is the color to use (0 for same color). Alpha characters in resp should be upper case. If resp is "" then all characters are valid. If resp is NULL then any alpha character is valid. returns: -1 if ESC pressed index of character if resp is valid character if resp is NULL or "" */ int prompt_at(int x, int y, char *str,char *resp,int pcolor) { int ocolor,c; char *index; goxy(x,y); ocolor=color; if (pcolor) color=pcolor; /* clear to end of line */ clreol(); writes(str); while (1) { /* get key */ c=getch(); if (!c) { /* ignore extended keys */ getch(); continue; } /* if esc quit */ if (c==27) break; /* shift upper */ c=toupper(c); /* if resp in not null, check it */ if (resp&&(index=strchr(resp,c))) break; /* if resp is null, check for alpha */ if (resp==NULL&&isalpha(c)) break; /* if resp=="" then anything is OK */ if (resp&&!*resp) break; } color=ocolor; goxy(x,y); clreol(); curshide(); return c==27?-1:resp&&*resp?index-resp:c; } /* prompt for input @x,y. Prompt with promptstr valid is a string of valid input characters (if NULL, all characters are OK., clr is the color (0 for same), len is the input length, buf is the buffer (should be at least len+1 long, and help is an optional help string (use NULL for the default help). returns: -1 if ESC # of characters input otherwise You can use the backspace key to edit entries */ int ask_at(int x, int y, char *promptstr,char *valid, int clr,int len,char *buf,char *help) { int count=0,c,ocolor=color; char *bp=buf; /* clear buffer */ memset(buf,0,len+1); /* set color, goto input line, and clear it */ if (clr) color=clr; goxy(x,y); clreol(); /* write prompt */ writes(promptstr); /* main loop */ while (1) { /* get a character. Extended keys are <0 */ c=getch(); if (!c) c=-getch(); /* handle backspace */ if (c==8) { if (bp!=buf) { bp--; writec(8); writec(' '); writec(8); *bp='\0'; count--; } continue; } /* Escape or enter ends input */ if (c=='\r'||c==27) { /* restore color */ color=ocolor; /* clear line */ goxy(x,y); clreol(); curshide(); /* return */ return c==27?-1:count; } /* If F1 give help */ if (c==-59) { vidsave(); prompt(help?help: " Use <ENTER> to accept, <ESC> to quit, <Backspace>" " to correct.","",SOCOLOR); vidrestore(); continue; } /* ignore other extended keys (c<0) or regular keys if at input limit (count==len) */ if (count==len||c<0) continue; /* if not valid character, ignore */ if (valid&&!strchr(valid,c)) continue; /* echo input character */ writec(c); /* store in buffer */ *bp++=c; /* update count */ count++; } } /* routines to save and restore the video context */ /* places to save things */ static char vbuf[4096]; static int save_xy,save_color; /* save video */ void vidsave() { union REGS r; save_color=color; r.h.ah=3; r.h.bh=0; int86(0x10,&r,&r); save_xy=r.x.dx; memcpy((void *)vbuf,(void *)(mono?0xb0000:0xb8000),4096); } /* restore video */ void vidrestore() { union REGS r; memcpy((void *)(mono?0xb0000:0xb8000),(void *)vbuf,4096); color=save_color; r.h.ah=2; r.h.bh=0; r.x.dx=save_xy; int86(0x10,&r,&r); } <a name="003f_001a"> <a name="003f_001b">[LISTING SIX]
<a name="003f_001b"> /****************************************************************** * TIMING.C - simple non-rigorous benchmark for Phar Lap's * * 286 | DOS Extender & Intel 386 CodeBuilder * * Compile with: ICC timing.c graphics.lib (386 protected mode) * * OR: * * CL -AL -Lp -G2 -Ox timing.c graphp.obj llibpe.lib graphics.lib * * (286 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