Retro video games delivered to your door every month!
Click above to get retro games delivered to your door ever month!
X-Hacker.org- Watcom C/C++ User's Guide - doing in-line assembly is reasonably straight-forward with watcom c/c++ http://www.X-Hacker.org [<<Previous Entry] [^^Up^^] [Next Entry>>] [Menu] [About The Guide]
Doing in-line assembly is reasonably straight-forward with Watcom C/C++
although care must be exercised.  You can generate a sequence of in-line
assembly anywhere in your C/C++ code stream.  The first step is to define
the sequence of instructions that you wish to place in-line.  The auxiliary
pragma is used to do this.  Here is a simple example based on a DOS function
call that returns a far pointer to the Double-Byte Character Set (DBCS)
encoding table.

Example:

     extern unsigned short far *dbcs_table( void );
     #pragma aux dbcs_table = \
             "mov ax,6300h"   \
             "int 21h"        \
             value   [ds si]  \
             modify  [ax];

To set up the DOS call, the AH register must contain the hexadecimal value
"63" (63h).  A DOS function call is invoked by interrupt 21h.  DOS returns a
far pointer in DS:SI to a table of byte pairs in the form (start of range,
end of range).  On a non-DBCS system, the first pair will be (0,0).  On a
Japanese DBCS system, the first pair will be (81h,9Fh).

With each pragma, we define a corresponding function prototype that explains
the behaviour of the function in terms of C/C++.  Essentially, it is a
function that does not take any arguments and that returns a far pointer to
a unsigned short item.

The pragma indicates that the result of this "function" is returned in DS:SI
(value [ds si]).  The pragma also indicates that the AX register is modified
by the sequence of in-line assembly code (modify [ax]).

Having defined our in-line assembly code, let us see how it is used in
actual C code.

Example:

     #include <stdio.h>

     extern unsigned short far *dbcs_table( void );
     #pragma aux dbcs_table = \
             "mov ax,6300h"   \
             "int 21h"        \
             value   [ds si]  \
             modify  [ax];


     void main()
     {
         if( *dbcs_table() != 0 ) {
             /*
                 we are running on a DOS system that
                 supports double-byte characters
             */
             printf( "DBCS supported\n" );
         }
     }

Before you attempt to compile and run this example, consider this:  The
program will not work!  At least, it will not work in most 16-bit memory
models.  And it doesn't work at all in 32-bit protected mode using a DOS
extender.  What is wrong with it?

We can examine the disassembled code for this program in order to see why it
does not always work in 16-bit real-mode applications.


         if( *dbcs_table() != 0 ) {
             /*
                 we are running on a DOS system that
                 supports double-byte characters
             */
      0007  b8 00 63                          mov     ax,6300H
      000a  cd 21                             int     21H
      000c  83 3c 00                          cmp     word ptr [si],0000H
      000f  74 0a                             je      L1

             printf( "DBCS supported\n" );
         }
      0011  be 00 00                          mov     si,offset L2
      0014  56                                push    si
      0015  e8 00 00                          call    printf_
      0018  83 c4 02                          add     sp,0002H

     }

After the DOS interrupt call, the DS register has been altered and the code
generator does nothing to recover the previous value.  In the small memory
model, the contents of the DS register never change (and any code that
causes a change to DS must save and restore its value).  It is the
programmer's responsibility to be aware of the restrictions imposed by
certain memory models especially with regards to the use of segmentation
registers.  So we must make a small change to the pragma.


     extern unsigned short far *dbcs_table( void );
     #pragma aux dbcs_table = \
             "push ds"        \
             "mov ax,6300h"   \
             "int 21h"        \
             "mov di,ds"      \
             "pop ds"         \
             value   [di si]  \
             modify  [ax];

If we compile and run this example with a 16-bit compiler, it will work
properly.  We can examine the disassembled code for this revised program.


         if( *dbcs_table() != 0 ) {
             /*
                 we are running on a DOS system that
                 supports double-byte characters
             */
      0008  1e                                push    ds
      0009  b8 00 63                          mov     ax,6300H
      000c  cd 21                             int     21H
      000e  8c df                             mov     di,ds
      0010  1f                                pop     ds
      0011  8e c7                             mov     es,di
      0013  26 83 3c 00                       cmp     word ptr es:[si],0000H
      0017  74 0a                             je      L1

             printf( "DBCS supported\n" );
         }
      0019  be 00 00                          mov     si,offset L2
      001c  56                                push    si
      001d  e8 00 00                          call    printf_
      0020  83 c4 02                          add     sp,0002H

