SAMPLE: PrintMon.exe Demonstrates the Win32 Spooler API

ID: Q196805

The information in this article applies to:

SUMMARY

Windows and Windows NT control printouts with a module called the Spooler. The Spooler's printer queues can be monitored and managed by an application with the Spooler API.

This sample, Printmon.exe, demonstrates the use of the Win32 Spooler API to monitor print queues. The sample monitors the contents of a user-selected printer queue using two techniques. On Windows NT, the sample monitors a printer queue by polling or by change events generated by the printer queue. On Windows, it can only poll because the change notification interface is not implemented.

The source code for this sample contains two functions that implement polling with the GetPrinter() and EnumJobs() functions or event driven updates implemented with the FindFirstPrinterChangeNotification() and FindNextPrinterChangeNotification() functions.

To install the sample, run the Printmon.exe executable. By default, this executable extracts a Microsoft Visual C++ 5.0 project into a subdirectory named PrintMon. A pre-build executable, PrintJob.exe, is also extracted, which eliminates the need to build the sample if just the sample executable is desired.

MORE INFORMATION

The following file is available for download from the Microsoft Software Library:

 ~ PrintMon.exe (size: 81242 bytes) 

Release Date: Dec-04-1998

For more information about downloading files from the Microsoft Software Library, please see the following article in the Microsoft Knowledge Base:

   ARTICLE-ID: Q119591
   TITLE : How to Obtain Microsoft Support Files from Online Services

Operating the PrintJob sample application

The sample application uses a single document interface to monitor a single printer queue. To watch multiple printer queues, launch multiple instances of the sample application.

The user can select between methods of monitoring the printer queue via the "Options" menu item. By default, the sample uses the polling method of monitoring on Windows and Windows NT. To change the method of monitoring to Printer Change Notifications, select the "Use Printer Notifications" item in the "Options" menu. When Printer Change Notifications are in use by the sample, this menu item is checked. The method of monitoring can be changed while viewing a printer queue.

When the sample application is run on Windows 95 or Windows 98, it can not use Printer Change Notifications to monitor the printer queue because the Notifications mechanisms are not implemented. Consequently, the "Use Printer Notifications" menu item is unavailable (grayed out) and the sample will only operate using the Polling method.

To select a printer queue to monitor, use the "Printer" menu and choose the "Select" option. A dialog with an edit control will appear. Type the name of the printer you want monitor in this dialog box. Valid printer names are either:

When a printer is successfully selected, the sample shows the name of the printer and its current status in the caption bar of the application. It also lists all of the print jobs currently in that printer queue in a Window's ListView control in the client area. For each print job, the document name, owner name, current status, when it was submitted, and the progress of the print job is shown.

The sample application shows progress by bytes and by page count. Note that the Operating System may not know how many pages are in the print job so it is possible for the pages portion of the progress indication to show 0 (zero) pages while still showing the number of bytes printed.

Should the user want to immediately update the contents of the sample application's printer queue view, they can select the "Refresh" item from the "Printer" menu. This item causes either of the two monitoring methods to completely refresh the view of the printer queue.

Calling Spooler API functions

Care must be taken when calling the Spooler API functions. The API works with printers which, on Windows NT, are owned and secure objects. Printers are also highly configurable and installable/removable components of the operating system, which means they are vulnerable to being set up incorrectly. Further, there can be three different types of printers installed, which have subtle differences in how they are treated by the Spooler API.

Generally, the functions of the Spooler API take a handle to a printer as the parameter identifying the target printer object. The handle is obtained from a call to OpenPrinter(). Note however that OpenPrinter() can return a handle to Printer or a Print Server. Some of the Spooler API functions can accept a handle to either a printer or a print server, while most take only handles to printers.

When working with the Spooler API functions it is important to realize that a handle is not necessarily invalid if the function failed. This can be the case because the function may have been passed the wrong type of handle. OpenPrinter()'s success does not guarantee that the other Spooler API functions will succeed. An application can determine if a handle is associated with a printer by calling one of the Spooler API functions, which only accepts printer handles such as GetPrinter().

On Windows NT, security on printer objects is an important and necessary component. Therefore, calls to OpenPrinter() may fail even when the printer name parameter is correct. When this occurs, the problem is a conflict between the requested access rights in the OpenPrinter() call and the rights granted by the administrator on that printer object. However, simple information gathering such as what this sample does requires only minimal access rights.

One should also note that even if OpenPrinter() succeeds in granting Administrative rights to the calling process, some of the Spooler API functions may still fail with an access rights error code. When this occurs, it generally means that in addition to being an administrator, the user of the calling process must also be the owner of the printer object. This would be the case for example when calling SetPrinter() to change the SECURITY_ATTRIBUTES of a printer object. One must be an owner to change the access rights of a printer object.

Spooler API functions frequently use buffers of variable size to pass information between the calling process and the Spooler. The calling process must allocate this buffer. For many functions in the Spooler API the application must call the function at least twice: once to ask the Spooler API how big a buffer to allocate, and a second time to have the buffer filled. For a more complete explanation of how to call functions in the Spooler API, please see the following article in the Knowledge Base:

   ARTICLE-ID: Q158828
   TITLE     : How To Call Win32 Spooler Enumeration APIs Properly

