Question

Background: I've been tasked with writing a data collection program for a Unitech HT630, which runs a proprietary DOS operating system that can run executables compiled for 16-bit MS DOS, albeit with some restrictions. I'm using the Digital Mars C/C++ compiler, which seems to be working very well.

For some things I can use standard C libraries, but other things like drawing on the screen of the unit require assembly code. The assembly examples given in the device's documentation are different from how I was taught to use inline assembly code in C/C++. For reference, BYTE in the examples below is of type unsigned char.

Sample of the example code I was given:

#include <dos.h>

/* Set the state of a pixel */
void LCD_setpixel(BYTE x, BYTE y, BYTE status) {
  if(status > 1 || x > 63 || y > 127) {
    /* out of range, return */
    return;
  }
  /* good data, set the pixel */
  union REGS regs;
  regs.h.ah = 0x41;
  regs.h.al = status;
  regs.h.dh = x;
  regs.h.dl = y;
  int86(0x10, &regs, &regs);
}

How I was always taught to use inline assembly:

/* Set the state of a pixel */
void LCD_setpixel(BYTE x, BYTE y, BYTE status) {
  if(status > 1 || x > 63 || y > 127) {
    /* out of range, return */
    return;
  }
  /* good data, set the pixel */
  asm {
    mov AH, 41H
    mov AL, status
    mov DH, x
    mov DL, y
    int 10H
  }
}

Both forms seem to work, I haven't encountered a problem with either approach as of yet. Is one form considered better than the other for DOS programming? Does the int86 function handle something for me that I am not handling myself in my own assembly code in the second example?

Thank you in advance for any help.

Was it helpful?

Solution

When you use the int86 function call, that is a C runtime library call which sets up the registers and issues a DOS interrupt function. Both methods are really the same with one exception, when you use inline assembler, the code is actually embedded into the object code when compiled and linked.

Inline assembly would be considered faster since you do not have the overhead involved in calling the C runtime library to invoke the DOS interrupt for you. The onus is on you to ensure there is enough stack space when using inline assembly, whereas the C Runtime library takes care of allocating stack space when setting up the registers prior to invoking the int86 function.

The int86 is a way to make it easier to invoke DOS interrupts. This was extremely popular among the old Borland Turbo C suite of compilers and on Microsoft, I am talking about old compilers before Win 3.1 came out.

Speaking of the interrupt 0x10, which is responsible for the video output, if I remember correctly, at the time, some BIOS's destroyed the bp register and the workaround was to do this:

__asm{
   push bp;
}
/* set up the registers */
int86(0x10, &regs, &regs);
__asm{
   pop bp;
}

You can find out the extensive BIOS functions on Ralph Brown's Interrupt List here. Also HelpPC v2.1 may help also, found here.

OTHER TIPS

the first form is more readable which also counts for something ;-)

if you want to know if int86 is doing something behind your back, just compile your program and examine the generated assembly code

By calling int86, your code remains in C. Either way, it's writing the pixel by doing a system interrupt.

If you have a lot of pixels to write, and you start seriously hitting speed issues, there might be a more direct (and much less safe but possibly worthwhile) way to write directly to the pixel memory.

Both code snippets accomplish the same thing. The big advantage of the 1st one is that there's some likelihood that you'll still be able to use it when you switch compilers. And that you don't stomp on a register that the 'C' compiler's code generator was using for another purpose. Something you definitely forget to take care of in your asm snippet.

You should check the compilers manual to find out who is responsible to restore register values after an inline assembly section. Since your variables are assigned to registers, unintended changes of the values may lead to hard-to-find bugs. int86(0x10, &regs, &regs); saves registers and restores them after executing the software-interrupt.

Some compilers accept instructions to define a clobber list (registers that should be saved and restored). Usually an assembler section should save registers and flags that will be changed with push and restore them using pop, either by the compiler or yourself. Therefore the first example should be prefered.

That isn't inline assembly, it's C. Very low-level C, using a function to cause the interrupt, but still C.

This page has some documentation (for the DJGPP compiler, yours might work differently), including the structure used to represent the registers. It also notes:

Note that, unlike the __dpmi_int function, requests that go through int86 and similar functions are specially processed to make them suitable for invoking real-mode interrupts from protected-mode programs. For example, if a particular routine takes a pointer in BX, int86 expects you to put a (protected-mode) pointer in EBX. Therefore, int86 should have specific support for every interrupt and function you invoke this way. Currently, it supports only a subset of all available interrupts and functions [...]

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top