//+------------------------------------------------------------------------- // // Microsoft Windows // // Copyright (C) Microsoft Corporation, 1995 - 1996 // // File: overview.hxx // //-------------------------------------------------------------------------- /*++ Overview of printui structure ============================= Client usage: ------------- The print user interface is a separate library that can be linked into either shell32.dll or printui.exe for independent and quick testing. To use this library, the user simply loads the dll. In DllEntryPoint routine, the library is intializes itself. To create a print queue window, the client calls: VOID vQueueCreate( LPCTSTR pszPrinter, INT nCmdShow ) Note that this all does not fail: it occurs asynchronously. If base initialization fails, then a message box is put up immediately with an error. If the printer fails to open for whatever reason (such as unknown printer), then the window is left open and shows an error message in both the title and status bar. Note: this is the only public interface to creating a queue. Programming goals: ------------------ There goals in order of priority are: 1. Correctness (no bugs, robust) 2. Maintainability (clean architecture, readable, extensible) 3. Portable (should run on win9x) 4. Responsive (UI should never hang) 5. Small working set 6. Fast Naming conventions: ------------------- As a general rule, hungarian notation is used. Also, the nouns a placed before the verbs (PrinterCreate rather than CreatePrinter). Class names are prefixed with the following characters: Type Inheritance ---- ----------- V - Virtual 0 or 1 (Types|Virtual), 0 or more Mix-ins. T - Type 0 or 1 (Types|Virtual), 0 or more Mix-ins. M - Mix-in 0 or more Mix-ins. Multiple inheritance is allowed, but this structure prevents cycles from appearing. Macros: ------- ALWAYS_VALID - indicates the object is always valid, and creates an inline bValid( VOID ) function that returns TRUE. SIGNATURE( '1234' ) - Places a signature in an object. The bytes are reversed so that a dc displays the name correctly. Also creates an inline function bSigCheck() that ensures the object matches the signature. VAR( Type, name ); - Creates the field variable (prefixed by '_') and 'get' function (Type name();) that retrieves the variable. DLINK_BASE( Type, name, linkname ); - Creates the head base pointer for double-linked list. DLINK( Type, name ); - Creates a dlink entry and inline functions to manipulate the link (functions prefixed with "name_.") REF_LOCK( Type, name ); - Create a reference lock that acquires and releases a VRef object. The reference count is appropriately incremented and decremented. ( Debug macros ) SINGLETHREAD( dwThreadId ); - The first time this is called, initializes dwThreadId to the current thread. Subsequent calls assert that the current thread is the same as dwThreadId (this first one). Library intialization: ---------------------- When the dll is loaded, the window classes are registered and the libraries are initialized. Queue Creation: --------------- When the user calls vQueueCreate the following occurs: Internal structure notes: ------------------------- There are three main classes: TQueue: The highest level queue object This maintains the listview and user interface elements. It maintains a reference to a TPrinter. TPrinter: Single interface to printer objects This handles retrieving printer data and sending asynchronous printer commands. VData: Abstracts client notifications and information retrieval. There are two types of notifications: notification that something changed, and actual data. This class presents a uniform interface to TPrinter, and holds all job information. Given that virtually everything executes asynchronously, (including commands), destruction of queues must be handled carefully: TPrintLib: This is referenced counted, and once all print queues have been destroyed, this object deletes itself. Each queue holds a ref count. When all queues are destroyed, the ref count reaches zero and vRefZeroed() is called. At this point we tell TThreadM to start shutting down by calling TThreadM::vDelete. TQueue: Since this is tied to the user interface, it only lives while the UI window is open, or there it is processing a notification (there is a refcount). It notifies the its TPrinter that the UI is going away, so that the TPrinter does not notify the queue. TPrinter: This receives it's notification to delete via vDelete(). It disassociates itself from the TQueue then queues itself with commands to close then delete. After all other commands are processed, it deletes the TData object. VData: This is simply a data repository, so it does not require any special shutdown code. The following classes are used by the print folder to retrieve info about printers on a server. TFolder: Analagous to TQueue for print queues. It will have either (1 TConnectionNotify, 1 TDSServer, 0+ TDSConnection) or (1 TDSServer). The former case represents the local print folder where we must watch local printers + connections + check if any connections are added or deleted. The latter represents a remote printer folder (we don't need to watch connections). VDataSource: A single source of printers. TDSConnection: derived from VDataSource. Represents a single printer connection (always enumerates and returns one printer.) TDSServer: derived from VDataSource. Represents a server, which may have multiple printers. TConnectionNotify: Watches the registry to see if printer connections are added or removed. Printer States: The printer state is represented with a by a DWORD--the top three bits. Each action can modify the bits to transition to a new state. EXEC_AWAKE Print queue was restored (from iconized state), so notifications should start up again. EXEC_SLEEP Print queue minimized; shut down notifications. ?? Even in the minimized state, it should show whether the printer is paused. But if notifications are shut down, how will it update this? We could reduce notification level just to status, which would cut down on traffic but still give us Paused informations. EXEC_ERROR An error occurred such that the TPrinter should not be re-opened until the user explicitly hits refresh. This occurs when the printer name is invalid, or access is denied. EXEC_REOPEN Open printer with maximal access. From states: ALL EXEC_DELAY GetLastError() must be valid! Sleep for a while when there's an error. It will decrement _uFailCount in case it wants to retry a second time before giving up. This will also create an event in case the user tries to refresh while it's sleeping. From states: ALL EXEC_CLOSE Closes printer object. From states: ALL EXEC_REQUESTEXT Request that the printer shut down. From states: ALL EXEC_NOTIFYSTART Begin the notification process--calls VData.svNotifyStart. From states: NOTIFYEND, EXEC_REOPEN (printer must be valid) EXEC_NOTIFYEND Ends the notification--calls VData.svNotifyEnd. When the user minimizes the printer, we will close the notification so that we won't eat network bandwidth when no one's looking. From states: NOTIFYSTART, REFRESH, COMMAND EXEC_REFRESH Refresh the object. From states: COMMAND, NOTIFYSTART EXEC_REFRESH_PRINTER EXEC_REFRESH_JOBS Extra state bits used for TDataRefresh only. EXEC_COMMAND Execute a command. From states: Columns vs. Indicies -------------------- A column is the UI ListView column, while an index is an index into the TData information. Column: Index: DOCUMENT DOCUMENT STATUS_STRING STATUS_STRING USER_NAME USER_NAME STATUS <- specific to Index only. Sequence of notifications ------------------------- The data notifications are abstracted into the VData object. Full refresh In the downlevel case, no information is sent back with a notification, so the entire queue must be refreshed. Partial refresh For uplevel, data is sent back, and single jobs are changed. The following describes how a notification is processed. There are two main issues that result this complex system: first, the data must be processed in the UI thread so that we don't deal with dangling data. Second, we want to keep the TPrinter, TQueue, TData and TData* classes as separate as possible. 1. Notification system signals event, indicating something changed and we must be updated. 2. Notify thread (there is a single thread for all printers (unless there are > 63 queues, in which case multiple threads are used)) picks up the changed event and MNotifyWork::vProcessNotifyWork is called. This must process the request very quickly, since other printers use this thread too. a. For TDataRefresh, this routine adds a refresh job that will be executed in a separate thread. b. For TDataNotify, FindNextPrinterChangeNotification is called to retrieve the update. Since FFPCN does not hit the network, it executes quickly. 3. If there is data available from the notification, the thread calls Printer.vRequestBlockProcess which cals the Queue object with the request. The data block is totally encapsulated in the hBlock. 4. The Queue object posts a message to the queue, allows the message to be processed synchronously in the UI thread. All data is accessed from the UI thread. The message indicates whether state (which jobs are selected, which has focus) should be saved. 5. When the message is received by the UI thread, it is sent to TQueue. a. If the state must be preserved the JobIds are saved. b. The TData* is sent the Block which must be processed. 1. If a job must be modified, it updates the data then notifies the Queue that the job must be repainted. 2. If a new job has come in, it is added to the data structure, then the Queue is notified that a job must be inserted into the list view. 3. If a job is deleted, the Queue is notified before the item is deleted, since the list view may reference the job data. c. If necessary, the state is restored (generally done if the list view was reconstructed). 6. The data block is freed. Singleton class ------------------------- A Sington class is a class which can have only one instance during the life of the program, however, it may have multiple referenced to this class. TPrinterDriverSetup is singleton class. This class is used to allow acccess to the DriverSetup information. The DriverSetup information is expensive to create and does not change during the life of the program. The TPrinterDriverSetup::bInstance function is a static function which is used by users of this class to obtain a reference to it. bInstance takes one argument the address where to return a reference to the singlton class and returns a boolean if a valid instance is returned. TPrinterDriverSetup::vDelete() is used to release an instance of the singleton class. There should be a matching number of bInstance() and vDelete() calls, to insure proper cleanup of the singleton class. --*/