C PROGRAMMING

The Surrogate Library

Al Stevens

Several readers have asked why I selected Turbo C for the "C Programming" column project. Many of you use other compilers, and some of you feel left out. One such reader goes so far as to say that the next several issues of DDJ will be useless to him, and he will likely allow his subscription to expire. That got my attention.

All this distress is because the programs being discussed are written with the specific screen management functions of Turbo C. This particular reader felt that the column should restrict itself to generic ANSI C whenever possible to reach the widest possible audience. That argument has merit, and I do not want to lose readers, particularly those who care enough to tell me when things are not right. I must, therefore, deal with this concern.

First, let's consider why generic C is not practical for this project. The program uses our own home-grown video windows on the IBM PC and compatibles. Such a program must, if performance is to be considered, directly address video memory. The program will also use the PC's serial port. These requirements bind the program to the PC hardware architecture. Of course, most of the many C compilers for the PC could be used to develop such a program, and each such program would be somewhat different from the others. The methods that you use for low-level hardware access are different enough with each compiler that different programs will result. Perhaps a need exists for a standard library for MS-DOS PC programs, but no such standard is in acceptance. Some compilers attempt to be compatible with others, but these attempts fall apart when the compiler developers are working on similar extensions at the same time. Witness the different approaches to graphics libraries in Turbo C and Microsoft C.

The news, however, is not all bad. A relatively small amount of the code in our project is specific to and dependent upon Turbo C. All such code is in the area of screen management, and is represented by a set of Turbo C functions and a few internal Turbo C constructs. Most of the rest of the code is generic at least at the PC level. Microsoft C and Turbo C do a lot of things the same way. Remember, however, that the underlying theory that applies to our video windows is based on the PC's video architecture; the functions that we are developing are for the PC with MS-DOS. They are not CP/M, Unix, or VAX/VMS programs, for example. This fact could give rise to a new uproar, and I reluctantly wait for that heat and will try bravely to bear up under it.

Why did I chose Turbo C? The answer is simple: Turbo C is the compiler I use for nearly everything else in MS-DOS. That is not so much an endorsement as a mere fact. I use Turbo C because it has excellent support for the kind of programs I write: TSRs and programs with video windows. Microsoft C has some support for those areas, but not as much as Turbo C.

I might have selected Microsoft C for the project, in which case I would be addressing this same discourse to the users of all the other compilers. I like Microsoft C. Recently my work has extended into the OS/2 arena, and that venture has me using Microsoft C once again. All that great Turbo C support does not apply to OS/2. First, Turbo C does not come in an OS/2 flavor. Second, one does not write the same kind of TSRs for OS/2 that one writes for MS-DOS. Third, one does not do windows in OS/2; that will be the domain of screen groups and the Presentation Manager, like it or not.

The TC to MSC Surrogate Library

What good does all this rationalization do for those of you dear readers who do not use Turbo C and who want to use the code in the "C Programming" column? None, unfortunately, and that is something I intend to partially rectify in this column. This month I will provide a library of Microsoft C functions and macros that turn the "C Programming" project into a Microsoft C-compatible program. The library will mirror those. Turbo C-specific functions that I have used so that you can compile the project's functions with Microsoft C. In subsequent months I will add to this library (if need be).

How about the other compilers? At last count there were countless other MS-DOS C compilers. Lattice, Aztec, Watcom, Zortech, MIX, High C, Whitesmith's, Mark Williams, De Smet, QC88, Small C, Eco-C, are all names that come to mind. At one time or another, I have used each of them. I do not, however, intend to provide a compiler-independent program here. I will describe the Turbo C functions that must be emulated for Microsoft C, and I will provide the source code for that purpose. Users of those other fine compilers may use my example to build their own libraries. I am doing it for Microsoft C because that will satisfy the largest base of readers, and that wheel is squeaking rather prominently just now, thus getting the grease. To take this same side trip for all those compilers would use my allotted space in DDJ for about the next year, and we wouldn't get anything else done.

