article

Getting the status of the selected printer from Visual Basic

Email
Submitted on: 1/22/2015 2:30:00 AM
By: Duncan Jones (from psc cd)  
Level: Advanced
User Rating: By 3 Users
Compatibility: VB 5.0, VB 6.0
Views: 5491
 
     Shows how you can use the Windows API to return additional information about the printer above and beyond that which is available through the Visual Basic Printer object.

 
				

 1. What the Printer object missed

 Printing has long been a very problematic part of developing complete and professional applications in Visual Basic. This was redressed to a large degree with the new Printer object introduced in Visual Basic 4.
 However, there are shortcomings with this object. The biggest shortcoming is that you cannot find out whether the printer is ready, busy, out of paper etc. from your application.
However, there is an API call, GetPrinter which returns a great deal more information about a printer.

<-- API Declaration -->

Private Declare Function GetPrinterApi Lib "winspool.drv" Alias _
       "GetPrinterA" (ByVal hPrinter As Long, _
         ByVal Level As Long, _
         buffer As Long, _
         ByVal pbSize As Long, _
         pbSizeNeeded As Long) As Long

 This takes the handle to a printer in hPrinter and fills the buffer provided to it with information from the printer driver. To get the handle from the Printer object, you need to use the OpenPrinter API call.
 This handle must be released using the ClosePrinter API call as soon as you are finished with it.

<-- API Declaration -->

Private Type PRINTER_DEFAULTS
  pDatatype As String
  pDevMode As DEVMODE
  DesiredAccess As Long
End Type

Private Declare Function OpenPrinter Lib "winspool.drv" _
    Alias "OpenPrinterA" (ByVal pPrinterName As String, _
    phPrinter As Long, pDefault As PRINTER_DEFAULTS) As Long

Private Declare Function ClosePrinter Lib "winspool.drv" _
    (ByVal hPrinter As Long) As Long

 You pass the Printer.DeviceName to this to get the handle.

<-- Use -->

Dim lret As Long
Dim pDef As PRINTER_DEFAULTS

lret = OpenPrinter(Printer.DeviceName, mhPrinter, pDef)

 2. The different statuses

 There are a number of standard statuses that can be returned by the printer driver.

<-- Enumerated type -->

Public Enum Printer_Status
   PRINTER_STATUS_READY = &H0
   PRINTER_STATUS_PAUSED = &H1
   PRINTER_STATUS_ERROR = &H2
   PRINTER_STATUS_PENDING_DELETION = &H4
   PRINTER_STATUS_PAPER_JAM = &H8
   PRINTER_STATUS_PAPER_OUT = &H10
   PRINTER_STATUS_MANUAL_FEED = &H20
   PRINTER_STATUS_PAPER_PROBLEM = &H40
   PRINTER_STATUS_OFFLINE = &H80
   PRINTER_STATUS_IO_ACTIVE = &H100
   PRINTER_STATUS_BUSY = &H200
   PRINTER_STATUS_PRINTING = &H400
   PRINTER_STATUS_OUTPUT_BIN_FULL = &H800
   PRINTER_STATUS_NOT_AVAILABLE = &H1000
   PRINTER_STATUS_WAITING = &H2000
   PRINTER_STATUS_PROCESSING = &H4000
   PRINTER_STATUS_INITIALIZING = &H8000
   PRINTER_STATUS_WARMING_UP = &H10000
   PRINTER_STATUS_TONER_LOW = &H20000
   PRINTER_STATUS_NO_TONER = &H40000
   PRINTER_STATUS_PAGE_PUNT = &H80000
   PRINTER_STATUS_USER_INTERVENTION = &H100000
   PRINTER_STATUS_OUT_OF_MEMORY = &H200000
   PRINTER_STATUS_DOOR_OPEN = &H400000
   PRINTER_STATUS_SERVER_UNKNOWN = &H800000
   PRINTER_STATUS_POWER_SAVE = &H1000000
End Enum

 3. The data structures

  As each printer driver is responsible for returning this data there has to be a standard to which this returned data conforms in order for one application to be able to query a number of different types of printers. As it happens, there are nine different standard data types that can be returned by the GetPrinter API call in Windows 2000 (only the first two are universal to all current versions of Windows).
Of these, the second is the most interesting - named PRINTER_INFO_2
<-- Data structure -->

