[comp.lang.c] Sample Printer Driver Skeleton For PC-DOS

cramer@kontron.UUCP (Clayton Cramer) (01/20/87)

Several weeks ago I asked for help relating to building an interrupt
service routine so that I can build a C program that takes the standard
PC-DOS printer driver.  A number of people provided very helpful advise.
Consequently, I am posting the following code to provide a skeleton
so that can build your own TSR programs that take over the printer ISR.
It's not copyrighted, but when you get filthy rich writing the next
1-2-3, I sure hope you'll remember where you got your start. :-)

The following files are included:

	make		Makes the various components of the TSR and the
        		testing filter.
        loadisr.c	Loads the ISR and gets it started.
        isr.asm		The actual ISR that dispatches control to the 
        		C function.
	filter.c	A C function that is called by isr.asm.
        testfilt.c	A program that simulates the functions of isr.asm
        		so that you can debug filter.c without the extraneous
                        junk involved with the interrupt service routine.

Use Microsoft C V4.0 and MASM V4.0.

It's not beautiful, and it's not perfect, but it does work.

Also, can anyone tell me:

	1. how to make a TSR program go away?
        2. how to figure out the size of a .EXE file at link time?

--------------make file----------------------------------------
.c.exe:
  cl -DLINTARGS /Zi /Od $*.c

.asm.obj:
  masm $*.asm,$*.obj,$*.lst;

# The filter written in C.
filter.obj: filter.c
  cl /Gs /AL -DLINTARGS -c /Zi /Od $*.c

# The interrupt service routine that invokes the filter written in C.
isr.obj: isr.asm

# The C program that loads the ISR and the filter.
loadisr.obj: loadisr.c 
  cl /AL -DLINTARGS -c /Zi /Od $*.c

# The link step that links the ISR, the ISR loader, and the filter.
filter.exe: loadisr.obj isr.obj filter.obj
  cl /AL /Zi /Od filter.obj loadisr.obj isr.obj

# The C program that tests the filter.
testfilt.obj: testfilt.c
  cl /AL -DLINTARGS -c /Zi /Od $*.c

testfilt.exe: testfilt.obj filter.obj
  cl /AL /Zi /Od testfilt.obj filter.obj

--------------------loadisr.c----------------------------------------
#include <stdio.h> 
#include <dos.h> 
#define _ProgramSize_ 25000

extern char far *DRIVERPTR;
extern void far SETINTNBR ();
extern void far SAVE_DS_SS ();
int             IntNbrToTakeOver = 0x61;

main (Argc, Argv)

int             Argc;
char            *Argv[];

  {
  int           ExitCode = 0;
  union REGS    Regs;
  struct SREGS  SegRegs;

  SAVE_DS_SS ();
  switch (Argc)
    {
    case 1:     break;
    
    case 2:     if (1 != sscanf (Argv[1], "%d", &IntNbrToTakeOver))
                  {
                  fprintf (stderr, "invalid interrupt number: %s\n", Argv[1]);
                  ExitCode = 3;
                  }
                break;

    default:    fprintf (stderr, "too many arguments -- only an alternate interrupt number is valid\n");
                ExitCode = 2;
                break;
    }
  if (0 == ExitCode)
    {
    Regs.h.ah = 0x35;               /* Get ptr to any existing interrupt vector */
    Regs.h.al = (char) IntNbrToTakeOver;
    int86x (0x21, &Regs, &Regs, &SegRegs);
    if ((0 == SegRegs.es) && (0 == Regs.x.bx))
      {
      /* Means that the interrupt we intend to cannibalize isn't in use. */
      fprintf (stderr, "installing print translator\n");
      Regs.h.al = (char) 0x17;
      int86x (0x21, &Regs, &Regs, &SegRegs);    /* Get ptr to existing printer driver */
      SegRegs.ds = SegRegs.es;          /* es:bx = addr of existing printer driver */
      Regs.x.dx = Regs.x.bx;
      Regs.h.ah = 0x25;                 /* set ptr to interrupt driver */
      Regs.h.al = (char) IntNbrToTakeOver;  /* interrupt we are taking over */
      int86x (0x21, &Regs, &Regs, &SegRegs);  /* point printer driver to ouriver */
      Regs.h.al = (char) 0x17;    /* the original printer driver */
      SegRegs.ds = FP_SEG ((char far*) DRIVERPTR); /* get addr of driver we are installing */
      Regs.x.dx = FP_OFF ((char far*) DRIVERPTR);  
      int86x (0x21, &Regs, &Regs, &SegRegs);
      SETINTNBR (IntNbrToTakeOver);
      printf ("test string for printer\n");
      Regs.x.dx = _ProgramSize_;              /* size of the program we want to remain resident */
      int86x (0x27, &Regs, &Regs, &SegRegs);  /* terminate and stay resident */
      }
    }
  }