This library development is not a big effort as you will see, but I do encourage anyone who builds one for another compiler to send the code in; we'll post it on CompuServe in the DDJ Forum. But let me caution you. In the past I wrote and marketed a library of C functions, and I distributed versions that worked with most of those compilers. It was a frustrating experience. The effort required to keep up with the latest versions of the compilers was significant. The targets would never hold still. Eventually I surrendered and distributed only source code. The source code was a melange of compile-time conditionals that managed the difference between compilers and, in doing so, obscured the original meaning of the code. Such a mess is marginally acceptable in a commercial product, but it should never be seen in a published work where the purpose is as much to inform as to perform.

I built these surrogate functions and tested them with Microsoft C, Versions 5.0 and 5.1. I tried to get them running with QuickC as well, but the window.c file causes an internal QuickC compiler error. I recompiled just that module with Microsoft C, linked it with the rest of the program as compiled with QuickC, and everything worked. A more recent version of QuickC might not have this problem, but I have only the first version.

The library consists of three source files and a make file. The source files are microsft.h (see Listing One, page 129), microsft.c (Listing Two, page 129), and vpeek.asm (Listing Three, page 131). The make file is twrp.mak (Listing Four on page 132). The make file uses these new functions to build last month's TWRP tiny word processor with Microsoft C. The microsft.h file is intended to be appended to or included in the window.h file from my September column. The microsoft.c file provides the surrogate functions that emulate those features of the project that depend on things unique to Turbo C.

These emulations are not comprehensive -- some of them will not work as Turbo C substitutes for everything you might do. Rather, they provide sufficient support for the ways we used those functions in the "C Programming" project so far. For this reason I call them surrogates rather than clones. Naturally, as I add to the project, I will verify that all the new code works with both compilers, and I will update this library if necessary. If you want a comprehensive cross-compiler library, you might try buying the Turbo C runtime library source code and compiling the pertinent functions with Microsoft C. Such an ambitious endeavor is beyond the scope of this column. I need say no more than that it would be a significant effort, fraught with peril.

Following is a discussion of each component of the library.

microsft.h -- The microsft.h file is meant to be appended to or included in window.h from September. You must make one change to window.h to accommodate differences between the two compilers' treatments of prototypes. In window.h on about line 9 you will see this prototype:

  int select_window(int,int,int,int
                     (*func)(int,int));

You must change it to the following:

  int select_window(int,int,int,nt
                           (*)(int,int));

Microsoft C does not allow you to mix named and unnamed parameters in a prototype while Turbo C does not seem to mind.

The microsft.h file begins with a test of the COMPILER compile-time conditional flag. This flag is defined by the compile statement in the make file, and it tells the compiler to use the code inserted from microsft.h. My objective was to minimize changes to code already published.

The #define statements for movmem and setmem substitute the names of appropriate MSC functions.

The setmem function sets all the bytes in a buffer to a specified character value. The first parameter is a character pointer that points to the buffer. The second parameter is the buffer's size in bytes. The third parameter is the character value to be filled into the buffer. The corresponding Microsoft C function is memset. Its parameters are in a different sequence than those of Turbo C's setmem.

The movmem function takes three parameters: a source character pointer, a destination character pointer, and a byte count. The function moves a block of memory and accounts for overlapping source and destination blocks, moving from the correct end of the buffer to prevent byte replication. The corresponding Microsoft C function is memmove. Its parameters are in a different sequence than those of movmem. Note that Microsoft C has a movmem function that resembles Turbo C's movmem, but it does not correct for overlapping buffers and cannot be used here.

The #define statements for cprintf, cputs, getch, and putch substitute the names of functions in microsft.c for these names. Both Turbo C and MSC have functions with these names, but their behaviors are different enough that we must make substitutions.

Next come the prototypes for the functions of TC that MSC does not have. After that are #define statements for the screen colors.

microsft.c -- This file has the functions that emulate the Turbo C functions -- the Turbo surrogate. It starts with some #includes and prototypes. The vpeek and vpoke prototypes are for the functions in vpeek.asm, which read and write video memory compensating for video snow. If your system does not use the color graphics adaptor (CGA), you do not need the assembly language functions.

The compile-time global symbol, ADAPTOR, specifies the video adaptor your program uses, and the VSEG symbol is automatically equated to the segment address of display refresh memory, which is 0xb000 for the monochrome display adaptor and 0xb800 for the others. The SNOW symbol is set to 0 if the adaptor does not generate video snow when video memory is accessed. Otherwise it is set to 1. If you use an adaptor other than the CGA, you can remove the references to vpeek.asm and vpeek.obj from Listing Four, twrp.mak.