Private Type PRINTER_INFO_2
   pServerName As String
   pPrinterName As String
   pShareName As String
   pPortName As String
   pDriverName As String
   pComment As String
   pLocation As String
   pDevMode As Long
   pSepFile As String
   pPrintProcessor As String
   pDatatype As String
   pParameters As String
   pSecurityDescriptor As Long
   Attributes As Long
   Priority As Long
   DefaultPriority As Long
   StartTime As Long
   UntilTime As Long
   Status As Long
   JobsCount As Long
   AveragePPM As Long
End Type

  However, it is not as simple as just passing this structure to the GetPrinter API call as a printer can return more information than is in that structure and if you do not allocate sufficent buffer space for it to do so your application will crash.
Fortunately the API call caters for this - if you pass zero in the pbSize parameter then the API call will tell you how big a buffer you will require in the pbSizeNeeded.
This means that filling the information from the printer driver becomes a two step process:

<-- Using the GetPrinter API call -->

  Dim lret As Long
  Dim SizeNeeded As Long

  Dim buffer() As Long

  ReDim Preserve buffer(0 To 1) As Long
  lret = GetPrinterApi(mhPrinter, Index, buffer(0), UBound(buffer), SizeNeeded)
  ReDim Preserve buffer(0 To (SizeNeeded / 4) + 3) As Long
  lret = GetPrinterApi(mhPrinter, Index, buffer(0), UBound(buffer) * 4, SizeNeeded)

<-- Retrieving the string part -->

  However the buffer is just an array of Long data types. Some of the data within the PRINTER_INFO_2 data structure is String data. This must be collected from the addresses which are stored in the appropriate buffer position.

  To get a string from a pointer the CopyMemory API call is used and there is also an API call, IsBadStringPtr, which can be used to verify that the address pointed to does actually contain a valid string.

<-- Declarations -->

'\\ Memory manipulation routines
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
'\\ Pointer validation in StringFromPointer
Private Declare Function IsBadStringPtrByLong Lib "kernel32" Alias "IsBadStringPtrA" (ByVal lpsz As Long, ByVal ucchMax As Long) As Long

Retrieving the string from a pointer is a common thing to have to do so it is worth having this utility function in your arsenal.

Public Function StringFromPointer(lpString As Long, lMaxLength As Long) As String

  Dim sRet As String
  Dim lret As Long

  If lpString = 0 Then
    StringFromPointer = ""
    Exit Function
  End If

  If IsBadStringPtrByLong(lpString, lMaxLength) Then
    '\\ An error has occured - do not attempt to use this pointer
      StringFromPointer = ""
    Exit Function
  End If

  '\\ Pre-initialise the return string...
  sRet = Space$(lMaxLength)
  CopyMemory ByVal sRet, ByVal lpString, ByVal Len(sRet)
  If Err.LastDllError = 0 Then
    If InStr(sRet, Chr$(0)) > 0 Then
      sRet = Left$(sRet, InStr(sRet, Chr$(0)) - 1)
    End If
  End If

  StringFromPointer = sRet

End Function

 So to use this to populate your PRINTER_INFO_2 variable:

With mPRINTER_INFO_2 '\\ This variable is of type PRINTER_INFO_2
   .pServerName = StringFromPointer(buffer(0), 1024)
   .pPrinterName = StringFromPointer(buffer(1), 1024)
   .pShareName = StringFromPointer(buffer(2), 1024)
   .pPortName = StringFromPointer(buffer(3), 1024)
   .pDriverName = StringFromPointer(buffer(4), 1024)
   .pComment = StringFromPointer(buffer(5), 1024)
   .pLocation = StringFromPointer(buffer(6), 1024)
   .pDevMode = buffer(7)
   .pSepFile = StringFromPointer(buffer(8), 1024)
   .pPrintProcessor = StringFromPointer(buffer(9), 1024)
   .pDatatype = StringFromPointer(buffer(10), 1024)
   .pParameters = StringFromPointer(buffer(11), 1024)
   .pSecurityDescriptor = buffer(12)
   .Attributes = buffer(13)
   .Priority = buffer(14)
   .DefaultPriority = buffer(15)
   .StartTime = buffer(16)
   .UntilTime = buffer(17)
   .Status = buffer(18)
   .JobsCount = buffer(19)
   .AveragePPM = buffer(20)
End With

Source code for this article to download

The complete source code for these examples is available for download here
You will be asked to register with Yahoo!Groups in order to access it.


Other 17 submission(s) by this author

 


Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Advanced category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments


 There are no comments on this submission.
 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)
 

To post feedback, first please login.