When you need more speed and greater capacity
This article contains the following executables: DUDLEY.LST
Bill is director of engineering for Design Computation Inc., a vendor of CAD software for printed circuit design. Bill has a masters in electrical engineering from Cornell University. When not programming for profit or fun, he can be found riding his Norton Commando (on nice days) or working on that or one of his other bikes (on less nice days). He can be reached at RD5, Box 239, Jackson, N. J. 08527.
As one of the programmers at a developer of CAD software for printed circuit-board design, I was assigned the job of porting our CAD programs from Microsoft C 4.0 to an 80386 protected-mode compiler. After making a quick study of the three available compilers that support the 80386 -- NDP from Microway, High-C 386 from MetaWare, and Watcom 7.0/386 from Watcom -- we chose the Watcom compiler. This article describes the problems and solutions we encountered in the process of porting 75,000 lines of C.
We had two programs to port, the autorouter and the graphical editor. The autorouter, called, oddly enough, ROUTER, was the first candidate for porting for two reasons: It was written by a very small team so it was a lot "cleaner," and because it was not interactive, the speed of its graphics output was, for the time being, not important. The second program to port was the graphical editor, Draftsman-EE (called DM). In this case, video display graphics speed is very important, so we expected to spend more development work here.
We had two goals when we began the port: Achieving greater speed and increased capacity. First of all, we figured that accessing data in one large (multi-megabyte) array would be faster than all the rinky-dink calculations we were doing to calculate which EMS page the data was in, and then get it in the real-mode version of our product. Secondly, we felt that the real-mode product was limited by the size of an unsigned int. The protected mode (32 bit) advantage is obvious.
In an attempt to quantify the speed improvement, we simulated it by making a small model version of ROUTER that had no paging to EMS of data elements. It ran about 25 percent faster than the normal real-mode ROUTER, so that was what we expected. (See the "Summary" at the end of this article for the surprising result.)
Moving to an ANSI Compiler
This is probably old news to everybody now, but I thought I'd bring up the ANSI compiler issue and attempt to explain it again for those of you who are out of touch. The big deal in moving to an ANSI compiler is that the function headers change from:
integer_fn(i, f, c) int i; float f; char c; { ... }
to:
int integer_fn(int i, float f, char c) { ... }
This would be harmless enough in itself, except for the following gotcha: Pre-ANSI compilers always promoted floats to doubles in function argument lists. ANSI compilers pass floats as floats if you so declare. And here is what caused me a few interesting hours: The ANSI compiler will pass floats to the function when it is called, but the function will expect doubles when it runs, unless you use the new style function header. The moral of this story is that you may continue to use old function headers in your code, except for functions that expect doubles or floats as arguments, in which case you must use the new style headers.
Increased Memory Usage
An obvious side effect of moving to protected-mode operation is that integers are now 32 bits unless declared "short." The non-obvious result is that structures containing ints grow, memory usage increases, and any code that hardcoded the size of the struct breaks horribly. I know, we shouldn't have hardcoded them, but we did, and we had a reasonably good excuse.
Now I have a hairy preprocessor macro that computes the size of a "data element," which is really the biggest of any of several different structs. Some judicious juggling of the struct contents also reduced the maximum struct size to save some memory.
The Programming Challenges
Our CAD programs required some special services that are not available from DOS. The first one was the ability to talk to a "security device," more popularly known as a "parallel port dongle." This really presented two problems: How to link a real-mode module to a protected-mode program, and how to pass the character string data from the protected-mode application to the real-mode dongle code.
The second service -- not available from DOS -- is device independent video graphics. We solved this problem in our real-mode programs by supplying a family of video driver TSR (terminate and stay resident) programs. Now we needed to figure out how to talk to our video drivers from protected mode.
Talking to Real-Mode Assembly Code Modules
The manufacturer of our dongle, Rainbow Technologies, supplies an assembly language module that talks to the dongle, and can be linked to a Microsoft C program. This enables you to interrogate the dongle from C without getting your hands dirty. A protected-mode program, however, needs some tricks in order to link it with the supplied real-mode module.
The code in Listing One (page 104) is a stripped-down version of the assembly code dongle module. Notice that I chose to pass the arguments directly in the registers instead of on the stack. This is because I have to use Phar Lap's facility for calling real-mode code, which allows me to use the registers easily.
The other noteworthy item is that the data buffer is in the real-mode code segment. This was done because it was simpler than figuring out how to have a real-mode data segment and getting it to link in the proper order.
The code in Listing Two (page 104) is the C module that talks to the assembly module of Listing One. It has two functions: Initializing variables to point to the various parts of the real-mode code and doing the actual call to interrogate the dongle.
Three locations in the real-mode code are directly accessed from protected mode as shown in Example 1. The linker allows us to know the address of anything in the real-mode section, so that gives us the protected-mode version of those addresses. The Phar Lap DOS extender has a function for finding out the equivalent real-mode address of any protected-mode address, so the subroutine pr2real() returns the real-mode address at the start of the real-mode code as a (32 bit) integer. Subroutine real_setup() uses this result to initialize real_addr, real_seg, and real_off.
Example 1: Three locations in the real-mode code are directly accessed from protected mode.
extern char test_string; /* string buffer in real mode module */ extern char end_real, /* end of real mode code */ extern char QUERY_FAR; /* actually a function, but we just want address */
The actual dongle communication occurs in the subroutine COMPUTE(), which uses Phar Lap function 0x2510 to call the real-mode routine QUERY_FAR. Before the call is made, the blk_mv_pr() routine copies the string to be tested to the buffer in the real-mode code segment. Blk_mv_pr() and its sinister twin, blk_mv_rp()are shown in Listing Three (page 105).
Passing Data Between Real- and Protected-Mode Code Sections
The second challenge is that of passing chunks of data between real- and protected-mode code sections. When you request a DOS service, for example writing to the disk, the Phar Lap DOS extender handles this by copying the disk data from your buffer in protected mode to another buffer in real mode, and then running DOS. This happens transparently so that you never know how messy it is.
If you are doing something abnormal, however, you quickly find out how messy it can get. Our video drivers need to exchange data with the application in two cases: Telling the application what the palette of the video board is (so the user can change color assignments) and passing large blocks of pixels to and from the application for screen save/restore operations.
The answer to this is to use the Phar Lap facility for reserving a block of "real-mode" memory (memory guaranteed accessible by a real-mode program). Listing Four (page 105) shows the graphics video module in the application. Subroutine setvmode() initializes the video driver as well as establishing the location (in both real- and protected-address spaces) of the 9-Kbyte buffer we use for communication with the driver. Subroutine rstr_vbuf() restores the screen image from a previously saved file. (Subroutine save_vbuf() is not shown but does exactly the reverse.)
Talking to a Real-Mode Graphics Driver from Protected Mode
The third challenge arose when we wanted to communicate with our video drivers. The actual communication of drawing requests seemed easy enough, because our driver is accessible through its own (software) interrupt. Easy it was, but deadly slow.
This wasn't really a surprise, since our real-mode product had long ago given up using software interrupts for communication with the video driver. In the real-mode product, the video driver returns at initialization time the segment and offset of its main entry point. The application makes a pointer to a function out of this, and calls the driver directly just as if it was part of the application code.
The problem was much worse in the protected-mode product, however. This was due to the overhead of switching the machine from protected to real mode and back again for every vector sent to the video driver. The solution was obvious -- another programming challenge! We would stuff the driver commands in a list, and when the list filled up, throw it over the wall to the real-mode code, which would then call the video driver for each item in the list. This would reduce the overhead of the real protected-mode change by a factor equal to the length of the list.
In Listing Four, notice the two lines:
prot.vidfn.addr[1] = r.x.si; prot.vidfn.addr[0] = r.x.di;
These stuff the address that the driver returns (the address of its entry point) into struct prot for later use by the real-mode list interpreter. Listing Five (page 108), the include file, defines this structure (see Example 2 ). This structure actually occurs twice: Once on the protected side and again on the real-mode side. Whenever the protected-mode code runs the draw list interpreter, it first copies the protected struct over to the real-mode version, and then uses Phar Lap function Ox2510 to call the real mode draw list interpreter.
Example 2: Protected-mode structure
struct { union { int (* p) (); short int addr[2]; /* [0] segment and [1] offset of driver entry point */ } vidfn; short int lp; short int list [LLEN] [6]; } prot;
Listing Six (page 108) is the assembly language module for the protected-mode subroutine kdidraw(), which stuffs draw commands on the draw list. If the draw list fills up, it automatically calls the protected-mode subroutine pdinterp() to empty it.
Listing Seven (page 109) shows the C code of the protected-mode side of the draw list interpreter. Subroutine pdintinit() sets up the registers structs and subroutine pdinterp() calls the real-mode interpreter. (This is very similar to Listing Four, which calls the real-mode dongle module.)
Finally, Listing Eight (page 109) is the assembly code of the real-mode subroutine that "interprets" the draw list. It is just a tight loop that pushes six ints from the list onto the stack and calls the subroutine pointed to by the segment and offset at the beginning of the RAM locations labelled _real. It does this repeatedly until the first integer in the group of 6 is 0. This is the end-of-list marker. The _real location is the real-mode copy of the prot struct.
The assembly language modules were generated by coding the problem in C and then optimizing the assembler output of the compiler. The reason was simply to maximize the speed because the overhead of the video calls was slowing us down.
Before I embarked upon the coding of the protected-mode version of the draw list interpreter, I built up a version in Borland's Turbo C. This enabled me to test the performance as well as debug bits of it with a source debugger before committing to the protected version. The C code from the Turbo C version was not in itself useful for the protected-mode version, but the experience of building and debugging it was worth the time.
Summary
How did all this turn out? Boy, the speed increase really surprised us. The protected ROUTER runs twice as fast as the real-mode product (if the video is turned off). The protected graphics editor, Draftsman-EE, also shows a factor of two improvement for compute bound processes. Video speed appears the same as in the real-mode product, which is probably due to the vectors being generated in real-mode code. The next job is to make a protected-mode video driver to fix that bottleneck. As for increased capacity, no customer has yet tried a job that is greater than 65,535 elements. (The real-mode product will do a 300 IC board, which is pretty big.)
_PORTING C PROGRAMS TO 80386 PROTECTED MODE_ by William F. Dudley, Jr.
[LISTING ONE]<a name="019c_000e">
; William F. Dudley, Jr.
; real mode module callable from protected mode, passes string in
; buffer in code segment.
ss_text SEGMENT BYTE PUBLIC 'CODE' use16
ASSUME CS:ss_text , DS:NOTHING , ES:NOTHING
; The string is pointed to by DS:SI with the length in CX.
; encryption value of string returned in AX.
public QUERY_FAR
QUERY_FAR PROC FAR
CALL QUERY
RET
QUERY_FAR ENDP
QUERY PROC NEAR
;
; here lives the real mode dongle communications code
QUERY ENDP
public _test_string
_test_string db 256 dup (?)
public _end_real
_end_real label byte
ss_text ENDS
END
<a name="019c_000f"><a name="019c_000f">
<a name="019c_0010">
[LISTING TWO]<a name="019c_0010">
/* William F. Dudley Jr.
* module to connect real mode assembly code to prot mode C
*/
#include <stdio.h>
#include <dos.h>
#include "list5.h"
int real_addr; /* real address of start of real mode code */
int real_seg, real_off; /* segment and offset of real_addr */
#pragma aux QUERY_FAR "*"; /* tell Watcom C that no '_' is used */
extern char test_string; /* string buffer in real mode module */
extern char end_real; /* end of real mode code */
extern char QUERY_FAR; /* actually a function, but we just want address */
short COMPUTE(char *str); /* this is the subroutine that does the work */
int pr2real(void); /* initialize value of real_addr, etc. */
void real_setup(void) {
real_addr = pr2real();
real_seg = (real_addr >> 16) & 0xffff ;
real_off = real_addr & 0xffff ;
}
int pr2real(void) {
union REGS r;
struct SREGS sr;
r.x.ax = 0x250f; /* convert prot addr to real addr */
r.d.ebx = 0; /* start of program */
r.d.ecx = (int)&end_real; /* length of real mode stuff */
sr.fs = 0x3c;
sr.es = Ds();
sr.ds = sr.ss = sr.gs = 0x14;
sr.cs = 0x0c;
int386x(0x21, &r, &r, &sr);
if(r.d.cflag) {
fprintf(stderr, "Error in PR2REAL(), can't map address.\n");
fflush(stderr);
exit(1);
}
return(r.d.ecx);
}
short COMPUTE(char *str) {
union REGS r;
struct phregs pr;
unsigned short i;
unsigned j;
r.x.ax = 0x2510; /* call function */
r.d.ebx = real_addr; /* get segment of real mode stuff */
r.x.bx = (int)&QUERY_FAR; /* EBX is address of QUERY_FAR subroutine */
r.d.ecx = 0; /* 0 words on stack */
r.d.edx = (int)≺ /* DS:EDX is address of register struct */
pr.ECX = strlen(str); /* CX is length of string */
i = real_off + (int)&test_string;
j = i + (real_seg<<4); /* calculate address in selector 0x34 */
blk_mv_pr(str, 0x34, j, pr.ECX); /* copy string to buffer in real CS */
r.x.si = i; /* DS:SI points to string */
pr.ES = pr.DS = real_seg;
int386(0x21, &r, &r);
return(r.x.ax);
}
<a name="019c_0011"><a name="019c_0011">
<a name="019c_0012">
[LISTING THREE]<a name="019c_0012">
; William F. Dudley Jr.
; copy from real to protected or vice-versa
; void blk_mov_pr(char *bufadr, unsigned reg_seg, unsigned reg_off, unsigned count);
; type variable is in:
; char *bufadr EAX
; uint reg_off EBX
; uint count ECX
; uint reg_seg EDX
; Transfers COUNT bytes from the buffer (in the current data seg) bufadr
; to address in protected memory at reg_seg:reg_off.
NAME blk_mov
EXTRN __STK:WORD
_TEXT SEGMENT PUBLIC BYTE USE32 'CODE'
ASSUME CS:_TEXT
PUBLIC blk_mov_pr_
PUBLIC blk_mov_rp_
; protected to real
blk_mov_pr_ proc near
pushf
push EDI
push ESI
push ES
jecxz non1
cld
; count is in ECX already
mov ESI, EAX ;bufadr is source
mov ES, DX ;reg_seg is dest (ES:EDI)
mov EDI, EBX ;reg_off is dest
rep movsb
non1: pop ES
pop ESI
pop EDI
popf
ret
blk_mov_pr_ endp
; real to protected
blk_mov_rp_ proc near
pushf
push EDI
push ESI
push ES
push DS
jecxz non2
cld
push DS
pop ES
;count is in ECX
mov EDI,EAX ;bufadr is dest (ES:EDI)
mov DS,DX ;reg_seg is source (DS:ESI)
mov ESI,EBX ;reg_off is source
repe movsb
non2: pop DS
pop ES
pop ESI
pop EDI
popf
ret
blk_mov_rp_ endp
_TEXT ENDS
END
<a name="019c_0013"><a name="019c_0013">
<a name="019c_0014">
[LISTING FOUR]<a name="019c_0014">
/* William F. Dudley Jr. */
#include <stdio.h>
#include <dos.h>
#include <process.h>
#include <io.h>
#include "list5.h" /* dud's driver interface constants */
/* map of real mode link (call buffer) memory:
* rel address name comment
* 0 rcolortable pointer to 128 bytes for color table storage
* 128 qpixel pointer to MAXB (8500) bytes for pixel storage
*/
#define MAXB 8500
int mblocks; /* no of save/restore blocks */
static int ddi_allocated = 0; /* true if initialization has been performed */
static int rcolortable; /* real mode address of colortable (intermode buf) */
static int qpixel; /* real mode address of line storage space */
int nsegment, noffset; /* line storage segment & offset */
static short psegment; /* protected address of pixel buffer */
static int poffset; /* protected address of pixel buffer */
extern int vectnum; /* ddi interrupt vector (usually 0x7A) */
extern void pdintinit(void); /* setup for pdinterp() */
static int blocksizes[100]; /* array of saved blocks for save/rstr scrn */
/* sets video mode and initializes the video parameters */
void setvmode(void)
{
union REGS r;
struct SREGS sr;
int i;
char paltbl[64]; /* driver will dump palette here. */
if(!ddi_allocated) {
r.x.ax = 0x250d; /* get real mode link information */
/* segment regs must all have legal values in them.
* These values are documented in the Phar-Lap Extender manual
*/
sr.fs = 0x3c;
sr.ds = sr.ss = sr.es = sr.gs = 0x14; /* the data "segment" */
sr.cs = 0x0c; /* the code "segment" */
int386x(0x21, &r, &r, &sr);
/* es:edx = protected address of call buffer */
rcolortable = r.d.ebx; /* ebx = real address of call buffer */
poffset = r.d.edx; /* save protected offset to table start */
psegment = sr.es; /* psegment = 0x60 in Phar-World */
qpixel = rcolortable + 128;
reset_lines();
if(r.d.ecx < (MAXB + MAXCOLORS)) { /* ecx = size of buffer */
fprintf(stderr,"real mode buffer isn't big enough: %d\n", r.d.ecx);
abort();
}
}
r.h.ah = KINIT1;
r.x.bx = (rcolortable >> 16) & 0xffff; /* segment of real mode buf */
r.x.cx = rcolortable & 0xffff; /* offset of real mode buf */
r.x.si = r.x.di = 0; /* clear so we can tell if they are set */
int86(vectnum, &r, &r);
/* The registers have various video constants in them. The code that
* uses them is not shown for clarity.
*/
if(!r.x.si && !r.x.di) { /* if driver does not return its address */
fprintf(stderr, "old driver installed, you need current version!\n");
exit(1);
}
prot.vidfn.addr[1] = r.x.si; /* real mode address of video entry */
prot.vidfn.addr[0] = r.x.di;
pdintinit();
listinit();
color_mask = (int)r.h.al - 1;
if(!ddi_allocated) {
/* copy from real to prot bufr */
blk_mv_rp(paltbl, psegment, poffset, 64);
/* copy array of chars to array of ints */
for(i=0 ; i <= color_mask ; i++ ) colortable[i] = (int)paltbl[i];
}
r.h.ah = KINIT2;
r.h.al = 0; /* don't switch modes */
int86 (vectnum, &r, &r);
/* The registers have various video constants in them. The code that
* uses them is not shown for clarity.
*/
mblocks = r.h.dl; /* number of blocks to save screen */
ddi_allocated = TRUE;
}
void reset_lines ()
{
nsegment = (qpixel >> 16) & 0xffff;
noffset = qpixel & 0xffff;
return;
}
/* Restore Video Buffer, returns status */
int rstr_vbuf(void)
{
union REGS r;
int i, l;
int rtncode = 0;
char bbuf[8200];
char *lbuf;
lbuf = (char *)bbuf;
if (vbfnum!=NULL) rtncode=lseek(vbfnum,0L,0); /* beg of file */
else return(OKAY);
r.h.ah = INITDMP; /* init driver */
r.h.al = SWRITE;
#ifdef __386__
r.x.bx = nsegment;
r.x.cx = noffset;
#else
r.x.bx = FP_SEG(lbuf);
r.x.cx = FP_OFF(lbuf);
#endif
int86(vectnum, &r, &r);
/* now restore screen */
for(i = 0 ; i < mblocks ; i++ ) {
rtncode=read(vbfnum, lbuf, blocksizes[i]);
if(rtncode<= 0) return(ERROR);
r.h.ah = KDUMP;
#ifdef __386__
l = (blocksizes[i] < 8192) ? blocksizes[i] : 8192 ;
blk_mv_pr(bbuf, psegment, poffset+128, l); /* copy from prot to real */
#endif
int86(vectnum, &r, &r);
}
return(OKAY);
}
/* clear the draw list */
void listinit(void) {
prot.list[0][0] = 0;
prot.lp = 0;
list_p = prot.list[0];
}
<a name="019c_0015"><a name="019c_0015">
<a name="019c_0016">
[LISTING FIVE]<a name="019c_0016">
/* William F. Dudley, Jr.
* macros for getting to the driver from an application
*/
extern int vectnum;
int Ds(void); /* what is value of DS register */
#pragma aux Ds = \
0x8c 0xd8 /* mov ax, ds */ \
modify [AH AL];
/* register arrangement for Phar-Lap function 0x2510 */
struct phregs {
unsigned short DS;
unsigned short ES;
unsigned short FS;
unsigned short GS;
int EAX;
int EBX;
int ECX;
int EDX;
} ;
#define LLEN 100 /* assembly language module must agree with this */
#ifndef PDINTERP
extern
#endif
struct {
union {
int (* p)(); /* this is for human info only, we never call it */
short int addr[2]; /* [0]seg and [1]off of driver entry point */
} vidfn ;
short int lp;
short int list[LLEN][6];
} prot ;
#ifndef PDINTERP
extern
#endif
short int *list_p;
void listinit(void);
void pdinterp(void);
void kdidraw(short int,short int,short int,short int,short int,short int);
/* tell Watcom C how to use registers for arguments */
#pragma aux kdidraw parm [EAX] [EBX] [ECX] [EDX] [ESI] [EDI];
/* move to x1,y1, route/line width will be w */
#define M(x1,y1,w) kdidraw((KMOVE<<8), x1, y1, w, 0, 0)
/* put dot at x1, y, color c, atrib at */
#define DOT(x1,y1,c,at) kdidraw(at+(KWDOT<<8), x1, y1, c, 0, 0)
/* draw line from M point to x1,y1, color c, atrib at */
#define D(x1,y1,c,at) kdidraw(at+(KDRAW<<8), x1, y1, c, 0, 0)
<a name="019c_0017"><a name="019c_0017">
<a name="019c_0018">
[LISTING SIX]<a name="019c_0018">
; William F. Dudley, Jr.
; "porting a large application to 386 protected mode"
; This is the protected mode function that stuffs draw commands
; in the draw list. If the list fills up, it automatically calls
; pdinterp() to empty it.
;
NAME storlist
EXTRN pdinterp_:WORD
EXTRN _prot:WORD
EXTRN _list_p:WORD
LLEN EQU 100 ; size of draw list, must agree with C version.
DGROUP GROUP CONST,_DATA,_BSS
_TEXT SEGMENT PUBLIC BYTE USE32 'CODE'
ASSUME CS:_TEXT,DS:DGROUP
PUBLIC kdidraw_
; args in ax,bx,cx,dx,si,di
; global list pointer in _list_p is incremented by 12
kdidraw_:
push esi ;save si
mov si,ax ;save ax
mov eax,dword ptr _list_p
mov word ptr [eax],si
mov word ptr [eax+2],bx
mov word ptr [eax+4],cx
mov word ptr [eax+6],dx
pop esi ; get back si
mov word ptr [eax+8],si
mov word ptr [eax+10],di
add eax, 12
mov dword ptr _list_p,eax
inc word ptr _prot+4H
cmp word ptr _prot+4H,LLEN-3
jle L1
call near ptr pdinterp_
jmp short L2
L1: mov word ptr [eax],0000H
L2: ret
_TEXT ENDS
CONST SEGMENT PUBLIC WORD USE32 'DATA'
CONST ENDS
_DATA SEGMENT PUBLIC WORD USE32 'DATA'
_DATA ENDS
_BSS SEGMENT PUBLIC WORD USE32 'BSS'
_BSS ENDS
END
<a name="019c_0019"><a name="019c_0019">
<a name="019c_001a">
[LISTING SEVEN]<a name="019c_001a">
/* pdinterp.c -- William F. Dudley, Jr.
* protected dinterp() for Phar-Lap environment.
* this is the protected half of the draw list kdi processor
*/
#include <stdio.h>
#include <dos.h>
#define PDINTERP 1
#include "list5.h"
extern int real_addr; /* real address of start of real mode code */
extern int real_seg, real_off; /* segment and offset of real_addr */
extern char real; /* real copy of vidfnp, lp, draw list */
extern char end_real;
extern char dinterp; /* actually a function, but we just want address */
void pdinterp(void);
int pr2real(void);
static union REGS pregs;
static struct phregs pr;
static unsigned short real_o;
static unsigned abs_adr;
static int pdinitted=0;
void pdinterp() {
union REGS r;
if(!prot.lp) return;
if(!pdinitted) pdintinit();
/* copy list to buffer in real code seg */
blk_mov_pr(&prot, 0x34, abs_adr, sizeof(prot));
int386(0x21, &pregs, &r);
prot.list[prot.lp = 0][0] = 0;
list_p = prot.list[0];
}
void pdintinit(void) {
pregs.x.ax = 0x2510; /* call function */
pregs.d.ebx = real_addr; /* get segment of real mode stuff */
pregs.x.bx = (short int)&dinterp; /* EBX is address of dinterp subroutine */
pregs.d.ecx = 0; /* 0 words on stack */
pregs.d.edx = (int)≺ /* DS:EDX is address of register struct */
real_o = real_off + (short int)ℜ
abs_adr = real_o + (real_seg<<4); /* calculate address in selector 0x34 */
pregs.x.si = real_o+6; /* DS:SI points to list */
pr.ES = pr.DS = real_seg;
pdinitted = 1;
}
#if IN_C
/* this is a C version of the kdidraw() function in list6.asm */
void kdidraw(short int Ax,short int Bx,short int Cx,short int Dx, short int Si,short int Di) {
register short int *pi_;
pi_=prot.list[prot.lp];
*pi_++ = (short)(Ax);
*pi_++ = (short)(Bx);
*pi_++ = (short)(Cx);
*pi_++ = (short)(Dx);
*pi_++ = (short)(Si);
*pi_++ = (short)(Di);
if(++prot.lp > LLEN-3) pdinterp();
else *pi_ = 0;
}
#endif
<a name="019c_001b"><a name="019c_001b">
<a name="019c_001c">
[LISTING EIGHT]<a name="019c_001c">
; William F. Dudley, Jr.
; "Porting a large application to 386 protected mode"
; This is the real mode draw list interpreter.
;
d_text SEGMENT BYTE PUBLIC 'CODE' use16
ASSUME CS:d_text , DS:NOTHING , ES:NOTHING
LLEN EQU 100 ; size of draw list
public _dinterp
public _real
_real label word
db (12*LLEN+6) dup (?)
_dinterp proc far
call dint
ret
_dinterp endp
; ds:si points to real array at entry
dint proc near
floop: push word ptr [si+10] ; di
push word ptr [si+8] ; si
push word ptr [si+6] ; dx
push word ptr [si+4] ; cx
push word ptr [si+2] ; bx
push word ptr [si+0] ; ax
add si,12
call dword ptr cs:[_real]
add sp,12
iftest: cmp word ptr [si+0] ,0
jne floop
ret
dint endp
d_text ends
end
[Example 1: Three locations in the real mode code are directly accessed
from protected mode]
extern char test_string; /* string buffer in real mode module */
extern char end_real; /* end of real mode code */
extern char QUERY_FAR; /* actually a function, but we just want address */
[Example 2: Protected-mode structure]
struct {
union {
int (* p)();
short int addr[2]; /* [0]segment and [1]offset of driver entry point */
} vidfn ;
short int lp;
short int list[LLEN][6];
} prot ;