MORE INFORMATION
The following file is available for download from the Microsoft Download Center:
Release Date: Dec-04-1998
For additional information about how to download Microsoft Support files, click the following article number to view the article in the Microsoft Knowledge Base:
119591 How to Obtain Microsoft Support Files from Online Services
Microsoft scanned this file for viruses. Microsoft used the most current virus-detection software that was available on the date that the file was posted. The file is stored on security-enhanced servers that help to prevent any unauthorized changes to the file.
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:
- The friendly name of a printer that appears in the printers folder on
the local computer.
- A UNC share name of the form "\\Server\ShareName" for a network printer.
- An LPTX port name when on Windows 95.
- The share name for the printer when on Windows NT.
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:
158828 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:
160129 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.