With Turbo C the tests for the video segment and snow are made at run time by the compiled code and are not visible to you. For this subset emulation, however, you must specify the video adaptor when you compile the program. This is consistent with the philosophy of this program where configuration items are controlled by compiled #define control statements.

The video structure is a duplicate of an external structure that is internal to Turbo C and that is referenced in window.c. We declare it here because Microsoft C has no such structure. By maintaining the values that our software uses, we can make the program react just as it does when the Turbo C run-time library (RTL) is running things.

The window function is used to establish a rectangle of memory as the current video window. Its integer parameters are the left, top, right, and bottom screen coordinates where the upper left screen coordinate is row 1, column 1, and the lower right coordinate is row 25, column 80.

The _vptr function returns a far pointer to video memory based on the x and y coordinates passed to it. This is an emulation of an external Turbo C function that is normally only called from within the Turbo C run-time library but that we used in window.c. You'll recall that I chastised myself for using it. Now I pay.

The _vram function writes a linear block of program memory to video memory. Its parameters are a far pointer to the start of the video memory location, a far pointer to the program memory buffer, and the number of 2-byte integers to write. Video memory consists of 2 bytes -- a video attribute and an ASCII character -- for each screen character position. This function is also an emulation of one that is normally only called from the TC RTL. We used it in window.c to effect a smooth write to the screen without the annoyance of a cursor flash across the write such as you see when you use cprintf and cputs.

The _getvram function is the reverse of _vram. It reads rather than writes video memory. This function is not an emulation, but one that we need for the gettext function described below. In Turbo C, the _vram function manages video memory moves in both directions by using the segment addresses to determine if the source or destination is the video RAM. Rather than go to that trouble, I coded a simple _vram and then added _getvram as a reciprocal of _vram.

The gettext and puttext functions are higher-level read and write video memory functions. They deal with linear buffers of program memory and rectangular windows of video memory. They are used to save and restore the video space that our windows occupy. The coordinates (left, top, right, bottom) are relative to the full screen and begin with 1,1 at the top left. The movetext function moves a video window. We use it for window scrolling, so this emulated function works with vertical movements only. The Turbo C movetext function is a lot smarter, being able to move a window from and to any screen positions. The arguments to movetext are the four corner coordinates of the original window (relative to the full screen) and the upper left coordinates of where it is to be moved. If you are using the CGA, you can eliminate movetext because we used BIOS to scroll CGA screens for performance reasons. To avoid changing the scroll function in window.c, put a null function named movetext in microsft.c in place of the one given here.

The gotoxy function positions the cursor at a location in the current window (the one most recently defined by the window function) as specified by the x and y parameters. These parameters are relative to the window, with 1,1 being the upper left corner of the window.

The wherex and wherey functions return the current cursor x and y positions relative to the current window.

The textcolor and textbackground functions set the colors that will be used the next time a text display is written. Their parameters are integer values as defined in microsft.h.

The wprintf function is substituted for cprintf. Both compilers have cprintf functions that are similar, but they are both related to their own internal text display processes. The cprintf macro in microsft.h replaces calls to cprintf with ones to wprintf. This new function makes a simple translation using the vsprintf function of Microsoft C. The resulting string is then copied to video memory based on the current cursor position. Note that this surrogate function does not attempt to deal with control characters such as \n and \r. This omission is because we never use such controls in our calls to cprintf. I hope I do not regret this shortcut later.

We have substituted wgetch and wputch for getch and putch because the existing Microsoft C functions do not work in the context in which we are using them.

