0

I'm trying to list the jobs on a printer. I try to call enumjobs a first time to get the size of the buffer i need to pass as a parameter in a second call (as it is advised in microsoft documentation) But, when calling enumjobs, i keep getting a descriptor not valid error with the enumjobs api.

What the f... am i doing wrong ? !

procedure showLastError();
var
pErrorText:pchar;
lastError:integer;
begin
   lastError := GetLastError();
   pErrorText := nil;

   if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_ALLOCATE_BUFFER
                 ,nil,lastError,MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),@pErrorText,0,nil)<> 0) then
   begin
    showmessage(pErrorText);
   end;
end;

procedure TForm3.Button1Click(Sender: TObject);
var
handlePrinter:NativeUInt;
pJob:pointer;
sizePJob:integer;
pcbNeeded:cardinal;
pcReturned:cardinal;
cByteNeeded,cByteUsed:cardinal;

i:integer;
pJobInfo: PJobInfo1;
temp:integer;
ret:boolean;
pPrinterInfo:PPrinterInfo2W;
PPRINTER_INFO_1 : PRINTER_INFO_1;

begin
 handlePrinter := 0;
 if not OpenPrinter(nil,handlePrinter,nil) then
 begin
  showLastError();
  exit;
 end;
 if not GetPrinter(handlePrinter,3,nil,0,@cByteNeeded)then
 begin
  if (GetLastError() <> ERROR_INSUFFICIENT_BUFFER) then
  begin
   showLastError();
   exit;
  end;
 end;
 pPrinterInfo := allocMem(  cByteNeeded);
 if not GetPrinter(handlePrinter,3,pPrinterInfo,cByteNeeded,@cByteUsed)then
 begin
  if (GetLastError() <> ERROR_INSUFFICIENT_BUFFER) then
  begin
   showLastError();
   FreeMem(pPrinterInfo);
   exit;
  end;
 end;

 ret := EnumJobs(handlePrinter,0,pPrinterInfo.cJobs,2,nil,0,&pcbNeeded,&pcReturned);
 if (not ret)then
 begin
  if (GetLastError() <> ERROR_INSUFFICIENT_BUFFER) then
  begin
   showLastError();
   FreeMem(pPrinterInfo);
   exit;
  end;
 end;
 FreeMem(pPrinterInfo);
end;
Arsnow
  • 161
  • 1
  • 13
  • 2
    Well, if you want `PRINTER_INFO_2`, pass '2' for `Level`. – Sertac Akyuz Sep 16 '14 at 11:22
  • 2
    IOW, in the two GetPrinter calls, you're allocating a buffer for and passing a PRINTER_INFO_2 struct. But you are telling the api that it is a level 3 struct. That 'cJobs' after the call cannot be valid. You may even be overwriting memory because of the difference of the required size of the buffers by a level 2 vs a level 3 struct. – Sertac Akyuz Sep 16 '14 at 12:07
  • @SertacAkyuz, level 2 call is not supported to the handle obtained this way, while level 3 is. – Free Consulting Sep 17 '14 at 00:39
  • @Free - I think what's wrong is then the missing printer name, not a level 3 query. Oh, you covered that too.. – Sertac Akyuz Sep 17 '14 at 00:46

2 Answers2

2

What the f... am i doing wrong ? !

Basically, you are obtaining a wrong handle from Spooler subsystem, and hence the ERROR_INVALID_HANDLE error. OpenPrinter is bound to open a variety of handles for Spooler subsys objects. In particular, your statement:

if not OpenPrinter(nil,handlePrinter,nil) then

opens handle for local print server, which supports neither job queue enumeration nor detailed printer (level 2) information, but supports security (level 3) information, apparently. You have to specify an exact printer name to obtain a valid handle for printer, like:

if not OpenPrinter('Xerox ColorQube 9301 PS', handlePrinter, nil) then

Also, pay attention to Sertac Akyuz's comments. Yet another, check out Win32Check and SysErrorMessage utility functions, they are very handy when dealing with WinAPI.

Free Consulting
  • 4,300
  • 1
  • 29
  • 50
  • You're right, that was it. i had to do: var Device, Driver, Port: array[0..255] of Char; hDeviceMode: THandle; begin Printer.GetPrinter(Device, Driver, Port, hDeviceMode); if not OpenPrinter(@Device, Result, nil) then – Arsnow Sep 23 '14 at 10:55
  • @Arsnow, thats one of possibilities, yes. By the way, you could intermix VCL `TPrinter` object and Spooler API functions if you manage to read `TPrinter.FPrinterHandle`. – Free Consulting Sep 23 '14 at 13:16
1

Another alternative is use WMI. You can try with the class Win32_PrintJob (select * from Win32_PrintJob).

Test with code like this (created with "WMI Delphi code creator" from Rodrigo Ruz)

