![]() |
Desktop Development »
Printing »
General
Intermediate
License: The Code Project Open License (CPOL)
Monitoring a Printer Queue from VB.NETBy Duncan Edwards JonesHow to monitor a printer queue from Visual Basic .NET |
VB.NET 1.0, .NET 1.1, Win2K, WinXPVS.NET2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Some of the API calls used in this example are only supported on Windows NT, 2000, XP and .NET server. Therefore this technique does not apply to Windows 95, Windows 98 or Windows ME.
Although VB.NET printer handling has improved immeasurably over that offered by Visual Basic 6, there is still a need to turn to the Windows API in order to monitor a print queue.
All of the API calls that access the printer or spooler need a printer handle. This is obtained by passing the unique printer device name to the OpenPrinter API call and must be released by the ClosePrinter API call when it is no longer needed.
<DllImport("winspool.drv", EntryPoint:="OpenPrinterA", _
SetLastError:=True, CharSet:=CharSet.Ansi, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function OpenPrinter(ByVal pPrinterName As String, _
ByRef phPrinter As Int32, _
ByVal pDefault As Int32) As Boolean
End Function
<DllImport("winspool.drv", EntryPoint:="ClosePrinter", _
SetLastError:=True, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function ClosePrinter _
(ByVal hPrinter As Int32) As Boolean
End Function
To minimise the impact of a printer watch on system performance, we can specify precisely which printer events we are interested in. This is done by passing a parameter to FindFirstPrinterChangeNotification using one or more of the following values:
Public Enum Printer_Change_Notification_General_Flags
PRINTER_CHANGE_FORM = &H70000
PRINTER_CHANGE_PORT = &H700000
PRINTER_CHANGE_JOB = &HFF00
PRINTER_CHANGE_PRINTER = &HFF
PRINTER_CHANGE_PRINT_PROCESSOR = &H7000000
PRINTER_CHANGE_PRINTER_DRIVER = &H70000000
End Enum
Public Enum Printer_Change_Notification_Form_Flags
PRINTER_CHANGE_ADD_FORM = &H10000
PRINTER_CHANGE_SET_FORM = &H20000
PRINTER_CHANGE_DELETE_FORM = &H40000
End Enum
Public Enum Printer_Change_Notification_Port_Flags
PRINTER_CHANGE_ADD_PORT = &H100000
PRINTER_CHANGE_CONFIGURE_PORT = &H200000
PRINTER_CHANGE_DELETE_PORT = &H400000
End Enum
Public Enum Printer_Change_Notification_Job_Flags
PRINTER_CHANGE_ADD_JOB = &H100
PRINTER_CHANGE_SET_JOB = &H200
PRINTER_CHANGE_DELETE_JOB = &H400
PRINTER_CHANGE_WRITE_JOB = &H800
End Enum
Public Enum Printer_Change_Notification_Printer_Flags
PRINTER_CHANGE_ADD_PRINTER = &H1
PRINTER_CHANGE_SET_PRINTER = &H2
PRINTER_CHANGE_DELETE_PRINTER = &H4
PRINTER_CHANGE_FAILED_CONNECTION_PRINTER = &H8
End Enum
Public Enum Printer_Change_Notification_Processor_Flags
PRINTER_CHANGE_ADD_PRINT_PROCESSOR = &H1000000
PRINTER_CHANGE_DELETE_PRINT_PROCESSOR = &H4000000
End Enum
Public Enum Printer_Change_Notification_Driver_Flags
PRINTER_CHANGE_ADD_PRINTER_DRIVER = &H10000000
PRINTER_CHANGE_SET_PRINTER_DRIVER = &H20000000
PRINTER_CHANGE_DELETE_PRINTER_DRIVER = &H40000000
End Enum
When an event occurs -- for example, if a job is added to the print queue -- you will probably want to get information about the job that caused that event. Again, in order to minimise the impact on the system, you specify exactly which fields you want information from. For a print job event, the possible fields are:
Public Enum Job_Notify_Field_Indexes
JOB_NOTIFY_FIELD_PRINTER_NAME = &H0
JOB_NOTIFY_FIELD_MACHINE_NAME = &H1
JOB_NOTIFY_FIELD_PORT_NAME = &H2
JOB_NOTIFY_FIELD_USER_NAME = &H3
JOB_NOTIFY_FIELD_NOTIFY_NAME = &H4
JOB_NOTIFY_FIELD_DATATYPE = &H5
JOB_NOTIFY_FIELD_PRINT_PROCESSOR = &H6
JOB_NOTIFY_FIELD_PARAMETERS = &H7
JOB_NOTIFY_FIELD_DRIVER_NAME = &H8
JOB_NOTIFY_FIELD_DEVMODE = &H9
JOB_NOTIFY_FIELD_STATUS = &HA
JOB_NOTIFY_FIELD_STATUS_STRING = &HB
JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR = &HC
JOB_NOTIFY_FIELD_DOCUMENT = &HD
JOB_NOTIFY_FIELD_PRIORITY = &HE
JOB_NOTIFY_FIELD_POSITION = &HF
JOB_NOTIFY_FIELD_SUBMITTED = &H10
JOB_NOTIFY_FIELD_START_TIME = &H11
JOB_NOTIFY_FIELD_UNTIL_TIME = &H12
JOB_NOTIFY_FIELD_TIME = &H13
JOB_NOTIFY_FIELD_TOTAL_PAGES = &H14
JOB_NOTIFY_FIELD_PAGES_PRINTED = &H15
JOB_NOTIFY_FIELD_TOTAL_BYTES = &H16
JOB_NOTIFY_FIELD_BYTES_PRINTED = &H17
End Enum
To inform the print spooler that you want information on these fields, you create a PRINTER_NOTIFY_OPTIONS structure that is passed to FindFirstPrinterChangeNotification and which holds a pointer to an array of PRINTER_NOTIFY_OPTIONS_TYPE, one for each of the above fields that you require. These structures are documented on MSDN.
In VB.NET, it is easy to translate these structures into classes that can be passed to the API, by being marshaled as if they were structures:
<StructLayout(LayoutKind.Sequential)> _
Public Class PrinterNotifyOptionsType
Public wType As Int16
Public wReserved0 As Int16
Public dwReserved1 As Int32
Public dwReserved2 As Int32
Public FieldCount As Int32
Public pFields As IntPtr
Private Sub SetupFields()
'\\ Free up the global memory
If pFields.ToInt32 <> 0 Then
Marshal.FreeHGlobal(pFields)
End If
If wType = Printer_Notification_Types.JOB_NOTIFY_TYPE Then
FieldCount = JOB_FIELDS_COUNT
pFields = Marshal.AllocHGlobal((JOB_FIELDS_COUNT * 2) - 1)
'\\ Put the field indexes in the unmanaged array
Marshal.WriteInt16(pFields, 0,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_PRINTER_NAME))
Marshal.WriteInt16(pFields, 2,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_MACHINE_NAME))
Marshal.WriteInt16(pFields, 4,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_PORT_NAME))
Marshal.WriteInt16(pFields, 6,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_USER_NAME))
Marshal.WriteInt16(pFields, 8,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_NOTIFY_NAME))
Marshal.WriteInt16(pFields, 10,
CShort(Job_Notify_Field_Indexes.JOB_NOTIFY_FIELD_DATATYPE))
Marshal.WriteInt16(pFields, 12,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_PRINT_PROCESSOR))
Marshal.WriteInt16(pFields, 14,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_PARAMETERS))
Marshal.WriteInt16(pFields, 16,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_DRIVER_NAME))
Marshal.WriteInt16(pFields, 18,
CShort(Job_Notify_Field_Indexes.JOB_NOTIFY_FIELD_DEVMODE))
Marshal.WriteInt16(pFields, 20,
CShort(Job_Notify_Field_Indexes.JOB_NOTIFY_FIELD_STATUS))
Marshal.WriteInt16(pFields, 22,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_STATUS_STRING))
Marshal.WriteInt16(pFields, 24,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR))
Marshal.WriteInt16(pFields, 26,
CShort(Job_Notify_Field_Indexes.JOB_NOTIFY_FIELD_DOCUMENT))
Marshal.WriteInt16(pFields, 28,
CShort(Job_Notify_Field_Indexes.JOB_NOTIFY_FIELD_PRIORITY))
Marshal.WriteInt16(pFields, 30,
CShort(Job_Notify_Field_Indexes.JOB_NOTIFY_FIELD_POSITION))
Marshal.WriteInt16(pFields, 32,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_SUBMITTED))
Marshal.WriteInt16(pFields, 34,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_START_TIME))
Marshal.WriteInt16(pFields, 36,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_UNTIL_TIME))
Marshal.WriteInt16(pFields, 38,
CShort(Job_Notify_Field_Indexes.JOB_NOTIFY_FIELD_TIME))
Marshal.WriteInt16(pFields, 40,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_TOTAL_PAGES))
Marshal.WriteInt16(pFields, 42,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_PAGES_PRINTED))
Marshal.WriteInt16(pFields, 44,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_TOTAL_BYTES))
Marshal.WriteInt16(pFields, 46,
CShort(Job_Notify_Field_Indexes.
JOB_NOTIFY_FIELD_BYTES_PRINTED))
End If
End Sub
Public Sub New(ByVal value As Printer_Notification_Types)
wType = value
Call SetupFields()
End Sub
End Class
To start the printer watch, you need to pass the printer handle to FindFirstPrinterChangeNotification:
<DllImport("winspool.drv",
EntryPoint:="FindFirstPrinterChangeNotification", _
SetLastError:=True, CharSet:=CharSet.Ansi, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function FindFirstPrinterChangeNotification _
(<InAttribute()> ByVal hPrinter As Int32, _
<InAttribute()> ByVal fwFlags As Int32, _
<InAttribute()> ByVal fwOptions As Int32, _
<InAttribute(), MarshalAs(UnmanagedType.LPStruct)>
ByVal pPrinterNotifyOptions As PrinterNotifyOptions) As Int32
End Function
In the Visual Basic 6 implementation of this, a great deal of complexity was added by the fact that it is a single-threaded system. Thus, when the program was waiting for the printer notification, it was effectively locked up. In Visual Basic .NET, this is no longer necessary because it supports asynchronous events and threading.
The FindFirstPrinterChangeNotification API call returns a Windows synchronization wait handle. This can be used by the VB.NET Common Language Runtime to trigger a particular subroutine whenever that synchronisation object is signalled. This is done with the Threading.RegisteredWaitHandle object:
Private Shared _mhPrinterChangeNotification As RegisteredWaitHandle
Dim wh As New ManualResetEvent(False)
wh.Handle = mhWait
_mhPrinterChangeNotification =
ThreadPool.RegisterWaitForSingleObject(wh,
New WaitOrTimerCallback(AddressOf PrinterNotifyWaitCallback),
wh, -1, True)
Here, PrinterNotifyWaitCallback is a public subroutine that has the correct signature for WaitOrTimerCallback:
Public Sub PrinterNotifyWaitCallback( _
ByVal state As Object, _
ByVal timedOut As Boolean)
When the wait object is triggered, you have to call FindNextPrinterChangeNotification to find out what event triggered it and get the details.
<DllImport("winspool.drv",
EntryPoint:="FindNextPrinterChangeNotification", _
SetLastError:=True, CharSet:=CharSet.Ansi, _
ExactSpelling:=True, _
CallingConvention:=CallingConvention.StdCall)> _
Public Shared Function FindNextPrinterChangeNotification _
(<InAttribute()> ByVal hChangeObject As Int32, _
<OutAttribute()> ByRef pdwChange As IntPtr, _
<InAttribute(), MarshalAs(UnmanagedType.LPStruct)>
ByVal pPrinterNotifyOptions As PrinterNotifyOptions, _
<OutAttribute()> ByRef lppPrinterNotifyInfo As IntPtr ) As Boolean
End Function
This returns a 32-bit number in pdwChange that indicates what event has occurred. For example, this will contain PRINTER_CHANGE_ADD_JOB when a job is added to the print queue. Additionally, it returns a pointer to data allocated by the spooler in lppPrinterNotifyInfo, which contains a PRINTER_NOTIFY_INFO structure, followed by an array of PRINTER_NOTIFY_INFO_DATA structures. Again, in VB.NET these can be represented by classes:
<StructLayout(LayoutKind.Sequential)> _
Public Class PRINTER_NOTIFY_INFO
Public Version As Int32
Public Flags As Int32
Public Count As Int32
End Class
You can populate these classes from a pointer, using Marshal.PtrToStructure:
Private msInfo As New PRINTER_NOTIFY_INFO()
'\\ Read the data of this printer notification event
Marshal.PtrToStructure(lpAddress, msInfo)
TonerLow and properties Colour, Collate and PrintQuality to the PrinterInformation classSafeWaitHandle class
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 2 Jun 2009 Editor: Deeksha Shenoy |
Copyright 2002 by Duncan Edwards Jones Everything else Copyright © CodeProject, 1999-2009 Web15 | Advertise on the Code Project |