vpeek.asm -- The vpeek function has two unsigned parameters. The first is the segment address of video memory (always the value #defined as VSEG) and the second is the video offset address. The function returns the 2-byte value in the video memory address. Before accessing video RAM, vpeek waits for a video retrace cycle to avoid the snow-causing memory access conflict between the CPU and the video controller.

The vpoke function inserts a 2-byte value into a video memory location with the same snow-eliminating routine as vpeek. It has the same two video memory address parameters as vpeek. Its third parameter is the two-byte word to be inserted into video memory.

Header Files

Turbo C and Microsoft C define their function prototypes in header files in mostly the same way. The same header files define the same ANSI standard functions. There are two minor differences, however. Turbo C defines all the memory allocation functions in alloc.h and Microsoft C uses malloc.h. Turbo C uses mem.h for prototypes of functions similar to those that Microsoft C puts in memory.h. To get around this problem, you can put surrogate alloc.h and mem.h files in with your Microsoft C header files. The alloc.h file should simply #include malloc.h. The mem.h will #include memory.h. I have not included listings of these one-liners.

A C Crotchet: The ANSI Gotcha

In its goal to specify a standard C language that is all things to all computers, the ANSI X3J11 committee has faced some thorny problems. Their solutions do not always provide the best answer for everyone. Once upon a time, if you coded this statement:

  char cp [] = "\00123";

you got a character array of four characters initialized with three characters and a null terminator. The first character was the binary value 001 as specified by the backslash octal sequence. The next two characters were the ASCII values '2' and '3'.

The draft ANSI specification says that since some computers have character sizes of greater than eight bits, the backslash sequence must allow for more than three digits. Therefore, the statement will now declare a two-character array with the octal value 123 (hex 53) followed by the null terminator. The compiler's scan of digits to form an integral value continues as long as the compiler sees octal digits.

Existing code can get broken by compilers that comply, particularly if the array is compiled without warning messages. ANSI makes no provision for the protection of existing code with the changes brought about by this new rule. This discussion is, therefore, aimed at those who might be changing to an ANSI-compliant compiler. Eventually that will include all of us. This will be part of the future ANSI legacy, but let's see how it is effecting some of us right now.

Borland decided to comply with this new rule of the standard in Turbo C 2.0. This decision, I am told, was based on Borland's commitment to full compliance with the ANSI standard. That position is hard to argue with even when the consequences seem dire. Allow me to try.

When programmers complained that this new rule was breaking existing code, they were offered a workaround: Use the ANSI string concatenation feature, which works like this:

  char *cp = "\001" "123";

This, of course, is a workaround for developers of new code and does not address the problem faced by those who compile large systems of existing code. ANSI addressed this rule to solve portability issues. Some machines have 6-bit characters, some have 8, some have 12. Traditionally, when a programmer ported C code to a new machine, this was one of the portability considerations. Now, however, the new ANSI rule -- or more specifically, the Borland compliance with it -- gives us an unexpected portability issue and an unwanted surprise. Programs that compiled correctly one way for years -- including with Turbo C 1.5 -- are now not portable to Turbo C 2.0, and the compiler issues no warning and provides no way to turn off the new rule.

The only error message associated with the original format occurs when coincidental octal digits following the octal constant happen to form an integral value greater than 255. So if the initializer contains "\00377", the compiler says your code is correct but compiles something other than what ten years of tradition have conditioned us to expect. If, however, the string is "\00400" you get a compile error. In the latter circumstance you can do something about it. In the former, you have no clue that something is amiss until the program stops working.

In my opinion, a compiler should issue a warning when it deviates from tradition in the name of ANSI. The warning could be turned off by those who do not need or want to see it.

Perhaps compilers for computers with 8-bit characters should ignore the new rule. I doubt that many users would complain. That's right, your DDJ C columnist advocates nonviolent civil disobedience sometimes. As Paul Newman said in the movie Hud, "I've always believed in being lenient with the law. Sometimes I lean one way, sometimes I lean the other."

Most PC programs, however, are written to execute from inside a user interface that is tightly bound to the architecture of the PC. Our "C Programming" column project is an example of such a program. The vast majority of programs written in Turbo C will never be ported anywhere other than to the next improved edition of Turbo C. I would guess that if Borland were to offer an ANSI-only version of the compiler, there would be few takers.

Borland's official position on this issue is that the compiler exhibits correct ANSI behavior, and is, therefore, correct. On the other hand, Borland is a company that listens to its users. If enough of you say that you need something changed, they will change it. I say we need a warning message.

Since version 1.0, I have recommended Turbo C without hesitation. Until this problem is addressed, however, I suggest caution if Turbo C 2.0 is to be used in projects that involve large helpings of existing code. Fortunately, Turbo C 1.5, Microsoft C 5.1, and other compilers have not adopted this rule, so you have reasonable alternatives to Turbo C 2.0 if this new rule will be a problem.

Other Turbo C 2.0 Offerings

Turbo C 2.0 has a bounty of new features. Most notable is the long-awaited integrated debugger in the environment. Borland now markets a standalone Turbo Debugger as well, and Turbo C programs can be debugged with it, too. If you get the Turbo C Professional package, you get Turbo C 2.0, the Turbo Debugger, and the new Turbo Assembler. Also included are the linker, librarian, make utility, grep, and a host of other programmer's utility programs. All that is missing is a multi-window, programmable programmer's editor in the class of Brief or the Microsoft Editor.

One new feature of Turbo C 2.0 is a mixed blessing. Each object file is encoded with the paths and names of the #include files that went into its compilation. The Project Make facility has an option called "Auto dependencies." When this option is on, the make process checks the dates of the files that were included against the date of the object file. This is useful in projects where the project make files might not be current. With this feature you can get pretty sloppy about keeping the project make file up to date.

Why a mixed blessing? The embedded file names can increase the size of the object file significantly. One developer found that his commercial library now required additional diskettes. There is an undocumented TLIB switch (/0) for eliminating these strings. My sources say that Borland will post a utility to strip the path names from object files and that the next release of Turbo C will include a switch to suppress them. Now that I have them, though, I cannot do without them. I would like to see them used by the command line MAKE utility program.

Bugs in the version 1.5 cprintf and cputs functions were fixed in version 2.0. What's that, you didn't know those functions were broken? Then you, like I, did not read the documentation, which has always said that cprintf and cputs do not expand the newline into a carriage return, line feed. That's what the documentation said, but in TC 1.5, the new-line was expanded. In 2.0 you need to code \r\n to get the same effect you got with \n before. This keeps the compiler in synch with its documentation and also with Microsoft C. TC will need to stay close to MSC with many such functions if they intend to get into the OS/2 game. Many programmers used cprintf the way it worked rather than how it was described and are not pleased with the change.

Coming up...

Next month we get on with the project. We'll discuss the weighty subject of asynchronous communication and add serial port and modem functions to our library.

_C PROGRAMMING_ by Al Stevens [LISTING ONE]




/* -------------- microsft.h ---------------- */
/* #include this file at the end of window.h */

#if COMPILER == MSOFT

/* this line replaces the select_window prototype in window.h */
int select_window(int, int, int, int (*)(int,int));

#define setmem(bf,sz,c) memset(bf,c,sz)
#define movmem(fr,to,ln) memmove(to,fr,ln)
#define cprintf wprintf
#define cputs(s) wprintf(s)
#define putch(c) wputch(c)
#define getch() wgetch()

void window(int lf,int tp,int rt,int bt);
void puttext(int lf,int tp,int rt,int bt,char *sv);
void gettext(int lf,int tp,int rt,int bt,char *sv);
void movetext(int lf, int tp, int rt, int bt, int lf1, int tp1);
void gotoxy(int x,int y);
void textcolor(int cl);
void textbackground(int cl);
int  wherex(void);
int  wherey(void);
void wprintf(char *, ...);
void wputch(int c);
int wgetch(void);

#define BLACK         0
#define BLUE          1
#define GREEN         2
#define CYAN          3
#define RED           4
#define MAGENTA       5
#define BROWN         6
#define LIGHTGRAY     7
#define DARKGRAY      8
#define LIGHTBLUE     9
#define LIGHTGREEN   10
#define LIGHTCYAN    11
#define LIGHTRED     12
#define LIGHTMAGENTA 13
#define YELLOW       14
#define WHITE        15

#endif






[LISTING TWO]


/* ----------- microsft.c ------------ */

/*
 *  Surrogate Turbo C functions
 *  for Microsoft C users.
 */

#include <dos.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <conio.h>
#include <bios.h>

/* -------- One of these is your Display Adapter -------- */
#define MDA 1           /* Monochrome Display Adapter */
#define CGA 2           /* Color Graphics Adapter     */
#define EGA 3           /* Enhanced Graphics Adapter  */
#define VGA 4           /* Video Graphics Array       */

#define ADAPTER EGA     /* Specifies the Display Adapter */

#if ADAPTER==MDA
#define VSEG 0xb000     /* VSEG is the video memory segment */
#else
#define VSEG 0xb800
#endif

#if ADAPTER==CGA
#define SNOW 1
/* --- assembly language vpeek.asm: manages CGA flicker --- */
void vpoke(unsigned adr, unsigned off, int ch);
int vpeek(unsigned adr, unsigned off);
#else
#define SNOW 0
/* ---- macros for vpeek and vpoke for non-CGA systems ---- */
#define MKFP(s,o)    (((long)s<<16)|o)
#define vpoke(a,b,c) (*((int  far*)MKFP(a,b))=c)
#define vpeek(a,b)   (*((int  far*)MKFP(a,b)))
#endif

static union REGS rg;

/* --- a structure defined within Turbo C and used by us --- */
struct {
    char filler1[4];
    char attribute;     /* saves the current video attribute */
    char filler2[5];
    char snow;          /* says if the adapter snows */
} _video;

static int wlf,wtp,wrt,wbt; /* current window corners */
static int wx,wy;           /* current window cursor  */

/* ------- define a video window ---------- */
void window(int lf,int tp,int rt,int bt)
{
    wlf = lf;
    wtp = tp;
    wrt = rt;
    wbt = bt;
    _video.snow = (char ) SNOW;
}

/* ------ makes a video offset from x,y coordinates ----- */
#define vaddr(x,y) (((y)-1)*160+((x)-1)*2)

/* -- makes far pointer to video RAM from x,y coordinates -- */
void far * pascal __vptr(int x, int y)
{
    void far *vp;

    FP_SEG(vp) = VSEG;
    FP_OFF(vp) = vaddr(x,y);
    return vp;
}

/* ---- writes a block of memory to video ram ----- */
void pascal __vram(int far *vp, int far *bf, int len)
{
    while(len--)    {
        vpoke(VSEG, FP_OFF(vp), *bf++);
        vp++;
    }
}

/* ---- gets a block of memory from video ram ----- */
void pascal __getvram(int far *vp, int far *bf, int len)
{
    while(len--)    {
        *bf++ = vpeek(VSEG, FP_OFF(vp));
        vp++;
    }
}

/* ----- writes a memory block to a video window ----- */
void puttext(int lf,int tp,int rt,int bt,char *sv)
{
    while (tp < bt+1)   {
        __vram(__vptr(lf, tp), (int far *) sv, rt+1-lf);
        tp++;
        sv += (rt+1-lf)*2;
    }
}

/* ----- reads a memory block from a video window ------ */
void gettext(int lf,int tp,int rt,int bt,char *sv)
{
    while (tp < bt+1)   {
        __getvram(__vptr(lf, tp), (int far *) sv, rt+1-lf);
        tp++;
        sv += (rt+1-lf)*2;
    }
}

/* ------ moves a video window (used for scrolling) ------ */
void movetext(int lf, int tp, int rt, int bt, int lf1, int tp1)
{
    int nolines = bt - tp + 1;
    int incr = tp - tp1;
    int len, i;
    unsigned src, dst;

    if (tp > tp1)   {
        src = tp;
        dst = tp1;
    }
    else    {
        src = bt;
        dst = tp1+nolines-1;
    }
    while (nolines--)   {
        len = rt - lf + 1;
        for (i = 0; i < len; i++)
            vpoke(VSEG, vaddr(lf1+i, dst),
                vpeek(VSEG,vaddr(lf+i, src)));
        src += incr;
        dst += incr;
    }
}

/* ----- position the window cursor ------ */
void gotoxy(int x,int y)
{
    wx = x;
    wy = y;
    rg.h.ah = 15;
    int86(16, &rg, &rg);
    rg.x.ax = 0x0200;
    rg.h.dh = wtp + y - 2;
    rg.h.dl = wlf + x - 2;
    int86(16, &rg, &rg);
}

/* ----- return the window cursor x coordinate ----- */
int wherex(void)
{
    return wx;
}

/* ----- return the window cursor y coordinate ----- */
int wherey(void)
{
    return wy;
}

/* ----- sets the window foreground (text) color ------- */
void textcolor(int cl)
{
    _video.attribute = (_video.attribute & 0xf0) | (cl&0xf);
}

/* ----- sets the window background color ------- */
void textbackground(int cl)
{
    _video.attribute = (_video.attribute & 0x8f) | ((cl&7)<<4);
}

void writeline(int, int, char *);

/* ------ our substitution for MSC cprintf -------- */
void wprintf(char *ln, ...)
{
    char dlin [81], *dl = dlin, ch;
    int cl[81], *cp = cl;
    va_list ap;

    va_start(ap, ln);
    vsprintf(dlin, ln, ap);
    va_end(ap);

    while (*dl) {
        ch = (*dl++ & 255);
        if (!isprint(ch))
            ch = ' ';
        *cp++ = ch | (_video.attribute << 8);
    }
    __vram(__vptr(wx+wlf-1,wy+wtp-1),
            (int far *) cl, strlen(dlin));
    wx += strlen(dlin);
}

/* ------ our substitution for MSC putch -------- */
void wputch(c)
{
    if (!isprint(c))
        putch(c);
    wprintf("%c", c);
}

/* ------ our substitution for MSC getch -------- */
int wgetch(void)
{
    static unsigned ch = 0xffff;

    if ((ch & 0xff) == 0)   {
        ch++;
        return (ch >> 8) & 0x7f;
    }
    ch = _bios_keybrd(_KEYBRD_READ);
    return ch & 0x7f;
}





[LISTING THREE]


;---------------------------  vpeek.asm  ----------------------------
        dosseg
        .model compact
        .code
        public  _vpoke
; -------- insert a word into video memory
;   vpoke(vseg, adr, ch);
;   unsigned vseg;    /* the video segment address     */
;   unsigned adr;     /* the video offset address      */
;   unsigned ch;      /* display byte & attribute byte */
; ------------------------------------------
_vpoke  proc
        push    bp
        mov     bp,sp
        push    di
        push    es
        mov     cx,4[bp]    ; video board base address
        mov     es,cx
        mov     di,6[bp]    ; offset address from caller
        mov     dx,986      ; video status port address
loop1:  in      al,dx       ; wait for retrace to quit
        test    al,1
        jnz     loop1
loop2:  in      al,dx       ; wait for retrace to start
        test    al,1
        jz      loop2
        mov     ax,8[bp]    ; word to insert
        stosw               ; insert it
        pop     es
        pop     di
        pop     bp
        ret
_vpoke  endp

        public  _vpeek
; -------- retrieve a word from video memory
;   vpeek(vseg, adr);
;   unsigned vseg;    /* the video segment address */
;   unsigned adr;     /* the video offset address  */
; ------------------------------------------
_vpeek  proc
        push    bp
        mov     bp,sp
        push    si
        push    ds
        mov     si,6[bp]    ; offset address
        mov     cx,4[bp]    ; video board base address
        mov     ds,cx
        mov     dx,986      ; video status port address
loop3:  in      al,dx       ; wait for retrace to stop
        test    al,1
        jnz     loop3
loop4:  in      al,dx       ; wait for retrace to start
        test    al,1
        jz      loop4
        lodsw               ; get the word
        pop     ds
        pop     si
        pop     bp
        ret
_vpeek  endp
        end





[LISTING FOUR]


#
#  TWRP.MAK -- make file for TWRP.EXE with Microsoft C/MASM
#

.c.obj:
    cl /DCOMPILER=MSOFT -c -W3 -Gs -AC $*.c

twrp.obj : twrp.c editor.h help.h window.h

editshel.obj : editshel.c editor.h menu.h entry.h help.h \
                window.h microsft.h

editor.obj : editor.c editor.h window.h microsft.h

entry.obj : entry.c entry.h window.h microsft.h

menu.obj : menu.c menu.h window.h microsft.h

help.obj : help.c help.h window.h microsft.h

window.obj : window.c window.h microsft.h

microsft.obj : microsft.c

vpeek.obj : vpeek.asm
    masm /MX vpeek;

twrp.exe : twrp.obj editshel.obj editor.obj entry.obj menu.obj help.obj \
        window.obj microsft.obj vpeek.obj
    cl twrp editshel editor entry menu help window microsft vpeek






Copyright © 1989, Dr. Dobb's Journal