//-----------------------------------------------------------------------------------------------------
//     This code was generated by the Wmi Delphi Code Creator (WDCC) Version 1.8.5.0
//     http://code.google.com/p/wmi-delphi-code-creator/
//     Blog http://theroadtodelphi.wordpress.com/wmi-delphi-code-creator/
//     Author Rodrigo Ruz V. (RRUZ) Copyright (C) 2011-2014 
//----------------------------------------------------------------------------------------------------- 
//
//     LIABILITY DISCLAIMER
//     THIS GENERATED CODE IS DISTRIBUTED "AS IS". NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED.
//     YOU USE IT AT YOUR OWN RISK. THE AUTHOR NOT WILL BE LIABLE FOR DATA LOSS,
//     DAMAGES AND LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING OR MISUSING THIS CODE.
//
//----------------------------------------------------------------------------------------------------
program GetWMI_Info;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ActiveX,
  ComObj,
  Variants,
  Dialogs;



// La clase Win32_PrintJob representa un trabajo de impresión generado por una aplicación Win32. Las unidades de trabajo generadas por el comando Imprimir de una aplicación que se ejecuta en un sistema Win32 son descendientes (o miembros) de esta clase.
// Ejemplo: un documento de impresora creado por una aplicación de Office 97

procedure  GetWin32_PrintJobInfo;
const
  WbemUser            ='';
  WbemPassword        ='';
  WbemComputer        ='localhost';
  wbemFlagForwardOnly = $00000020;
var
  FSWbemLocator : OLEVariant;
  FWMIService   : OLEVariant;
  FWbemObjectSet: OLEVariant;
  FWbemObject   : OLEVariant;
  oEnum         : IEnumvariant;
  iValue        : LongWord;
  str:String;
begin;
  FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
  FWMIService   := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
  FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_PrintJob','WQL',wbemFlagForwardOnly);
  oEnum         := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
  Str := '';
  while oEnum.Next(1, FWbemObject, iValue) = 0 do
  begin
    Str := Str + sLineBreak + Format('Caption           %s',[String(FWbemObject.Caption)]);// String
    Str := Str + sLineBreak + Format('DataType          %s',[String(FWbemObject.DataType)]);// String
    Str := Str + sLineBreak + Format('Description       %s',[String(FWbemObject.Description)]);// String
    Str := Str + sLineBreak + Format('Document          %s',[String(FWbemObject.Document)]);// String
    Str := Str + sLineBreak + Format('DriverName        %s',[String(FWbemObject.DriverName)]);// String
    Str := Str + sLineBreak + Format('ElapsedTime       %s',[String(FWbemObject.ElapsedTime)]);// Datetime
    Str := Str + sLineBreak + Format('HostPrintQueue    %s',[String(FWbemObject.HostPrintQueue)]);// String
    Str := Str + sLineBreak + Format('JobId             %d',[Integer(FWbemObject.JobId)]);// Uint32
    Str := Str + sLineBreak + Format('JobStatus         %s',[String(FWbemObject.JobStatus)]);// String
    Str := Str + sLineBreak + Format('Name              %s',[String(FWbemObject.Name)]);// String
    Str := Str + sLineBreak + Format('Notify            %s',[String(FWbemObject.Notify)]);// String
    Str := Str + sLineBreak + Format('Owner             %s',[String(FWbemObject.Owner)]);// String
    Str := Str + sLineBreak + Format('PagesPrinted      %d',[Integer(FWbemObject.PagesPrinted)]);// Uint32
    Str := Str + sLineBreak + Format('PrintProcessor    %s',[String(FWbemObject.PrintProcessor)]);// String
    Str := Str + sLineBreak + Format('Priority          %d',[Integer(FWbemObject.Priority)]);// Uint32
    Str := Str + sLineBreak + Format('Size              %d',[Integer(FWbemObject.Size)]);// Uint32
    Str := Str + sLineBreak + Format('Status            %s',[String(FWbemObject.Status)]);// String
    Str := Str + sLineBreak + Format('StatusMask        %d',[Integer(FWbemObject.StatusMask)]);// Uint32
    Str := Str + sLineBreak + Format('TimeSubmitted     %s',[String(FWbemObject.TimeSubmitted)]);// Datetime
    Str := Str + sLineBreak + Format('TotalPages        %d',[Integer(FWbemObject.TotalPages)]);// Uint32
    Str := Str + sLineBreak + '--------------------------------------------------------';

    MessageDlg(Str, mtInformation, [mbOK], 0);

    FWbemObject:=Unassigned;
  end;
end;


begin
 try
    CoInitialize(nil);
    try
      GetWin32_PrintJobInfo;
    finally
      CoUninitialize;
    end;
 except
    on E:EOleException do
        Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode])); 
    on E:Exception do
        Writeln(E.Classname, ':', E.Message);
 end;
 Writeln('Press Enter to exit');
 Readln;      
end.

If you send a file to printer and execute the project (this is compiled with Delphi 6) you can obtanin a result like this:

enter image description here

Regards.

  • This is an alternative solution to the same problem (as explained in the text). The auto-generated code (which is also explained in the text ) is used to show the power of this tool and an example of use. If the user finds useful, can use it. – Germán Estévez -Neftalí- Sep 18 '14 at 06:24
  • OP didn't ask the way to enumerate spooler jobs by whichever means possible, he/she found a definite API for that already. – Free Consulting Sep 18 '14 at 08:52