Sources
Delphi Russian Knowledge Base
DRKB - база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Мониторинг принтера

28.09.2005
Михаил Продан, https://www.cpp.com.ua

Если организация оказывает вычислительные услуги, то в один прекрасный момент перед ней неизбежно встанет проблема тарификации, напрямую связанная с автоматическим учетом ресурсов. Эта статья рассказывает о том, как автоматизировать учет использования принтера.

В перечень услуг интернет-кафе давно уже входит распечатка текстовых и других документов. Однако организация этого дела все еще остается на этапе минимальной автоматизации. Вот если бы удалось написать программку, которая отслеживала бы, кто, когда и сколько напечатал, да интегрировать ее в систему контроля компьютерного клуба, то это не только избавило бы администраторов от лишнего диалога с клиентом, но и придало клубу более современный вид.

Теоретические основы мониторинга

В теории мониторинг принтера в Windows 98 не является чем-то неимоверно сложным - те же драйверы не просто осуществляют этот самый мониторинг, но и предоставляют возможности по управлению работой принтера. Мы же пока будем довольствоваться лишь отслеживанием посылаемых на печать задачами. Для этого в арсенале Windows предусмотрено два метода.

Первый - использование метода отслеживания сообщения WM_SPOOLERSTATUS, которое система посылает всякий раз при добавлении в очередь нового задания или же при удалении оного.

Второй способ - использование функций FindFirstPrinterChangeNotification, FindNextPrinterChangeNotification и FindClosePrinterChangeNotification. В этой заметке мы рассмотрим первый из них.

Как и большинство сообщений Windows, WM_SPOOLERSTATUS возвращает в структуре TMessage некоторую полезную информацию, которую вы можете иcпользовать в своих нуждах. Но, к сожалению, никакой действительно важной и нужной информации (помимо количества оставшихся в очереди заданий) эта структура не несет. К счастью, в Windows есть дополнительные методы для определения необходимой нам информации. Так, среди прочих в модуле WinSpool присутствует функция EnumJobs, возвращающая количество и характеристики заданий печати, в которых содержится требуемая информация - от названия документа, машины и имени пользователя, отправившего этот документ на печать, до времени, когда это было сделано и количества страниц, посланных на печать.

Эта функция выглядит следующим образом:

function EnumJobs(hPrinter: THandle; FirstJob, NoJobs, Level: DWORD;
         pJob: Pointer; cbBuf: DWORD;
         var pcbNeeded, pcReturned: DWORD): BOOL; stdcall;

где:

clip0258

clip0259

Для нормальной работы этой функции, прежде всего, необходимо раздобыть идентификатор принтера, который мы должны поместить в параметр hPrinter. Для этого предназначена функция OpenPrinter, открывающая соответствующий принтер и выдающая необходимый идентификатор:

function OpenPrinter(pPrinterName: PChar; var phPrinter: THandle;
         pDefault: PPrinterDefaults): BOOL; stdcall;

где:

Если в системе присутствует лишь один принтер (подключенный к компьютеру админа), то параметр ppPrinterName может быть строго зафиксирован в самом коде приложения. Если же принтеров в сети несколько (подключенный к серверу, к компьютеру администратора, к компьютеру кассы и т.д.), то лучше всего предоставить пользователю возможность самостоятельно выбирать, какой из принтеров следует инспектировать. Для этого можно воспользоваться функцией перечисления доступных принтеров:

function EnumPrinters(Flags: DWORD; Name: PChar; Level: DWORD;
         pPrinterEnum: Pointer; cbBuf: DWORD; var pcbNeeded,
         pcReturned: DWORD): BOOL; stdcall;

где:

clip0260

clip0261

Практическая реализация

Для практической реализации мы, как всегда, воспользуемся Delphi и создадим сначала свежее приложение (New р Application). Далее для демонстрации работы мы перенесем на форму 2 компонента: TreeView и ListBox

Далее занесем в OnCreate код для перечисления доступных принтеров:

procedure TForm1.FormCreate(Sender: TObject);
var PI:array[0..1023] of PRINTER_INFO_1;
  Needed,Returned:Cardinal;i:integer;
begin
  PrintersRoot:=TreeView1.Items.AddFirst(nil,'Printers');
  if not EnumPrinters(PRINTER_ENUM_LOCAL,nil,1,@PI,SizeOf(PI),Needed,Returned) then
    ListBox1.Items.Add(SysErrorMessage(GetLastError));
  For i:=0 to Returned-1 do
    TreeView1.Items.AddChild(PrintersRoot,PI[i].pName);