--------------------isr.asm----------------------------------------
              page        50,132
              extrn       _FILTER:far
CODE_SEG      SEGMENT
              ASSUME      CS:CODE_SEG

              public      _DRIVERPTR,_SETINTNBR, _SAVE_DS_SS

_DRIVERPTR    dd          DRIVER
filter_ds     dw          0
filter_ss     dw          0
isr_ds        dw          0
isr_ss        dw          0
Printer       dw          0
Count         dw          0

DRIVER        proc        far

              sti

; Save the ISR DS and SS registers so that I can retrieve later after the
; C function we call for filtering is finished.

              push        bx
              mov         bx,ds
              mov         cs:isr_ds,bx
              mov         bx,ss
              mov         cs:isr_ss,bx
              pop         bx

              push        bx
              push        cx
              push        dx
              push        di
              push        si
              push        es

              cmp         ah,0
              je          PRT_CHAR

; This must be some other printer function -- let's just perform the function

              call        PRT_INT
              jmp         SAVE_RESULT           ; save the output

PRT_CHAR:
              mov         cs:Printer,dx
              cli
              mov         bx,filter_ds
              mov         ds,bx
              mov         bx,filter_ss
              mov         ss,bx
              mov         bx,offset Count         ; Push ptr to Count on stack
              push        cs
              push        bx
              push        ax                      ; Push character to print
              call        _FILTER
              add         sp,6                    ; throw away arguments
              mov         bx,isr_ds
              mov         ds,bx
              mov         bx,isr_ss
              mov         ss,bx
              sti
              mov         cx,cs:Count             ; get count of characters to print
              cmp         cx,0                    ; any characters to print?
              je          NoCharsToPrint          ; no, return to caller

              mov         es,dx
              mov         bx,ax                   ; es:bx = ptr to buffer
              mov         dx,cs:Printer           ; get the Printer number

NextChar:
              mov         ah,0
              mov         al,es:[bx]              ; get the character to send
              call        PRT_INT                 ; print the character
              inc         bx                      ; increment the char ptr
              dec         cx                      ; decrement the char count
              ja          NextChar                ; get the next character

NoCharsToPrint:
              mov         ah,010h                 ; shows that we wrote it

SAVE_RESULT:
              pop         es
              pop         si  
              pop         di
              pop         dx
              pop         cx
              pop         bx
              iret        

DRIVER        endp

PRT_INT       proc        near

NEW_PRT_INT:  int         17h                   ; default prt interrupt nbr
              ret

PRT_INT       endp

_SAVE_DS_SS   proc        far
; Stores the DS and SS of the C program into SAVE_DS and SAVE_SS so that
; can correctly invoke the C function from the ISR.

              push        ax
              mov         ax,ds
              mov         cs:filter_ds,ax
              mov         ax,ss
              mov         cs:filter_ss,ax
              pop         ax
              ret
_SAVE_DS_SS   endp

_SETINTNBR    proc        far

              push        bp
              mov         bp,sp
              mov         ax,[bp+6]
              mov         cs:byte ptr NEW_PRT_INT+1,al
              mov         sp,bp
              pop         bp
              ret

_SETINTNBR    endp

CODE_SEG      ENDS
              END           
-------------------filter.c----------------------------------------
#include <stdio.h>
char        Hex[] = "0123456789ABCDEF";
char        Buffer[1024];

char*
FILTER(Ch, Count)

int               Ch, *Count;

  {
  /* Ch is the character to be translated, and *Count is the number of
     output characters put into Buffer. */
  Buffer[*Count = 0] = Hex[(Ch >> 4) & 0xf];
  Buffer[++(*Count)] = Hex[Ch & 0xf];
  ++(*Count);
  return (Buffer);
  }
--------------------testfilt.c----------------------------------------
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
extern char   *FILTER ();

main (Argc, Argv)

int               Argc;
char              *Argv[];

  {
  int             Ch, Count, I;
  char            *String;

  setmode (fileno (stdout), O_BINARY);
  freopen (Argv[1], "rb", stdin);
  while (EOF != (Ch = getchar ()))
    {
    String = FILTER (Ch, &Count);
    if (Count > 0)
      for (I = 0; I < Count; I++)
        putchar (String[I]);
    }
  }