One should also note that the status information shown by this sample application for both the printer and the print jobs in the printer queue come directly from the Spooler API. At various times, the status information may not be intuitively correct. For an explanation of what one would expect to see and what is actually shown, please see the following article in the Knowledge Base:

   ARTICLE-ID: Q160129
   TITLE     : HOWTO: Get the Status of a Printer and a Print Job

Monitoring Print Queues by Polling

Polling is a monitoring technique of asking for information periodically. It is characterized in computer science as a loop of source code that gathers information on each pass of the loop. Typically, what is being monitored takes much longer to change than the time it takes a computer to execute the loop. Because it would be a waste of processing time for the loop to monitor continuously, implementations of polling typically introduce a delay period between executions of the loop.

This sample implements a polling algorithm to monitor the printer queue in the PollingUpdate() function located in the Threads.C module. This function uses a time delayed loop to wake up, gather the printer queue information, update the display, and return to an efficient sleep state until the next period.

The Win32 WaitForMultipleObjects() function is used to implement the waiting period. The timeout period for the WaitForMultipleObjects() call is the refresh period of the polling algorithm. When the function times out, the loop executes. The function waits upon mutex objects that signal to manually refresh or to terminate the monitoring of the printer queue.

The polling interval can be changed at the g_nPollIntervalms variable at the top of the Threads.C module.

Monitoring Print Queues by Events

Event-driven monitoring is a technique of gathering information when changes occur, rather than on some arbitrary periodic basis. It is superior to polling in that it is process efficient and it does not miss information that passes between the polling period of the polling technique.

Event-driven monitoring of printers is possible on Windows NT by using Change Notifications. Printer Change Notifications on Windows NT operate with process synchronization objects. These objects are mutexs that become signaled when a desired change occurs on the target printer object.

Like the polling technique, the event driven monitoring is located in the NotificationUpdate() function in the Threads.C module. This function implements a loop that is controlled by the signaled state of the Printer Change Notification object. As each change or set of changes occurs on the printer, the object becomes signaled triggering the loop to retrieve details of the changes and update the view. The loop is also controlled by mutexs for refreshing the view and terminating the monitoring.

Two things should be considered when Printer Change Notifications are used.

First, multiple changes can be combined into a single change notification. This is necessary because many changes can occur in the time period between notifications. Therefore, care should be taken when writing code to parse the buffer returned by FindNextPrinterChangeNotification() because it may contain many unrelated changes.

Second, it is possible for the changes that occur to overflow the change notification mechanism. When this happens, a special call to FindNextPrinterChangeNotification() must be made to refresh the list of changes.

The sample source code properly deals with both of these possibilities.

Networks, Devices and Latency

Relative to the speed of central processors, networks are slow and error prone. Because remote or network printers on print servers are fundamental to today's computing environment, code written to work with printer queues must account for the speed and reliability differences.

Although the physical printers connected to the computer are not allowed to impact the performance of the Spooler, one should consider the differences in how physical devices are treated by the Operating System. The Knowledge Base article referenced earlier (Q160129), has a good explanation of the differences between the Spooler's representation of a device's state and its actual state.

Whenever the Spooler API functions are called the calling code should account for the long period of time it may take for a function to return and that failure of the functions may be periodic due to availability of network resources and physical devices.

Responsive User Interfaces: Threading

Given the techniques for gathering printer queue information and the possible long latencies associated with Spooler functions, single threaded processes using these functions would suffer from being unresponsive for possibly long periods of time.

This occurs because the Window procedure responsible for processing user input via the Window's menu items, keyboard, or mouse may be blocked while processing a polling loop, waiting for a notification, or simply stuck in a Spooler function waiting for a response.

To solve the issue of an unresponsive user interface, threads should be employed. By making the application multi-threaded, the developer can disassociate the processing of user input from the time intensive tasks of calling the Spooler API functions.

The processes main thread should restrict its tasks to processing messages in the Window procedure of the application. All calls to Spooler API functions should be called in threads created by the main thread of the process. By segregating the high latency work to worker threads, the main thread is guaranteed not to be blocked when needed to respond to user interaction in the Window procedure.

The sample accomplishes this by creating a new worker thread each time it is asked to begin monitoring a printer queue. This includes changes between polling and Printer Change Notifications.

Each time a new monitoring thread is created by the user, it signals the previous thread to terminate and waits upon it. When the previous thread finally terminates, it continues by entering the monitoring loop.

When the application shuts down it must wait for the worker thread to terminate. The sample accomplishes this by signaling to the worker thread that it must shut down and that it is to post a WM_CLOSE message to the applications Window. This is one example of a way to close a multi-threaded application down without blocking the message processing thread and causing a deadlock.

REFERENCES

For more information, please see the following articles in the Microsoft Knowledge Base:

   ARTICLE-ID: Q160129
   TITLE     : HOWTO: Get the Status of a Printer and a Print Job

   ARTICLE-ID: Q158828
   TITLE     : How To Call Win32 Spooler Enumeration APIs Properly

Additional query words: kbDSupport
Keywords          : kbfile kbsample kbGDI kbNTOS kbSpooler 
Version           : WINDOWS:95,98; winnt:3.51,4.0
Platform          : WINDOWS winnt

Last Reviewed: February 13, 1999