Monday, August 22, 2011

Read Printer Status from PowerBuilder

Based on another question I got via email, and couldn't find a good example for, so I created one.

The printer status is not reported back by the EnumJobs API method.  Instead, that reports back the status of the pending job on the printer.  If there are no pending jobs, the EnumJobs API won't tell you much about what's going on with the printer.

The solution is to use the GetPrinter API method with a level 2 call, which returns the PRINTER_INFO_2 structure containing, among other things, the printer status code.

The PRINTER_INFO_2 structure is actually just a collection of pointers, so what we're going to do in the sample is just pass an array of longs to the call and then convert some of the information back into values (i.e. strings) that actually mean something to us.

Note that the call will fail if we haven't pre-allocated enough memory in the object we pass in by reference to get the information, so we're going to call the method twice.  The first time we pass a long with a value of 0 for the buffer, so that the method will tell us how much memory we need to actually allocate for the call.  We then allocate that memory and make the call a second time passing in the actual buffer.

First, some constants with the values for the status attribute we're interested in:
Constant long PRINTER_STATUS_READY = 0 // &H0
Constant long PRINTER_STATUS_PAUSED = 1 // &H1
Constant long PRINTER_STATUS_ERROR = 2 // &H2
Constant long PRINTER_STATUS_PAPER_JAM = 8 // &H8
Constant long PRINTER_STATUS_PAPER_OUT = 16 // &H10
Constant long PRINTER_STATUS_MANUAL_FEED = 32 // &H20
Constant long PRINTER_STATUS_PAPER_PROBLEM = 64 // &H40
Constant long PRINTER_STATUS_OFFLINE = 132 // &H80
Constant long PRINTER_STATUS_IO_ACTIVE = 256 // &H100
Constant long PRINTER_STATUS_BUSY = 512 // &H200
Constant long PRINTER_STATUS_PRINTING = 1024 // &H400
Constant long PRINTER_STATUS_OUTPUT_BIN_FULL = 2048 //&H800
Constant long PRINTER_STATUS_NOT_AVAILABLE = 4096 // &H1000
Constant long PRINTER_STATUS_WAITING = 8192 // &H2000
Constant long PRINTER_STATUS_PROCESSING = 16384 // &H4000
Constant long PRINTER_STATUS_INITIALIZING = 32768 // &H8000
Constant long PRINTER_STATUS_WARMING_UP = 65536 // &H10000
Constant long PRINTER_STATUS_TONER_LOW = 131072 // &H20000
Constant long PRINTER_STATUS_NO_TONER = 262144 // &H40000
Constant long PRINTER_STATUS_PAGE_PUNT = 524288 // &H80000
Constant long PRINTER_STATUS_USER_INTERVENTION = 1048576 // &H100000
Constant long PRINTER_STATUS_OUT_OF_MEMORY = 2097152 //&H200000
Constant long PRINTER_STATUS_DOOR_OPEN = 4194304 // &H400000
Constant long PRINTER_STATUS_SERVER_UNKNOWN = 8388608 // &H800000
Constant long PRINTER_STATUS_POWER_SAVE = 16777216 // &H1000000

Next, we need some local external function declarations for the OpenPrinter/ClosePrinter calls we need to make to use the GetPrinter call.  We also need to declare a function pointing at the RltMoveMemory API call so we can convert pointers back to string values.  There are two declarations for GetPrinter, one that takes a single long for the buffer value for the first call and the second that takes the long array.
Function long GetPrinter (ulong hPrinter, long Level, long buffer, 
long pbSize, ref long pbSizeNeeded ) Library
"winspool.drv" Alias For"GetPrinterW"
Function long GetPrinter (ulong hPrinter, long Level, ref ulong
buffer[], long pbSize, ref long pbSizeNeeded ) Library
"winspool.drv" Alias For"GetPrinterW"
Function long OpenPrinter ( string pPrinterName, ref ulong phPrinter,
long pDefault ) Library "winspool.drv" Alias For "OpenPrinterW"
Function long ClosePrinter ( ulong hPrinter) Library "winspool.drv"
Subroutine CopyMemory ( ref blob Destination, long Source,
Long Length) Library "kernel32" Alias For "RtlMoveMemory"

We also have a helper function that takes a ulong as an argument and returns a string that converts pointers to string values:
integer	li_len
integer li_max = 1024
string ls_temp
blob lblb_temp

IF al_pointer = 0 THEN Return ''

lblb_temp = Blob ( Space ( li_max ) )
CopyMemory ( lblb_temp, al_pointer, li_max )

li_len = Len ( lblb_temp )

ls_temp = String ( lblb_temp )

Return ls_temp

We need someplace to put the data when we're done, so here's the definition of the printer_info_2 structure (just referenced as printer_info in the code):
global type printer_info from structure
string pservername
string pprintername
string psharename
string pportname
string pdrivername
string pcomment
string plocation
long pdevmode
string psepfile
string pprintprocessor
string pdatatype
string pparameters
long psecuritydescriptor
long attributes
long priority
long defaultpriority
long starttime
long untiltime
long status
long jobscount
long averageppm
end type

