Getting a Handle to the Printer You Want to Monitor
Before you can start listening for print‑queue events you must obtain a printer handle. The Windows Print Spooler API exposes a simple function, OpenPrinter, that takes a device name and returns a HANDLE you use in all subsequent calls. On the surface the call looks like this:
The printerName argument is the exact name that appears in the Devices and Printers control panel – for example "HP LaserJet 4200". If the name does not exist the function returns False and you can retrieve the error code with GetLastError. A good practice is to wrap this logic in a SafeHandle derived class so the operating system automatically frees the handle when the object is disposed.
Once you are finished monitoring, always call ClosePrinter to release the resource. Neglecting to close the handle can leave the spooler with stale references that may block other applications from accessing the printer. The pair of calls is tiny, but it is the cornerstone of every printer‑related API routine. In VB.NET you typically declare the API functions once in a module:
With the handle in hand, you are ready to describe the exact events you care about. That decision determines how much CPU time and memory the spooler will devote to your application, so be selective. The next step is to inform the spooler of the event types you wish to receive.
Specifying Which Printer Events to Watch
The Print Spooler can notify you about a handful of changes – jobs being added, deleted, paused, or completed, printer status changes, and even hardware failures. The FindFirstPrinterChangeNotification API lets you subscribe to any combination of these. Internally it accepts a flag set built from constants such as PRINTER_CHANGE_ADD_JOB and PRINTER_CHANGE_PAUSE_JOB. In VB.NET you can express the flags with a UInt32 variable:
But the API goes further. It can return only the pieces of data you specify, such as job title, status, or number of pages. This is achieved with the PRINTER_NOTIFY_OPTIONS structure, which contains an array of PRINTER_NOTIFY_OPTIONS_TYPE entries. Each entry references a field you want back. A typical set might request the job ID, the name of the user who submitted the job, and the number of pages. The structure is passed to FindFirstPrinterChangeNotification as a pointer. In VB.NET you can model the two structures with classes marked StructLayout so the runtime marshals them automatically:
Public Flags As UInt32
Public Count As UInt32
Public Options() As PrinterNotifyOptionsType
End Class
Public NotifyType As UInt32
End Class
Once you build an instance of PrinterNotifyOptions and fill the Options array with the types you care about, you can hand it to the API:
With the mask and the options in place, the next stage is to call To avoid blocking the UI thread, you register the printer‑change event with the thread pool. The call looks like this: When the spooler signals the event, the thread‑pool thread calls the The callback must be quick – the thread‑pool thread is part of the global pool, and holding it for long can starve other threads. A common pattern is to enqueue a work item or post a message to the UI thread so that heavy processing happens elsewhere. Inside The For example, if you requested the job ID and user name, the loop might look like this: After you have extracted the information you need, you can update a UI element, log to a file, or trigger any other business logic. Because the spooler may return multiple data blocks in a single event, you should iterate until you collect all required fields. Once you finish with the handle returned by With these pieces in place you have a robust, low‑overhead listener that reacts instantly to changes in the print queue. It works on Windows NT, 2000, XP and all .NET server releases, but not on legacy Windows 9x platforms. If you run into any quirks or need help tailoring the code to a specific printer model, feel free to reach out to the development team.FindFirstPrinterChangeNotification. The function will return a handle that represents a waiting object. Under the hood the spooler creates an event that is signaled when any of the selected changes occur. The key point is that this event is a standard Windows synchronization object, so the .NET runtime can hook it into the asynchronous waiting mechanism provided by Threading.RegisteredWaitHandle
Starting the Watch and Waiting for Events
PrinterNotifyCallback routine. The callback receives a state object and a Boolean indicating whether the event fired or a timeout occurred. Inside the callback you must perform the following actions:
pdwChange value returned by FindNextPrinterChangeNotification to determine the exact type of event (e.g. PRINTER_CHANGE_ADD_JOB).PRINTER_NOTIFY_INFO data structure. The spooler allocates this buffer, and you free it with LocalFree after you are done.Handling a Notification and Extracting Job Information
PrinterNotifyCallback you first call FindNextPrinterChangeNotification with the printer handle and the same mask you used earlier. The function returns a 32‑bit flag set in pdwChange and a pointer to a PRINTER_NOTIFY_INFO buffer. The buffer contains a header followed by an array of PRINTER_NOTIFY_INFO_DATA structures, each holding one piece of data you requested.pszDataType field tells you which data type the current PRINTER_NOTIFY_INFO_DATA contains – job ID, job title, user name, page count, etc. You can map these numeric codes to meaningful names in a dictionary for readability. The data itself is stored in the data union. For numeric types you can read the ul field; for strings you read the psz pointer and call Marshal.PtrToStringUni to convert to a .NET string.FindFirstPrinterChangeNotification, call FindClosePrinterChangeNotification to let the spooler clean up its internal event object. Finally, when the application exits, release the printer handle with ClosePrinter and free any unmanaged memory you allocated for the options structure.





No comments yet. Be the first to comment!