end;

Здесь мы сначала создаем в TreeView родительский узел, куда будем записывать все найденные принтеры. Далее запускаем поиск локальных принтеров посредством передачи в EnumPrinters флага PRINTER_ENUM_LOCAL. После этого при успешном завершении функции мы переносим названия всех принтеров в TreeView или же выводим сообщение об ошибке.

Теперь у нас есть список всех доступных принтеров - и мы со спокойной душей можем предложить пользователю выбрать и открыть один из них. Для этого в реакцию на событие OnDblClick компонента TreeView1 следует записать следующий код:

procedure TForm1.TreeView1Click(Sender: TObject);
begin
  if not OpenPrinter(PChar(TreeView1.Selected.Text),PH,nil) then
    ListBox1.Items.Add(SysErrorMessage(GetLastError));
end;

После двойного щелчка на названии принтера мы открываем его и записываем полученный идентификатор в переменную PH, которая объявлена в секции private нашей формы. А для пущей правильности кода в OnDestroy нашей формы напишем:

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ClosePrinter(PH);
end;

Далее нам предстоит реализовать слежение за сообщением WM_SPOOLERSTATUS - для этого я рекомендовал бы выбрать способ, предоставляемый Delphi посредством служебного слова message:

TForm1 = class(TForm)
  TreeView1: TTreeView;
  ListBox1: TListBox;
  procedure FormCreate(Sender: TObject);
  procedure TreeView1Click(Sender: TObject);
  procedure FormDestroy(Sender: TObject);
  private
  PrintersRoot:TTreeNode;
  PH:Cardinal;
  procedure WMPrinterStatus(var Msg:TMessage); message WM_SPOOLERSTATUS;
  { Private declarations }
  public
  { Public declarations }
end;

Здесь метод WMPrinterStatus будет вызываться только при поступлении сообщения WM_SPOOLERSTATUS. В коде реализации этого метода мы с вами напишем вот что:

procedure TForm1.WMPrinterStatus(var Msg:TMessage);
var i:integer;
  Job2:Array[0..1023] of JOB_INFO_2;Needed,Returned:Cardinal;
begin
  Msg.Result:=0;
  ListBox1.Items.Add('Jobs Left:'+IntToStr(Msg.WParamLo));
  if not EnumJobs(PH,0,1024,2,@Job2,SizeOf(Job2),Needed,Returned) then
    ShowMessage(SysErrorMessage(GetLastError));
  For i:=0 to Returned-1 do
    With Job2[i] do
      ListBox1.Items.Add(Format('%s %s %s',[pPrinterName,pDocument,pUserName]))
end;

Все. Проект готов, осталось только скомпилить его.

После запуска приложения на экране появляется уже знакомое нам окно, в компоненте TreeView которого присутствует список доступных локальных принтеров (рисунок ниже). Открываем принтер (двойной щелчок на названии) и запускаем любой текстовый редактор. Открываем документ (для пробы можно воспользоваться и графическим редактором). А теперь: Файл > Печать - и мы видим...

Послесловие

Итак, теперь мы в курсе, как отслеживать состояние очереди на печать. И не просто отслеживать, а еще и автоматически обрабатывать эту информацию в корыстных целях :).

Впрочем, этот метод не единственный в своем роде. К примеру, помимо реакции на событие WM_SPOOLERSTATUS, можно реализовать обработку очереди посредством функций FindFirstPrinterChangeNotification, FindNextPrinterChangeNotification, FindClosePrinterChangeNotification, которые позволяют отслеживать не только добавление и удаление заданий в (из) очереди, но и состояние очереди в процессе печати, а также предоставляют ряд дополнительных возможностей. Кроме того, эти методы позволяют настроить реакцию программы под более конкретные задачи (Printer, PrinterDriver, Form, Job...), что, конечно, позитивно сказывается на работоспособности системы в целом, особенно при больших нагрузках на нее.

К отрицательным же сторонам вопроса можно отнести тот факт, что при обработке FindFirstPrinterChangeNotification, FindNextPrinterChangeNotification, FindClosePrinterChangeNotification необходимо реализовывать отдельный поток для отслеживания этих действий, что само по себе непростая задача для человека, никогда не работавшего с потоками вплотную.

Previous page:
Метрики принтера
Top:
DRKB
Next page:
Настройки принтера