When you need more speed and greater capacity

This article contains the following executables: DUDLEY.LST

William F. Dudley, Jr.

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.

Click here for more information about the current incarnation of Draftsman and related products.

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; { ... }


  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.


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.


; 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

      CALL    QUERY

; here lives the real mode dongle communications code

      public   _test_string
_test_string   db   256 dup (?)

      public   _end_real
_end_real label   byte

ss_text         ENDS


/* 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");

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)&pr;      /* 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);


; 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
   PUBLIC   blk_mov_pr_
   PUBLIC   blk_mov_rp_

; protected to real
blk_mov_pr_   proc   near
   push   EDI
   push   ESI
   push   ES
   jecxz   non1
   ; 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
blk_mov_pr_   endp

; real to protected
blk_mov_rp_   proc   near
   push   EDI
   push   ESI
   push   ES
   push   DS
   jecxz   non2
   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
blk_mov_rp_   endp



/* 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;
   if(r.d.ecx < (MAXB + MAXCOLORS)) {   /* ecx = size of buffer */
       fprintf(stderr,"real mode buffer isn't big enough: %d\n", r.d.ecx);
    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");
    prot.vidfn.addr[1] = r.x.si;   /* real mode address of video entry */
    prot.vidfn.addr[0] = r.x.di;
    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;

/* 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;
    r.x.bx = FP_SEG(lbuf);
    r.x.cx = FP_OFF(lbuf);
    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 */
   int86(vectnum, &r, &r);

/* clear the draw list */
void listinit(void) {
    prot.list[0][0] = 0;
    prot.lp = 0;
    list_p = prot.list[0];


/* 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
   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
   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)


; 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.
       PUBLIC  kdidraw_
       ; args in ax,bx,cx,dx,si,di
       ; global list pointer in _list_p is incremented by 12
       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

_DATA       ENDS
_BSS        ENDS


/* 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)&pr;   /* DS:EDX is address of register struct */
  real_o = real_off + (short int)&real;
  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_++ = (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;


; William F. Dudley, Jr.
; "Porting a large application to 386 protected mode"
; This is the real mode draw list interpreter.

LLEN   EQU   100      ; size of draw list

   public   _dinterp
   public   _real
_real   label   word
   db   (12*LLEN+6) dup (?)

_dinterp proc   far
   call   dint
_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
dint   endp

d_text   ends

[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 ;

Click here for more information about Draftsman and related products.