And now the function that actually gets the status (as well as a bunch of other information:
string 			ls_printer
Long rc
ulong hPrinter
Long ll_pos
Long ll_size
printer_info pi
integer li_index
ulong ll_buffer[]

ls_printer = PrintGetPrinter()
ll_pos=pos (ls_printer, "~t")
ls_printer=left(ls_printer, ll_pos -1)

rc = OpenPrinter(ls_printer, hPrinter, 0 )

rc = GetPrinter ( hPrinter, 2, 0, 0, ll_size )

FOR li_index = 1 TO ( ll_size / 4 )
ll_buffer[li_index] = 0

rc = GetPrinter ( hPrinter, 2, ll_buffer, ll_size, ll_size )
rc = ClosePrinter ( hPrinter )

pi.pservername = of_getstringfrompointer ( ll_buffer[1] )
pi.pprintername = of_getstringfrompointer ( ll_buffer[2] )
pi.pShareName = of_getStringFrompointer(ll_buffer[3])
pi.pPortName = of_getStringFrompointer(ll_buffer[4])
pi.pDriverName = of_getStringFrompointer(ll_buffer[5])
pi.pComment = of_getStringFrompointer(ll_buffer[6])
pi.pLocation = of_getStringFrompointer(ll_buffer[7])
pi.pDevMode = ll_buffer[8]
pi.pSepFile = of_getStringFrompointer(ll_buffer[9])
pi.pPrintProcessor = of_getStringFrompointer(ll_buffer[10])
pi.pDatatype = of_getStringFrompointer(ll_buffer[11])
pi.pParameters = of_getStringFrompointer(ll_buffer[12])
pi.pSecurityDescriptor = ll_buffer[13]
pi.Attributes = ll_buffer[14]
pi.Priority = ll_buffer[15]
pi.DefaultPriority = ll_buffer[16]
pi.StartTime = ll_buffer[17]
pi.UntilTime = ll_buffer[18]
pi.Status = ll_buffer[19]
pi.JobsCount = ll_buffer[20]
pi.AveragePPM = ll_buffer[21]


Setting the Printer Status using PowerBuilder « Bruce Armstrong’s Blog said...

[...] a comment » OK, now that we know how to get the printer status, let’s see how to set it as [...]

Graeme Cooke said...

Hi Bruce,

What version of PB did you use when writing this code? The reason I ask is that I am getting a %s error when calling the CopyMemory function. This only seems to happen when I am checking the status of my network printers. I had wondered if there was a unicode versus Ansi issue.


brucearmstrong said...

Well, given that I'm using the W version of the API calls, it looks like it's already converted to Unicode. Not sure what the issue might be.

Graeme Cooke said...

Thanks for the input Bruce. I did think that and I am using PB11.5 so it should all be unicode. With regard the buffer for the pointers, is there anything I can look out for to ensure it is as expected?


brucearmstrong said...

I found the problem, and it's a bit weird. It's actually here:

FOR li_index = 1 TO ( ll_size / 4 )
ll_buffer[li_index] = 0

For network printers, ll_size is a bit larger (e.g., 8000 or so). So ll_size / 4 ends up being around 2000 or more.

The problem is that PowerBuilder seems to have problems with arrays > 1000 elements:

If an array has more than 1000 elements, the debugger no longer displays it correctly.
If an array has more than 1020 elements, PowerBuilder starts handling it improperly (the error message you're getting when you try to access elements of it).

For now, just change that code to something like:

long ll_count
long ll_max = 1020

ll_count = Min ( ll_size / 4, ll_max )

FOR li_index = 1 TO ll_count
ll_buffer[li_index] = 0

I'll open a case with Sybase on it.

brucearmstrong said...

Well, their response was to suggest using a blob in the call to GetPrinter rather than the array. It does work:

Change the second declaration of GetPrinter to:

Function long GetPrinter (ulong hPrinter, long Level,ref blob buffer, long pbSize, ref long pbSizeNeeded ) Library "winspool.drv" Alias For"GetPrinterW"

And then modify the of_getprinterstatus method as follows:

Long rc
ulong hPrinter
Long ll_size
printer_info pi
blob buffer

rc = OpenPrinter(as_printer, hPrinter, 0 )

rc = GetPrinter ( hPrinter, 2, 0, 0,ll_size )

buffer = blob(space(ll_size))
rc = GetPrinter ( hPrinter, 2, buffer, ll_size, ll_size )
ClosePrinter ( hPrinter )
IF rc 1 THEN
MessageBox ( "GetPrinter error", String ( rc ) )

pi.pservername = of_getstringfrompointer (long(blobmid(buffer, 1, 4)))
pi.pprintername = of_getstringfrompointer (long(blobmid(buffer,5, 4)))
pi.pShareName = of_getStringFrompointer(long(blobmid(buffer, 9, 4)))
pi.pPortName = of_getStringFrompointer(long(blobmid(buffer, 13, 4)))
pi.pDriverName = of_getStringFrompointer(long(blobmid(buffer, 17, 4)))
pi.pComment = of_getStringFrompointer(long(blobmid(buffer, 21, 4)))
pi.pLocation = of_getStringFrompointer(long(blobmid(buffer, 25, 4)))
pi.pDevMode = long(blobmid(buffer, 1, 4))
pi.pSepFile = of_getStringFrompointer(long(blobmid(buffer, 29, 4)))
pi.pPrintProcessor = of_getStringFrompointer(long(blobmid(buffer, 33, 4)))
pi.pDatatype = of_getStringFrompointer(long(blobmid(buffer, 37, 4)))
pi.pParameters = of_getStringFrompointer(long(blobmid(buffer, 41, 4)))
pi.pSecurityDescriptor = long(blobmid(buffer, 45, 4))
pi.Attributes = long(blobmid(buffer, 49, 4))
pi.Priority = long(blobmid(buffer, 53, 4))
pi.DefaultPriority = long(blobmid(buffer, 57, 4))
pi.StartTime = long(blobmid(buffer, 61, 4))
pi.UntilTime = long(blobmid(buffer, 65, 4))
pi.Status = long(blobmid(buffer, 69, 4))
pi.JobsCount = long(blobmid(buffer, 73, 4))
pi.AveragePPM = long(blobmid(buffer, 77, 4))

dw_status.Reset() = pi