If you examine this code, you can see that the DS register is saved and
restored by the in-line assembly code.  The code generator, having been
informed that the far pointer is returned in (DI:SI), loads up the ES
register from DI in order to reference the far data correctly.

That takes care of the 16-bit real-mode case.  What about 32-bit protected
mode?  When using a DOS extender, you must examine the accompanying
documentation to see if the system call that you wish to make is supported
by the DOS extender.  One of the reasons that this particular DOS call is
not so clear-cut is that it returns a 16-bit real-mode segment:offset
pointer.  A real-mode pointer must be converted by the DOS extender into a
protected-mode pointer in order to make it useful.  As it turns out, neither
the Tenberry Software DOS/4G(W) nor Phar Lap DOS extenders support this
particular DOS call (although others may).  The issues with each DOS
extender are complex enough that the relative merits of using in-line
assembly code are not worth it.  We present an excerpt from the final
solution to this problem.

Example:

     #ifndef __386__

     extern unsigned short far *dbcs_table( void );
     #pragma aux dbcs_table = \
             "push ds"        \
             "mov ax,6300h"   \
             "int 21h"        \
             "mov di,ds"      \
             "pop ds"         \
             value   [di si]  \
             modify  [ax];

     #else

     unsigned short far * dbcs_table( void )
     {
         union REGPACK       regs;
         static short        dbcs_dummy = 0;

         memset( &regs, 0, sizeof( regs ) );

         if( _IsPharLap() ) {
             PHARLAP_block pblock;

             memset( &pblock, 0, sizeof( pblock ) );
             pblock.real_eax = 0x6300;       /* get DBCS vector table */
             pblock.int_num = 0x21;          /* DOS call */
             regs.x.eax = 0x2511;            /* issue real-mode interrupt */
             regs.x.edx = FP_OFF( &pblock ); /* DS:EDX -> parameter block */
             regs.w.ds = FP_SEG( &pblock );
             intr( 0x21, &regs );
             return( firstmeg( pblock.real_ds, regs.w.si ) );

         } else if( _IsDOS4G() ) {
             DPMI_block dblock;

             memset( &dblock, 0, sizeof( dblock ) );
             dblock.eax = 0x6300;            /* get DBCS vector table */
             regs.w.ax = 0x300;              /* DPMI Simulate R-M intr */
             regs.h.bl = 0x21;               /* DOS call */
             regs.h.bh = 0;                  /* flags */
             regs.w.cx = 0;                  /* # bytes from stack */
             regs.x.edi = FP_OFF( &dblock );
             regs.x.es = FP_SEG( &dblock );
             intr( 0x31, &regs );
             return( firstmeg( dblock.ds, dblock.esi ) );

         } else {
             return( &dbcs_dummy );
         }
     }

     #endif

The 16-bit version will use in-line assembly code but the 32-bit version
will use a C function that has been crafted to work with both Tenberry
Software DOS/4G(W) and Phar Lap DOS extenders.  The firstmeg function used
in the example is shown below.


     #define REAL_SEGMENT    0x34

     void far *firstmeg( unsigned segment, unsigned offset )
     {
         void far    *meg1;

         if( _IsDOS4G() ) {
             meg1 = MK_FP( FP_SEG( &meg1 ), ( segment << 4 ) + offset );
         } else {
             meg1 = MK_FP( REAL_SEGMENT, ( segment << 4 ) + offset );
         }
         return( meg1 );
     }

We have taken a brief look at two features of the auxiliary pragma, the
"modify" and "value" attributes.

The "modify" attribute describes those registers that are modified by the
execution of the sequence of in-line code.  You usually have two choices
here; you can save/restore registers that are affected by the code sequence
in which case they need not appear in the modify list or you can let the
code generator handle the fact that the registers are modified by the code
sequence.  When you invoke a system function (such as a DOS or BIOS call),
you should be careful about any side effects that the call has on registers.
 If a register is modified by a call and you have not listed it in the
modify list or saved/restored it, this can have a disastrous affect on the
rest of the code in the function where you are including the in-line code.

The "value" attribute describes the register or registers in which a value
is returned (we use the term "returned", not in the sense that a function
returns a value, but in the sense that a result is available after execution
of the code sequence).

This leads the discussion into the third feature of the auxiliary pragma,

Online resources provided by: http://www.X-Hacker.org --- NG 2 HTML conversion by Dave Pearson