views:

315

answers:

7

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.

A: 

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 [...]

unwind
this is true for DJGPP C compiler, as int86 is not a standard function it is not given that the Digital Mars compiler works in the same way, it may have more or less restrictions
Alon
+1  A: 

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

Alon
"the first form is more readable". It is?
Steve Jessop
I think which form is more "readable" than the other is a matter of personal opinion, because personally I find the second more readable, but both are understandable. Dissecting the compiled code would be something of a last resort, if someone didn't have the answer for me here than I would surely go that route.
Heather
The first form is more readable by compilers which use a different format for inline assembly. But you may worry about the overhead of the unnecessary function call, plus addressing the elements of the `REGS` union, then presumably the `int86` copying all the registers (all those defined in the union, not just the ones you use) from memory, then copying them all back out again (which you then discard).
P-Nuts
@P-Nuts - Still drinking my coffee, I thought Alon meant readable as in readable by human eyes. I agree that the first form would be readable by more compilers.
Heather
+1  A: 

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.

Mike Dunlavey
There may be a point where I would have a lot of pixels to write, as the client is debating a splash screen at the start of the application. For now, I only need this to draw the odd line on the screen. That issue may end up being it's own question when the time comes.
Heather
@Heather: Yeah, I looked up your machine, and the screen is monochrome and tiny, so I doubt if graphics will be much of a bottleneck.
Mike Dunlavey
+7  A: 
tommieb75
I think I had a copy of Ralph Brown's interrupt list in college now that I think about it, and I have no idea what I did with it. Seems I forgot this awesome resource existed, thank you! This is very helpful.
Heather
+1 for mentioning Ralph Brown's interrupt list and HelpPc
Nils Pipenbrinck
+1  A: 

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.

Hans Passant
I suspected I was not doing something I needed to be doing in the asm block. Thanks.
Heather
+1  A: 

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.

stacker
I agree about int86 - I think I will use this approach to be safe unless speed becomes a real issue, thanks :)
Heather
+1  A: 

Calling a library function has a risk of not supporting your target CPU and environment limitations. For instance Digital Mars C compiler doesn't have a 80188/80186 target option which that Unitech device has. intx86 function and respective generated code may not support that CPU because of some incorrect assumption and possible lack of testing on that specific hardware.

Briefly, the first form requires more attention while the second one provides you lots of flexibility and performance. This has already been decided but I wanted to point at that gotcha.

ssg
That is one of my concerns, that because I don't have a compiler for that chipset I may come across unexplained behavior. We do have a copy of Microsoft C compiler 3.0 in the deep dark storage closet that I may end up using if DMC doesn't work.
Heather