home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / BAKLPT.ZIP / BAKLPT.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1988-08-03  |  12.4 KB  |  425 lines

  1.  
  2. {$R-,S-,I-,D+,V-}
  3. {
  4.   ** BakLPT Unit **
  5.   ** by Richard S. Sadowsky      CIS [74017,1670]
  6.   ** 8/3/88
  7.   ** version .6
  8.   ** Copyright 1988, Richard S. Sadowsky
  9.  
  10.   This unit provides an alternative to the standard PRINTER unit.
  11.   The output will be stored in a buffer and printed in the background.
  12.  
  13.   Requires QBUFMAX heap space as currently coded.
  14.  
  15. }
  16.  
  17.  
  18. Unit BakLPT;
  19.  
  20. interface
  21.  
  22. uses DOS,TPInt;
  23.  
  24. const
  25.   fmClosed         = $D7B0; { magic numbers for Turbo }
  26.   fmInput          = $D7B1;
  27.   fmOutput         = $D7B2;
  28.   fmInOut          = $D7B3;
  29.  
  30.   IO_Invalid       = $FC;    { invalid operation eg. attempt to write }
  31.                              { to a file opened in fmInput mode       }
  32.   LPT_1            = 0;      { Indicate LPT1 for BIOS int 17h }
  33.   LPT_2            = 1;      { Indicate LPT2 for BIOS int 17h }
  34.   LPT_3            = 2;      { Indicate LPT3 for BIOS int 17h }
  35.  
  36.   QBUFMAX          = 65521;
  37.  
  38.   NOT_ENOUGH_HEAP  = -1;
  39.   ALREADY_OPEN     = -2;
  40.   OUT_OF_QUEUE     = -3;
  41.   ISR_NOT_INSTALLED= -4;
  42.  
  43. type
  44.   DOSMode          = (Cooked,Raw,DefMode);
  45.   QBuffer          = Array[1..QBUFMAX] of Char;
  46.   PrintQueue       = ^QBuffer;
  47.   TextPtr          = ^Text;
  48.   TextBuffer       = array[0..127] of Char;
  49.  
  50.   QTextRec         = record
  51.                        Handle     : Word;
  52.                        Mode       : Word;
  53.                        BufSize    : Word;
  54.                        Private    : Word;
  55.                        BufPos     : Word;
  56.                        BufEnd     : Word;
  57.                        BufPtr     : ^TextBuffer;
  58.                        OpenFunc   : Pointer;
  59.                        InOutFunc  : Pointer;
  60.                        FlushFunc  : Pointer;
  61.                        CloseFunc  : Pointer;
  62.                        { 16 byte user data area, I use 12 bytes }
  63.                        PrintMode  : Byte;  { not currently used}
  64.                        FormNo     : Byte;  { not currently used}
  65.                        LPTNo      : Word;  { LPT number in [0..2] }
  66.                        QueSize    : Word;
  67.                        EProc      : Pointer;
  68.                        SaveMode   : Boolean;
  69.                        DOS_Mode   : DOSMode;
  70.                        UsrData    : Array[1..4] of byte;
  71.                        Name       : array[0..79] of Char;
  72.                        Buffer     : TextBuffer;
  73.                      end;
  74.  
  75. var
  76.   BakLptInstalled  : Boolean;
  77.   QueError         : Integer;
  78.   BakError         : Integer;
  79.   PrtQue           : PrintQueue;
  80.   QBI              : Word;
  81.   QOutPtr          : Word;
  82.  
  83.   Lst              : Text;   { for source compatability with Printer and }
  84.                              { LPT units, and TP3's Lst device }
  85.  
  86. { typed constants }
  87. const
  88.   Retry            : Array[0..2] of Word = (20,20,20);
  89.   RetryWait        : Array[0..2] of Word = (25,25,25);
  90.   LPTNames         : array[0..2] of String[4] = ('LPT1','LPT2','LPT3');
  91.   CHARS_PER        : Word = 50; { send out 50 chars per TICKS_TO_WAIT by def.}
  92.   TICKS_TO_WAIT    : Word = 4; { approx 1/4 sec by default }
  93.   SOC_Retries      : Word = 20;
  94.   FilterInt05      : Boolean = TRUE;
  95.   QuePause         : Boolean = FALSE;
  96.   _MODE            : DOSMode = DefMode;
  97.   QueUserExitFunc  : Pointer = NIL;
  98.   QueUserErrorFunc : Pointer = NIL;
  99.   DefaultLstDevice : TextPtr = NIL;
  100.  
  101. function DoInt17(Ch : Char; LPTNo : Word) : Byte;
  102. { send a character to LPTNo via ROM BIOS int 17h func 0h }
  103. { implented as an inline "macro" for speed and the heck  }
  104. { of it! Bet you've seen this routine before!            }
  105. Inline(
  106.   $5A/         {  pop     DX    ; get printer number}
  107.   $58/         {  pop     AX    ; get char}
  108.   $B4/$00/     {  mov     AH,00 ; set AH for BIOS int 17h function 0}
  109.   $CD/$17/     {  int     $17   ; do an int 17h}
  110.   $86/$E0);    {  xchg    AL,AH ; put byte result in AL}
  111.  
  112. function LPTStat(LPTNo : Word) : Byte;
  113. Inline(
  114.   $5A/         {  POP    DX      ; get LPT number}
  115.   $B4/$02/     {  MOV    AH,$02  ; int 17h function 2}
  116.   $CD/$17/     {  INT    $17     ; BIOS printer services}
  117.   $86/$C4);    {  XCHG   AH,AL   ; return byte in AH as function result}
  118.  
  119. function LPTReady(ErrorCode : Word) : Boolean;
  120. Inline(
  121.   $5B/         {  POP  BX}
  122.   $B8/$90/$00/ {  MOV  AX,$90  ; printer select bit}
  123.   $21/$D8/     {  AND  AX,BX   ; check to see if printer sel bit is set}
  124.   $74/$02/     {  JZ   L1      ; printer not ready, false (0) already in AL}
  125.   $B0/$01);    {  MOV  AL,1    ; printer ready so return true (1) in AL}
  126. {L1:}
  127.  
  128.  
  129. procedure AssignQue(var F : Text; LPTNumber : Word;
  130.                     QueueSize : Word);
  131. { like Turbo's assign, except associates Text variable with one of the LPTs }
  132.  
  133. procedure ResetQueue(BufferToo : Boolean);
  134.  
  135. implementation
  136.  
  137. const
  138.   INT1C_HANDLE     = 15;
  139.   INT05_HANDLE     = 16;
  140.  
  141.   STDPRN           = 4;
  142.  
  143.   TIMER_STACK_SIZE = 1024;
  144.  
  145.   InTimerISR       : Boolean = TRUE; { int 1Ch semaphore }
  146.  
  147. var
  148.   ExitSave         : Pointer;
  149.   SaveMode         : Boolean;
  150.   TimerStack       : Array[1..TIMER_STACK_SIZE] of Byte;
  151.  
  152. function BoolFuncFarCall(ProcAddr : Pointer) : Boolean;
  153. inline(
  154.   $89/$E3/               {  mov bx,sp}
  155.   $36/$FF/$1F/           {  call far dword ptr ss:[bx]}
  156.   $81/$C4/$04/$00);      {  add sp,4}
  157.  
  158. function DoUserExit : Boolean;
  159.  
  160. begin
  161.   if QueUserExitFunc <> NIL then
  162.     DoUserExit := BoolFuncFarCall(QueUserExitFunc)
  163.   else
  164.     DoUserExit := FALSE;
  165. end;
  166.  
  167. {$F+} { <==The following routines MUST be compiler as FAR }
  168.  
  169. procedure ExitHandler;
  170. { Restore the original device mode and close file }
  171.  
  172. begin
  173.  
  174.   ExitProc := ExitSave;  { Chain to other exit procedures }
  175.  
  176.   Close(Lst); { this triggers LstClose and possibly UserExitFunc if }
  177.               { chars are left in the queue buffer }
  178.  
  179. end;
  180.  
  181. function LstOpen(var F : QTextRec) : Integer;
  182.  
  183. begin
  184.   if PrtQue <> NIL then begin { if a queue exists then Lst is already open }
  185.     QueError := ALREADY_OPEN;
  186.     LstOpen := QueError;      { return the error condition and exit }
  187.     Exit
  188.   end;
  189.  
  190.   with F do begin
  191.     Handle     := StdPRN;     { I'm not sure why I'm doing this!!! }
  192.     Mode := fmOutput;         { make sure it knows this is output only }
  193.     GetMem(PrtQue,F.QueSize); { allocate the print queue on the heap }
  194.     QBI := 0;                 { set the Queue Buffer Index to 0 }
  195.     QOutPtr := 0;             { set queue output pointer to 0   }
  196.     if PrtQue = NIL then      { make sure there was sufficient memory }
  197.         QueError := NOT_ENOUGH_HEAP
  198.       else
  199.         QueError := 0;
  200.     DefaultLstDevice := @F;
  201.   end;
  202.  
  203.   InterruptsOff;        { flip Interrupts off for some important business }
  204.   FilterInt05 := FALSE; { ignore print screens while background printing }
  205.   InTimerISR := FALSE;  { This starts the TimerISR a'tickin' }
  206.   InterruptsOn;         { Don't forget to turn em on }
  207.  
  208.   LstOpen := QueError;  { return this for IOResult }
  209. end;
  210.  
  211. function LstClose(var F : QTextRec) : Integer;
  212.  
  213. var
  214.   ErrorCode        : Integer;
  215.   Abort            : Boolean;
  216.   DontCare         : boolean;
  217.  
  218. begin
  219.   ErrorCode := 0;
  220.   with F do begin
  221.     repeat
  222.       if (QBI > 0) and (QueUserExitFunc <> NIL) then
  223.         Abort := DoUserExit
  224.       else
  225.         Abort := TRUE;
  226.     until Abort;
  227.  
  228.     Mode       := fmClosed;
  229.  
  230.     FreeMem(PrtQue,F.QueSize);
  231.     PrtQue := NIL;
  232.   end;
  233.   DefaultLstDevice := NIL;
  234.   InterruptsOff;
  235.   InTimerISR := TRUE;
  236.   FilterInt05 := TRUE;
  237.   InterruptsOn;
  238.   LstClose := ErrorCode;
  239. end;
  240.  
  241. function LstOutput(var F : QTextRec) : Integer;
  242. { Send whatever has accumulated in the Buffer to int 17h   }
  243. { If error occurs, return in IOResult.  See Inside Turbo   }
  244. { Pascal chapter of TP4 manual for more info on TFDD       }
  245. var
  246.   I                : Word;
  247.   ErrorCode        : Integer;
  248.  
  249. begin
  250.   InterruptsOff;
  251.  
  252.   I := QBI;
  253.   Inc(QBI,F.BufPos);  { increment QBI by number of chars in QTextRec buffer }
  254.   if (QBI > F.QueSize) or (QBI < I) then { check for overflow }
  255.     QueError := OUT_OF_QUEUE  { return error code }
  256.   else begin
  257.     Inc(I);
  258.     Move(F.BufPtr^[0],PrtQue^[I],F.BufPos); { move from QTextRec buff to que }
  259.     QueError := 0;
  260.   end;
  261.   F.BufPos := 0; { reset BufPos }
  262.   InterruptsOn;
  263.   LstOutput := QueError;
  264. end;
  265.  
  266. {$F-} { Near ok now }
  267.  
  268. procedure AssignQue(var F : Text; LPTNumber : Word;
  269.                     QueueSize : Word);
  270. { like Turbo's assign, except associates Text variable with one of the LPTs }
  271. begin
  272.   with QTextRec(F) do begin
  273.     Mode       := fmClosed;
  274.     BufSize    := SizeOf(Buffer);
  275.     BufPtr     := @Buffer;
  276.     OpenFunc   := @LstOpen;   { open a print queue }
  277.     CloseFunc  := @LstClose;  { close a print queue }
  278.     InOutFunc  := @LstOutput; { you can Write and WriteLn to them }
  279.     FlushFunc  := @LstOutput;
  280.     LPTNo      := LPTNumber;  { user selected printer num (in [0..2]) }
  281.     QueSize    := QueueSize;
  282.     Move(LPTNames[LPTNumber],Name,4); { set name of device }
  283.     BufPos := 0; { reset BufPos }
  284.   end;
  285. end;
  286.  
  287. procedure ResetQueue(BufferToo : Boolean);
  288.  
  289. begin
  290.   { reset out pointer to 0 and if BufferToo then the buffer index also }
  291.   QOutPtr := 0;
  292.   if BufferToo then
  293.     QBI := 0;
  294. end;
  295.  
  296. function SendOutChar(C : Char; LPTNo : Word) : Byte;
  297.  
  298. var
  299.   ErrorCode        : Word;
  300.  
  301. begin
  302.   with QTextRec(DefaultLstDevice^) do begin
  303.     ErrorCode := LPTStat(LPTNo);
  304.     if LPTReady(ErrorCode) then begin
  305.       ErrorCode := DoInt17(C,LPTNo);
  306.       if LPTReady(ErrorCode) then
  307.         SendOutChar := 0
  308.       else
  309.         SendOutChar := ErrorCode
  310.     end
  311.     else
  312.       SendOutChar := ErrorCode;
  313.   end; {with}
  314. end;
  315.  
  316. procedure Int05Handler(BP : Word); Interrupt;
  317. { if FilterInt05 is FALSE, we will safely ignore all requests to print the }
  318. { screen }
  319. var
  320.   Regs             : IntRegisters absolute BP;
  321.  
  322. begin
  323.   if FilterInt05 then
  324.     ChainInt(Regs,ISR_Array[Int05_HANDLE].OrigAddr); { filter it }
  325.  
  326. end;
  327.  
  328. procedure QueueSystem(var Regs : IntRegisters);
  329.  
  330. { be as gentle as possible on the stack }
  331.  
  332. var
  333.   I                : Integer;
  334.  
  335. { type constants are used to avoid declaring these on the stack }
  336. const
  337.   ResetTheBuffer   : Boolean = FALSE;
  338.   NumToDo          : Word = 0;
  339.   CTP              : Word = 0;
  340.  
  341. Begin
  342.   CTP := QBI - QOutPtr;
  343.   if CHARS_PER < CTP then begin
  344.     NumToDo        := CHARS_PER;
  345.     ResetTheBuffer := FALSE;
  346.   end
  347.   else begin
  348.     ResetTheBuffer := TRUE;
  349.     NumToDo        := CTP;
  350.   end;
  351.   I := 1;
  352.   while (I <= NumToDo) and (BakError = 0) do begin
  353.     Inc(QOutPtr);
  354.  
  355.     BakError := SendOutChar(PrtQue^[QOutPtr],
  356.                             QTextRec(DefaultLstDevice^).LPTNo);
  357.     if (BakError <> 0) and (QueUserErrorFunc <> NIL) then begin
  358.       { force reset if user error func returns TRUE. }
  359.       Dec(QOutPtr); { adjust QOutPtr to point to last successfully }
  360.                     { printed character }
  361.       ResetTheBuffer:= BoolFuncFarCall(QueUserErrorFunc);
  362.     end;
  363.     Inc(I);
  364.   end; { while }
  365.   if ResetTheBuffer then ResetQueue(TRUE);
  366. end;
  367.  
  368. procedure TimerISR(BP : Word); Interrupt;
  369. var
  370.   Regs             : IntRegisters absolute BP;
  371.  
  372. const
  373.   Ticks            : Word = 0;
  374.  
  375. begin
  376.   EmulateInt(Regs,ISR_Array[Int1C_HANDLE].OrigAddr); { always filter int 1Ch! }
  377.   InterruptsOff; { I am paranoid about interrupts while checking semaphores }
  378.   if InTimerISR then begin
  379.     InterruptsOn;
  380.     Exit;
  381.   end;
  382.   Inc(Ticks);
  383.   InTimerISR := TRUE; { set global in-use semaphore }
  384.   InterruptsOn;
  385.  
  386.  
  387.   if (not QuePause) and (Ticks MOD TICKS_TO_WAIT = 0) then
  388.     SwapStackAndCallNear(Ofs(QueueSystem),
  389.                          @TimerStack[TIMER_STACK_SIZE],Regs);
  390.  
  391.   InterruptsOff; { more paranoia }
  392.   InTimerISR := FALSE; { clear global in-use semaphore }
  393.   InterruptsOn;
  394.  
  395. end;
  396.  
  397. begin
  398.   BakLptInstalled := FALSE;
  399.   PrtQue := NIL;
  400.   QueError := 0;
  401.   BakError := 0;
  402.  
  403.   AssignQue(Lst,LPT_1,QBUFMAX);
  404.                            { set up turbo pascal compatable Lst device }
  405.                            { that prints in the background using       }
  406.                            { biggest possible buffer (about 64k).      }
  407.  
  408.   Rewrite(Lst);            { open it for output }
  409.  
  410.   QueError := IOResult;
  411.   if QueError = 0 then begin
  412.     ExitSave := ExitProc;    { save old exit handler }
  413.     ExitProc := @ExitHandler;{ set new exit handler  }
  414.  
  415.  
  416.     if InitVector($1C,Int1C_HANDLE,@TimerISR) and
  417.                  InitVector($05,INT05_HANDLE,@Int05Handler) then begin
  418.       BakLptInstalled := TRUE;
  419.       QueError        := 0;
  420.     end
  421.     else
  422.       QueError        := ISR_NOT_INSTALLED;
  423.   end;
  424. end.
  425.