Get a list of available printers

using the EnumPrinters procedure

EnumPrinter retrieves a list of available printers, that is: local installed printers and also printers made available by the network.
Actually EnumPrinters retrieves an array of PRINTER_INFO_X records (where X stands for level: will be explained later). Each PRINTER_INFO_X record contains information about a single printer, in this example we will only show the Printername (like "HP Laserjet 4L") and Portname (like "LPT1:").
Let's take a look at the source first and explain later.

{windows.i}
 
DEFINE VARIABLE pPrinterEnum  AS MEMPTR NO-UNDO.
DEFINE VARIABLE pcbNeeded     AS INTEGER NO-UNDO.
DEFINE VARIABLE pcReturned    AS INTEGER NO-UNDO.
DEFINE VARIABLE RetValue      AS INTEGER NO-UNDO.
 
DEFINE VARIABLE pPrinterInfo  AS MEMPTR NO-UNDO.
DEFINE VARIABLE StructSize    AS INTEGER INITIAL 84.
 
DEFINE VARIABLE i             AS INTEGER NO-UNDO.
DEFINE VARIABLE lpPrinterName AS MEMPTR  NO-UNDO.
DEFINE VARIABLE lpPortName    AS MEMPTR  NO-UNDO.
 
  /* The first call to EnumPrinters is only to 
     get the required memory size */
 
   SET-SIZE(pPrinterEnum) = 30.  /* A default bobo value */
 
   RUN EnumPrinters{&A} IN hpApi(2, /* = PRINTER_ENUM_LOCAL */
                                 "", 
                                 2, 
                                 GET-POINTER-VALUE(pPrinterEnum),
                                 GET-SIZE(pPrinterEnum), 
                                 OUTPUT pcbNeeded, 
                                 OUTPUT pcReturned, 
                                 OUTPUT RetValue).
 
   /* RetValue will now be FALSE (=error) because we did not
      supply enough memory. But at least we know now how much
      memory was required (pcbNeeded) and also how many printers
      were found (pcReturned) */
 
   /* no printers installed, then return (rare) */
   IF pcbNeeded=0 THEN DO:
      MESSAGE "No printers found".
      RUN DeAlloc.
      RETURN.
   END.
 
   /* Reset the size of pPrinterEnum to the correct size */
   SET-SIZE(pPrinterEnum) = 0.
   SET-SIZE(pPrinterEnum) = pcbNeeded.
 
   /* The second call actually fills the pPrinterEnum structure */
 
   RUN EnumPrinters{&A} IN hpApi(2,  /* = PRINTER_ENUM_LOCAL */
                                 "", 
                                 2,
                                 GET-POINTER-VALUE (pPrinterEnum),
                                 GET-SIZE(pPrinterEnum), 
                                 OUTPUT pcbNeeded,
                                 OUTPUT pcReturned, 
                                 OUTPUT RetValue).
 
   /* pPrinterEnum holds a couple of PRINTER_INFO_2 records.
      the number of records is pcReturned.
      the number of bytes copied to pPrinterEnum is pcbNeeded.
      size of one PRINTER_INFO_2 record is 84 bytes.
   */
 
   DO i=0 TO pcReturned - 1 :       
 
      SET-POINTER-VALUE(pPrinterInfo) = GET-POINTER-VALUE(pPrinterEnum) + (i * StructSize).
 
      /* the second LONG field in the PRINTER_INFO_2 structure is 
         a pointer to a string holding the printer name */
      SET-POINTER-VALUE(lpPrinterName) = GET-LONG(pPrinterInfo, 5).
 
      /* the 4th LONG field in the PRINTER_INFO_2 structure is 
         a pointer to a string holding the port name */
      SET-POINTER-VALUE(lpPortName)    = GET-LONG(pPrinterInfo,13).
 
      MESSAGE "printername=" GET-STRING(lpPrinterName,1) SKIP
              "portname="    GET-STRING(lpPortName,1)
               VIEW-AS ALERT-BOX.
 
   END.
 
   /* Clean Up  */
   RUN DeAlloc.
 
PROCEDURE DeAlloc:
   SET-SIZE(pPrinterEnum) = 0.
END.

Explanations

OS-version considerations

The first parameter (flags) in EnumPrinters is set to PRINTER_ENUM_LOCAL but Windows 95 will also enumerate network printers because these are also handled by the local print provider. Windows NT will strictly enumerate locally installed printers. When you want to enumerate network printers on NT you will have to use other parameters.
The third parameter (level) is set to 2 indicating we expect records of type PRINTER_INFO_2. Windows 95 supports only levels 1,2,5 and NT supports only levels 1,2,4.
structsize=84 is only true for Level=2.
Brad Long enhanced the previous example to support all other PRINTER_INFO types and make it independent of Windows version. The sourcefile is attached.

About the memory pointers

This piece of source is also interesting for those who struggle with memptr variables (aren't we all?).
pPrinterEnum is a pointer to an array of PRINTER_INFO_2 records. The pointer to the first record is the same as the pointer to the array, the pointer to the second record is (first) + structsize, and so on.
A PRINTER_INFO_2 record does not contain strings but pointers to strings, this indirection is solved by the lpPortName and lpPrinterName MEMPTR variables. Of course you should always use statements like SET-SIZE(memptrvar)=0 for each MEMPTR variable near the end of your procedure, but in this particular example you should not do this for pPrinterInfo because the value of this variable points to memory inside the scope of pPrinterEnum. So if you deallocate pPrinterEnum you automatically deallocate pPrinterInfo. But what about the two string-pointers lpPortName and lpPrinterName, you wonder? Well, these also point to locations within pPrinterEnum as we will see:
There are only 2 printers installed on my PC, so I would expect that pcbNeeded=(2 * 84)=168 but the function returned 948. Almost 700 byte too much: this is where the strings are stored that are pointed to by the several string-pointers in the PRINTER_INFO_2 records.
That is smart thinking by Microsoft: you now have local copies of the strings so the pointers don't have to point to protected memory, and it also solves the important question which process should be responsible for deallocating the string space. The strings are inside Your block of memory so You deallocate them. And it is all done automatically by simple deallocating pPrinterEnum.
Here's a small map to illustrate it all:

Attachments

wingetprinters.p.zip : example


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

number of copies

Is is possible to get the number of copies from the Device context the same way you get the printer name and port?

Thanks
Joel