Paradox Community
Search:

 Welcome |  What is Paradox |  Paradox Folk |  Paradox Solutions |
 Interactive Paradox |  Paradox Programming |  Internet/Intranet Development |
 Support Options |  Classified Ads |  Wish List |  Submissions 


Paradox Programming Articles  |  Beyond Help Articles  |  Tips & Tricks Articles  


Win32 API and Printers
Getting Printer Jobs
© 2002 Rick Kelly
www.crooit.com

Preface

Libraries and forms (Paradox 9) presented are available as a download here. Paradox 8 is not supported although Paradox 7-32 surprisingly works. After downloading into the folder of your choice, make that folder :WORK: and run the included form for a demonstration. You'll need to run the form before opening it in design mode as it references table :PRIV:__PJobs that is created in the form's INIT event. (If running Paradox 10, or if the table doesn't get created properly, you should first run the BldSpool.ssl script prior to running the form.)


Introduction

The first article in our series showed how the Win32 API could be used to retrieve the default Windows printer, interrogate some basic properties for any properly installed Windows printer and the second article covered setting the default Windows printer. In this article we'll cover how to retrieve printer spooler jobs building upon and reusing the methods presented earlier.

From Windows perspective, the status of printers and print jobs is updated when a job is printing or despooling. At other times, when the printer is not printing and is not reporting any status information, the printer is considered to be ready and idle. Windows treats a physical printer as nothing more than the final destination of a print job as generated through a system defined interface.

The physical interface to a printer is known as the port monitor. Port monitors are responsible for sending the print job to the printer across whatever connection is defined and to report any errors that may occur.

Windows is not concerned with the state of the physical printer which ultimately determines the success of a print job at the time it is handed off to the port monitor. If the port monitor detects an error, it is posted in the print job's status flags. Consequently, a printer reports no status when its associated queue is empty and it is assumed that the printer is ready to accept print jobs. Applications are permitted to complete spooling of their print jobs and it is the user's responsibility to address any errors.

Remember that the true status of a physical printer can only be determined when Windows is attempting to send a job via the port monitor.


Referenced Win32 API

The following Win32 API method is used to retrieve or enumerate printer spool jobs.
EnumJobs(
  hPrn cLong,
  FirstJob cLong,
  NoJobs cLong,
  cLevel cLong,
  pJob cLong,
  cbBuf cLong,
  pcbNeeded cPtr,
  pcbReturned cPtr) cLong [stdcall "EnumJobsA"]
hPrn - Handle to the printer
FirstJob - Specifies the zero based position within the print queue of the first print job to enumerate - always 0 (start with first job) for our use
NoJobs - Specifies the total number of print jobs to enumerate - always -1 (all Jobs) for our use
cLevel = Level or type of spooler job information structure to return - we will use 2
pJob - pointer to address our job structures
cbBuf - Specifies the size, in bytes, of pJob
pcbNeeded = size of memory needed to hold pJob
pcbReturned = number of job record structures returned


Get Printer Spool Jobs

The job information structures returned by Win32 API method EnumJobs contain numerous data fields describing the print job for the specified printer. As Paradox does not support arrays of Record Types, we will use tables to capture print job information with one record per job. To allow forms to reference temporary tables in their data model, a method for creating the table in the forms INIT event is needed.

Example:
method CreateSpoolTable(stTableName String, var tbAny Table) Logical
;
; Create table to hold printer spool jobs
;
 tbAny = create stTableName
  with
   "JobPosition"  : "I",
   "JobID"        : "I",
   "MachineName"  : "A64",
   "UserName"     : "A64",
   "DocumentName" : "A255",
   "NotifyName"   : "A64",
   "DataType"     : "A32",
   "JobStatus"    : "A32",
   "Priority"     : "I",
   "StartTime"    : "T",
   "UntilTime"    : "T",
   "TotalPages"   : "S",
   "TotalSize"    : "I",
   "JobSubmitted" : "@",
   "TimePrinting" : "I",
   "PagesPrinted" : "S"
  key
   "JobPosition"
 endCreate
 return isTable(stTableName)
endMethod
Our method to retrieve printer job characteristics will support passing the name and optional creation of the table to contain the job enumeration and will return the status flags of the current job printing.

Constants referenced are:
cnOneDay    = 86400000.0  ;Milliseconds in a day
cnOneHour   = 3600000.0   ;Milliseconds in a hour
cnOneMinute = 60000.0     ;Milliseconds in a minute
cnOneSecond = 1000.0      ;Milliseconds in a second
;
; Printer Status
;
cnStatusPrinting = 1024
Example:
method GetPrinterJobs(
         stPrinter String,
         stTableName String,
         loCreate Logical,
         liSpoolStatus LongInt) Logical
var
  loReturn          Logical
  tbAny             Table
  tcAny             TCursor
  liPrinterHandle   LongInt
  liReturn          LongInt
  liSizeNeeded      LongInt
  liSizeUsed        LongInt
  liMemoryStructure LongInt
  liAny             LongInt
  liPointer         LongInt
  liIndex           LongInt
  liOffset          LongInt
  siMonth           SmallInt
  siDay             SmallInt
  siYear            SmallInt
  siHour            SmallInt
  siMinute          SmallInt
  siSecond          SmallInt
  siMilliSecond     SmallInt
  daAny             Date
  tiAny             Time
endVar
  liSpoolStatus = 0
  loReturn = False
;
; Check if spooler table needs to be created
;
  switch
    case loCreate = False :
      loReturn = tbAny.attach(stTableName)
      switch
        case loReturn = True :
          tbAny.empty()
      endSwitch
    otherwise :
      loReturn = CreateSpoolTable(stTableName,tbAny)
  endSwitch
;
; See if we can open our spooler table
;
  switch
    case loReturn = False :
    case tcAny.open(tbAny) = False :
      loReturn = False
    otherwise :
      loReturn = False
      tcAny.edit()
;
; Get a handle to the printer
;
      liPrinterHandle = 0
      liReturn = OpenPrinter(stPrinter,liPrinterHandle,0)
;
; Check if stPrinter contained a valid printer name
;
      switch
        case liReturn <> 1 :
        otherwise :
;
; First call is to get buffer size needed
;
          liSizeNeeded = 0
          liSizeUsed = 0
          liReturn = ENumJobs(
            liPrinterHandle,
            0,
            -1,
            2,
            0,
            0,
            liSizeNeeded,
            liSizeUsed)
;
; Allocate memory for Jobs Info Structure Level 2
;
          liMemoryStructure = GlobalAlloc(fromHex("0x40"),liSizeNeeded)
;
; Get Jobs Info Structure
;
          liReturn = ENumJobs(
            liPrinterHandle,
            0,
            -1,
            2,
            liMemoryStructure,
            liSizeNeeded,
            liSizeNeeded,
            liSizeUsed)
          switch
            case liReturn <> 1 :
            case liSizeUsed < 1 :
              loReturn = True
            otherwise :
              loReturn = True
              liAny = 0
              liPointer = 0
;
; Parse Jobs Info Structure Level 2
;
              for liIndex from 1 to liSizeUsed
;
; Calculate start of current job structure
;
                liOffset = liMemoryStructure
                  + (liIndex * 104)
                  - 104
;
; Build Job Record
;
                tcAny.insertRecord()
                MoveFromMemory(liAny,liOffset,4)
                tcAny."JobID" = liAny
                MoveFromMemory(liPointer, liOffset + 8,4)
                tcAny."MachineName" = cmSaveString(liPointer)
                MoveFromMemory(liPointer, liOffset + 12,4)
                tcAny."UserName" = cmSaveString(liPointer)
                MoveFromMemory(liPointer, liOffset + 16,4)
                tcAny."DocumentName" = cmSaveString(liPointer)
                MoveFromMemory(liPointer, liOffset + 20,4)
                tcAny."NotifyName" = cmSaveString(liPointer)
                MoveFromMemory(liPointer, liOffset + 24,4)
                tcAny."DataType" = cmSaveString(liPointer)
                MoveFromMemory(liPointer, liOffset + 44,4)
                tcAny."JobStatus" = cmSaveString(liPointer)
;
; If job is printing, save status flags
;
                MoveFromMemory(liAny,liOffset + 52,4)
                switch
                  case liAny.bitAnd(cnStatusPrinting) = cnStatusPrinting :
                    liSpoolStatus = liAny
                endSwitch
                MoveFromMemory(liAny,liOffset + 56,4)
                tcAny."Priority" = liAny
                MoveFromMemory(liAny,liOffset + 60,4)
                tcAny."JobPosition" = liAny
                MoveFromMemory(liAny,liOffset + 64,4)
                switch
                  case liAny > 0 :
                    tcAny."StartTime" = PW32Cnvt.GetTimeFromMinutes(liAny)
                endSwitch
                MoveFromMemory(liAny,liOffset + 68,4)
                switch
                  case liAny > 0 :
                    tcAny."UntilTime" = PW32Cnvt.GetTimeFromMinutes(liAny)
                endSwitch
                MoveFromMemory(liAny,liOffset + 72,4)
                tcAny."TotalPages" = liAny
                MoveFromMemory(liAny,liOffset + 76,4)
                tcAny."TotalSize" = liAny
;
; Retrieve system time values
;
                siMonth = 0
                siDay = 0
                siYear = 0
                siHour = 0
                siMinute = 0
                siSecond = 0
                siMillisecond = 0
                MoveFromMemory(siYear, liOffset + 80,2)
                MoveFromMemory(siMonth, liOffset + 80 + 2,2)
                MoveFromMemory(siDay, liOffset + 80 + 6,2)
                daAny = date(siMonth,siDay,siYear)
                MoveFromMemory(siHour, liOffset + 80 + 8,2)
                MoveFromMemory(siMinute, liOffset + 80 + 10,2)
                MoveFromMemory(siSecond, liOffset + 80 + 12,2)
                MoveFromMemory(siMilliSecond, liOffset + 80 + 14,2)
                tiAny = time(siHour * cnOneHour +
                          siMinute * cnOneMinute +
                          siSecond * cnOneSecond +
                          siMillisecond)
                tcAny."JobSubmitted" =
                  datetime((number(daAny) * cnOneDay) +
                           number(tiAny) +
                           (cmGetTimeZoneOffset() *
                            cnOneMinute))
                MoveFromMemory(liAny,liOffset + 96,4)
                tcAny."TimePrinting" = liAny
                MoveFromMemory(liAny, liOffset + 100,4)
                tcAny."PagesPrinted" = liAny
                tcAny.unlockRecord()
              endFor
          endSwitch
          liReturn = GlobalFree(liMemoryStructure)
          liReturn = ClosePrinter(liPrinterHandle)
      endSwitch
  endSwitch
  switch
    case tcAny.isAssigned() = True :
      tcAny.close()
  endSwitch
  return loReturn
endMethod

Conclusion

We now have methods that provide access to the Win32 API for accessing and retrieving print jobs.


Next: Controlling Jobs in the Print Spooler


Discussion of this article


 Feedback |  Paradox Day |  Who Uses Paradox |  I Use Paradox |  Downloads 


 The information provided on this Web site is not in any way sponsored or endorsed by Corel Corporation.
 Paradox is a registered trademark of Corel Corporation.


 Modified: 15 May 2003
 Terms of Use / Legal Disclaimer


 Copyright © 2001- 2003 Paradox Community. All rights reserved. 
 Company and product names are trademarks or registered trademarks of their respective companies. 
 Authors hold the copyrights to their own works. Please contact the author of any article for details.