home *** CD-ROM | disk | FTP | other *** search
/ Monster Media 1994 #1 / monster.zip / monster / PROG_PAS / LPT.ZIP / LPT.PAS < prev    next >
Pascal/Delphi Source File  |  1993-12-22  |  14KB  |  330 lines

  1. {$R-,S-}
  2. {
  3.   ** LPT Unit **
  4.   ** Copyright (c) 1988 Richard S. Sadowsky
  5.   ** by Richard S. Sadowsky
  6.   ** 1/12/88
  7.   ** version 1.0
  8.  
  9.   Revised by
  10.    Mark Reichert, 72763,2417
  11.    11/1/93
  12.    version 2.0
  13.   The DOS references warn against relying on the Int 17 Bios function since
  14.   only IBM and Epson printers and printers that are 100% compatible to them
  15.   reliably send back the documented bits for all situations.  However, there
  16.   are to very good reasons for using this unit.
  17.  
  18.   First, the reason to use this unit at all over the default use of the
  19.   Int 21, Function 40 (Write to File or Device) is that that function
  20.   doesn't check the buffer.  At least on the computer/printer setup in my
  21.   office (DELL, HP LJ), part of a string will be put into the buffer before
  22.   a DriveNotReady critical error message is sent back (remember, it's Write
  23.   to File, as well as Device).  Thus part of line is already in the buffer
  24.   with NO way to get it back off AND no way of telling how much of it is out
  25.   there.  Just the usual fun provided by Microsoft.  Sending the output a
  26.   character at a time lets you intercept a busy (buffer full) message and
  27.   have the program wait beyond the timeout done by the Int 17 and printer.
  28.  
  29.   Second, I'm only relying on the printer and computer manufacturers not
  30.   being malicious.  You will see so in my comment right before the code
  31.   for the Out_Chars function that actually sends a line out to the printer.  }
  32.  
  33. unit Lpt;
  34.  
  35. interface
  36.  
  37. Uses Crt,    { Uses Delay }
  38.      Dos,    { Uses fmOutput }
  39.      IOChek; { Uses ErrorEnum OutOfPaper, etc. }
  40.              { This unit is in Dos Programming in the BP CompuServe Library }
  41.  
  42. Type
  43.   Strg5 = String[5];
  44.  
  45.   { Hook for third party to make their own screen environment compatible error box
  46.     pop up with appropriate message for error code;  Escaped indicates whether the
  47.     user 'escaped' out of box to end printing altogether }
  48.   PrinterErrorProc = Procedure (Var ErrCode : Integer; Var Escaped : Boolean);
  49.  
  50.   { Hook for third party to write to their own log file to keep track of all error
  51.     conditions including the ones being handled by the program like busy signals;
  52.     Depending on unit variables set by procedures called from outside of the log unit,
  53.     TypeOfMsg, an enum in my system, allows the LogProc to skip writing the LogStr.
  54.     This way most of the time the Log would show only the most important (Major)
  55.     messages, but through parameter strings or DOS environment variables, one
  56.     could 'turn on' other (Minor) statements to get more information on the next run }
  57.   PrinterLogProc   = Function (TypeOfMsg : Word; Const LogStr : String) : Integer;
  58.  
  59. const
  60.   { Also have other values for whether to additionally Flush Buffer after write
  61.     and whether to write another statement afterwards to show MemAvail and MaxAvail,
  62.     but I want to keep this simple here }
  63.  
  64.   Major = 0;
  65.   Minor = 1;
  66.   LPTNames  : array[0..2] of Strg5 = ('LPT1'#0,'LPT2'#0,'LPT3'#0);
  67.  
  68. procedure AssignLst(var F : Text; LPTNumber : Word);
  69. { like Turbo's assign, except associates Text variable with one of the LPTs }
  70.  
  71. Procedure AssignPrintErrorHandler(PrintErrorHandler : PrinterErrorProc);
  72.  
  73. Procedure AssignLogHandler(LogHandler : PrinterLogProc);
  74.  
  75. type
  76.   TextBuffer       = array[0..127] of Char;
  77.  
  78.   TextRec          = record
  79.                        Handle     : Word;
  80.                        Mode       : Word;
  81.                        BufSize    : Word;
  82.                        Private    : Word;
  83.                        BufPos     : Word;
  84.                        BufEnd     : Word;
  85.                        BufPtr     : ^TextBuffer;
  86.                        OpenFunc   : Pointer;
  87.                        InOutFunc  : Pointer;
  88.                        FlushFunc  : Pointer;
  89.                        CloseFunc  : Pointer;
  90.                        { 16 byte user data area, I use 4 bytes }
  91.                        PrintMode  : Word;  { not currently used}
  92.                        LPTNo      : Word;  { LPT number in [0..2] }
  93.                        UserData   : array[1..12] of Char;
  94.                        Name       : array[0..79] of Char;
  95.                        Buffer     : TextBuffer;
  96.                      end;
  97.  
  98. implementation
  99.  
  100. Const
  101.   NumOfLinesBusy  : Word = 0;
  102.   LPTErrorHandler : PrinterErrorProc = nil;
  103.   LPTLogHandler   : PrinterLogProc   = nil;
  104.  
  105. { This function returns numbers equal to the ordinal value of ErrorEnums from the
  106.   IOCHEK unit, to assembler they're the same thing.
  107.  
  108.   As I state above, this function only assumes that the manufacturers are not
  109.   malicious.  The first bit check is for NO PAPER, because I think that is the
  110.   one bit no manufacturer would screw up.  The rest assume most other printers
  111.   act like my HP LaserJet IIP+.
  112.  
  113.   Second, it checks for the SELECTED bit, the status of which sort of divides the
  114.   recognized errors in two groups.  If it was set, the TIMEOUT bit is checked, and
  115.   if it is NOT set, then the character went through fine and we can send another.
  116.   If the TIMEOUT bit was set, it makes sure the NOT BUSY bit was NOT set.  This is
  117.   really belt and suspenders safety; it was useless on my HP because that bit was
  118.   never set, but it might be on others.  I'm only assuming that no printer would
  119.   set it when the printer ACTUALLY IS busy.  A DriveNotReady error is noted because
  120.   that is what the BP Critical Error Handler gave in this situation.
  121.  
  122.   Third, if the SELECTED bit is not set, the TIMEOUT bit is checked, and if it is
  123.   set, then that meant that the machine was ON but OFFLINE and I figure that that
  124.   would be true on most setups.  A DeviceWriteFault error is noted because that
  125.   is what the BP Critical Error Handler gave in this situation.
  126.  
  127.   Last, both the SELECTED and TIMEOUT bits are not set, we have an unknown I/O error,
  128.   so the I/O Error bit is checked and if it is set, then a UnknownCommand error number
  129.   is sent back.  If it isn't, the actual byte sent back by the printer is return, so
  130.   that the bits can be looked at by the main program. }
  131.  
  132. function Out_Chars(Var CurrBuffPtr : Pointer; Var NumOfChars : Word; LPTNo : Word) : Integer; assembler;
  133. Asm
  134.   mov BX, DS
  135.   LDS SI, NumOfChars     { get location of NumOfChars variable }
  136.   mov CX, [SI]           { load into CX for loop }
  137.   LDS SI, CurrBuffPtr    { get location of CurrBuffPtr variable }
  138.   LDS SI, [SI]           { get location of Buffer it is pointing to }
  139.   mov DX, LPTNo          { get printer number - 0 = LPT1, 1 = LPT2, 2 = LPT3 }
  140.   cld                    { make sure lodsb increments SI by clearing direction flag }
  141. @LoopTop:                { start loop }
  142.   lodsb                  { get char }
  143.   mov AH, 00h            { set AH for BIOS int 17h function 0 }
  144.   int 17h                {  do an int 17h to sent to printer }
  145.   xchg AL, AH            { put byte result in AL }
  146.   test AL, 100000b       { see if the No Paper flag is set }
  147.   jz @HadPaper
  148.   mov AL, OutOfPaper     { No Paper = OutOfPaper, enum is num to asm }
  149.   jmp @Error
  150. @HadPaper:
  151.   test AL, 10000b        { see if the selected flag is not set }
  152.   jz @NotSelected
  153.   test AL, 01b           { see if the time out flag is set }
  154.   jnz @Busy
  155.   loop @LoopTop          { unless that was the last char, loop to the top }
  156.   mov AL, NoError        { Selected and Not TimeOut = NoError, enum is num to asm }
  157.   jmp @end
  158. @Busy:
  159.   test AL, 10000000b     { see if the busy flag is not set }
  160.   jnz @Unknown
  161.   mov AL, DriveNotReady  { Selected And TimeOut And Busy = DriveNotReady }
  162.   jmp @Error               { DriveNotReady usually = Buffer Full, Printer Busy }
  163. @NotSelected:
  164.   test AL, 01b           { see if the time out flag is set }
  165.   jz @Unknown
  166.   mov AL, DeviceWriteFault { Not Selected and TimeOut = DeviceWriteFault }
  167.   jmp @Error                  { DeviceWriteFault usually = OffLine }
  168. @Unknown:
  169.   test AL, 1000b         { see if the IO Error flag is set }
  170.   jz @end                { just leave error in return if we can't identify it }
  171.   mov AL, UnknownCommand { Not Selected And Not TimeOut and IO Error = UnknownCommand }
  172. @Error:
  173.   mov DI, SI             { by pointing DI back one char, the next run of Out_Chars }
  174.   dec DI                 { will reload char that was last tried and didn't go thru }
  175.   LDS SI, CurrBuffPtr    { get the location of the pointer to the buffer again }
  176.   mov word ptr [SI], DI  { SEG same, offset has changed as LODSB has pulled of chars }
  177. @end:
  178.   LDS SI, NumOfChars
  179.   mov word ptr [SI], CX  { return the number of chars left }
  180.   xor AH, AH             { make AX = AL for return as integer }
  181.   mov DS, BX
  182. End;
  183.  
  184. function LstIgnore(var F : TextRec) : Integer; far;
  185. { A do nothing, no error routine }
  186. begin
  187.   LstIgnore := 0 { return 0 for IOResult }
  188. end;
  189.  
  190. function LstOutput(var F : TextRec) : Integer; far;
  191. { Send whatever has accumulated in the Buffer to int 17h           }
  192. { If error occurs, return in IOResult.  See BP 7.0 Language Guide, }
  193. { Chapter 14, page 172 for more info on Text-File Device Drivers   }
  194. var
  195.   I, NumOfCharsBusy,
  196.   NoOfChars  : Word;
  197.   CurBufPtr  : Pointer;
  198.   ErrorCode  : Integer;
  199.   Escaped,
  200.   ProcessError : Boolean;
  201. begin
  202.   LstOutput := 0;
  203.   NumOfCharsBusy := 0;
  204.   with F do
  205.     begin
  206.       { if the file has not been opened for Output, send an error number }
  207.       If Mode = fmOutput Then
  208.         Begin
  209.           I := 0;
  210.           NoOfChars := BufPos;
  211.           CurBufPtr := BufPtr;
  212.           Repeat
  213.             { send chars to printer }
  214.             ErrorCode := Out_Chars(CurBufPtr, NoOfChars, LPTNo);
  215.             if ErrorCode <> Ord(NoError) then
  216.               begin { if error }
  217.                 ProcessError := True;
  218.                 If ErrorCode = Ord(DriveNotReady) Then
  219.                   Begin
  220.                     ProcessError := False;
  221.                     { The first five busy characters in a line are handled
  222.                       here.  5 is an arbitrary number and can be changed }
  223.                     If NumOfCharsBusy < 5 Then
  224.                       Begin
  225.                        { if the handler hasn't been given, don't attempt it }
  226.                         If Assigned(LPTLogHandler) Then
  227.                           LPTLogHandler(Major, 'Char was Busy');
  228.                         Delay(100);
  229.                         Inc(NumOfCharsBusy);
  230.                       End
  231.                     Else
  232.                     { The first 2 lines of 5 busy characters each are handled
  233.                       here.  2 is an arbitrary number and can be changed }
  234.                       If NumOfLinesBusy < 2 Then
  235.                         Begin
  236.                           If Assigned(LPTLogHandler) Then
  237.                             LPTLogHandler(Major, 'Line was Busy');
  238.                           NumOfCharsBusy := 0;
  239.                           Delay(500);
  240.                           Inc(NumOfLinesBusy);
  241.                         End
  242.                       Else
  243.                     { If more than 2 lines of 5 busy characters are recieved
  244.                       then we should tell the user who might want to attempt
  245.                       to print later. }
  246.                         ProcessError := True;
  247.                   End;
  248.                 If ProcessError Then
  249.                   Begin
  250.                     Escaped := False;
  251.                     NumOfLinesBusy := 0;
  252.                     { if the handler hasn't been given, don't attempt it }
  253.                     If Assigned(LPTErrorHandler) Then
  254.                       LPTErrorHandler(ErrorCode, Escaped)
  255.                     Else
  256.                       Escaped := True;
  257.                     If Escaped Then
  258.                       Begin
  259.                        { If BufPos <> 0, next writeln will put chars at end
  260.                          of current chars in buffer }
  261.                         BufPos := 0;
  262.                         LstOutput := ErrorCode;    { return errorcode in IOResult }
  263.                         Exit                       { return from function }
  264.                       End;
  265.                   End;
  266.               end;
  267.           Until (ErrorCode = 0) and (NoOfChars <= 0);
  268.           { at this point, the line should have gone through successfully }
  269.         end
  270.       Else
  271.         LstOutput := Ord(FileNotOpenForOutput);
  272.       BufPos := 0;
  273.     End;
  274. end;
  275.  
  276. { This is one place where Mr. Sadowsky's version was inconsistent with the
  277.   'correct' usage, or at least the way Turbo Vision uses TFDD's to write to
  278.   a message window on the screen.  First, it is the LstOpen function setup
  279.   by Assign and called by Reset or Rewrite that sets InOutFunc and FlushFunc,
  280.   and not Assign.  Second, it is not a good idea to set FlushFunc to LstOutput
  281.   if there is any chance that the programmer will try to 'close' the printer.
  282.   Doing so will cause a random memory dump as a nonexistant, at this point,
  283.   buffer is written to the printer. }
  284.  
  285. function LstOpen(var F : TextRec) : Integer; far;
  286. begin
  287.   with F do
  288.   begin
  289.     if Mode = fmOutput then
  290.     begin
  291.       InOutFunc := @LstOutPut;
  292.  
  293.       { making FlushFunc = InOutFunc caused closing the file to try to print
  294.         garbage from the buffer, since BP's Close calls Flush.  I don't think
  295.         that we need flush for printing so.. }
  296.       FlushFunc := @LstIgnore;
  297.     end;
  298.   End;
  299.   LstOpen := 0 { return 0 for IOResult }
  300. end;
  301.  
  302. procedure AssignLst(var F : Text; LPTNumber : Word);
  303. { like Turbo's assign, except associates Text variable with one of the LPTs }
  304.  
  305. begin
  306.   with TextRec(F) do begin
  307.     Handle     := $FFFF;      { this is not a file, it has no real handle }
  308.     Mode       := fmClosed;   { but it should be 'opened' through a Rewrite }
  309.     BufSize    := SizeOf(Buffer);
  310.     BufPtr     := @Buffer;
  311.     OpenFunc   := @LstOpen; { LstOpen assigns the InOutFunc and FlushFunc }
  312.     CloseFunc  := @LstIgnore; { you don't close the printer }
  313.     LPTNo      := LPTNumber;  { user selected printer num (in [0..2]) }
  314.     Move(LPTNames[LPTNumber][1],Name,5); { set name of device }
  315.     BufPos := 0; { reset BufPos }
  316.   end;
  317. end;
  318.  
  319. Procedure AssignPrintErrorHandler(PrintErrorHandler : PrinterErrorProc);
  320. Begin
  321.   LPTErrorHandler := PrintErrorHandler;
  322. End;
  323.  
  324. Procedure AssignLogHandler(LogHandler : PrinterLogProc);
  325. Begin
  326.   LPTLogHandler := LogHandler;
  327. End;
  328.  
  329. end.
  330.