Printing

(This is only a very brief overview of printing)

Printing is basically the same as drawing on screen: both are handled through the GDI (Graphic Device Interface) who translates your high level drawing commands to low level instructions according to the appropriate device drivers. This driver can be a printer driver, a display driver, fax driver or whatever.

Before you can start to draw anything you must first define on which device you will draw, ie you must get a handle to a device context (HDC). When you want to draw on screen you use the function hdc=GetDC(hwnd) and when you want to draw on a printer you use the function hdc=CreateDC(0,printername,0,lpInitData).
Nearly every GDI function has hdc as its first parameter so it is a very important handle. When you are finished drawing you must use ReleaseDC(hdc) because Windows can only create a limited amount of context handles simultaniously.

In **pseudo-code** your program will have a structure like this:

hdc=CreateDC(...)
 
StartDoc(hdc).
  StartPage(hdc).
    TextOut(hdc,10,10,'this is page 1',14).
  EndPage(hdc).
  StartPage(hdc).
    TextOut(hdc,10,10,'this is page 2',14).
  EndPage(hdc).
EndDoc(hdc).
 
DeleteDC(hdc).

Everything between StartDoc and EndDoc is one spooler job, this is important for a multipage document because it will prevent other jobs from printing pages in between.
As I said, this is just pseudo-code. Click here for a working code example.

Dimensions

You will need to know the size of the paper, in pixels. This and other important data can be obtained from GetDeviceCaps and DeviceCapabilities.

Drawing functions

GDI has a huge amount of drawing functions. The most commonly used are:
* MoveTo(hdc,x,y) : moves the dc cursor to coordinate x,y
* LineTo(hdc,x,y) : draws a line from current position to x,y
* TextOut(hdc,x,y,text,lenght(text))
There are also several functions to manipulate text placement, see SetTextAlign. To write text in columns it is convenient to use the ExtTextOut function because it also has a rectangle parameter: the text will be clipped to the rectangle when needed and you can also easily ensure that text is centered or right-aligned to the rectangle.

Objects

You will need objects like pens, fonts, brushes, bitmaps. For example when you want to draw a red dotted line you will have to create a red dotted pen first using hRedpen=CreatePen(PS_DOT,1,RGB(255,0,0)).
But when you want to create a black solid line, you don't have to create one because it is already 'in stock'. GDI has several stock objects.
After you created one or more objects you must select one of them to make it the current object using SelectObject(hRedpen). Every created object must be manually destroyed too or else you will run out of GDI resources, using DeleteObject(hRedpen). An object can not be destroyed when it is currently selected so you may have to select a stock object first.


Dump raw data to printer

By Nenad Orlovic, norlovic@zg.tel.hr

Suppose you have a file that contains printer control codes, and now you want to send this file to a printer. The file may have been created by a report engine when the port was set to "FILE:".
The following procedure copies the file to a printer.
This example will send file to printer even if in printer properties port is FILE:
This is because variable OutFileName = "", but it can be changed to a filename or perhaps even to a UNC-name for a printer.
See the notes near the bottom of this page.

DEFINE INPUT PARAMETER PrinterName AS CHARACTER NO-UNDO. /* As set in Printer properties */
DEFINE INPUT PARAMETER FILENAME AS CHARACTER NO-UNDO.
 
 
DEFINE VARIABLE X AS INTEGER NO-UNDO.
DEFINE VARIABLE hPrinter AS INTEGER NO-UNDO.
DEFINE VARIABLE hFile AS INTEGER NO-UNDO.
DEFINE VARIABLE pBuf AS MEMPTR NO-UNDO.
DEFINE VARIABLE FileSize AS INTEGER NO-UNDO.
DEFINE VARIABLE iSize AS INTEGER NO-UNDO.
DEFINE VARIABLE xSize AS INTEGER NO-UNDO.
DEFINE VARIABLE pFileName AS MEMPTR NO-UNDO.
DEFINE VARIABLE OutFileName AS CHARACTER  NO-UNDO.
DEFINE VARIABLE pOutFileName AS MEMPTR NO-UNDO.
DEFINE VARIABLE DataType AS CHARACTER  NO-UNDO.
DEFINE VARIABLE pDataType AS MEMPTR NO-UNDO.
DEFINE VARIABLE pDocInfo AS MEMPTR NO-UNDO.
 
   RUN OpenPrinterA (PrinterName,OUTPUT hPrinter,0, OUTPUT X).
   IF X = 0
   THEN MESSAGE "Error opening printer: " PrinterName VIEW-AS ALERT-BOX.
   ELSE DO:
     RUN CreateFileA (FILENAME , -2147483648,0,0,3,128,0,OUTPUT hFile). /* -2147483648 = $80000000 */
     IF hFile = -1
     THEN MESSAGE "Error opening file: " FILENAME VIEW-AS ALERT-BOX.
     ELSE DO:
       RUN GetFileSize (hFile,0,OUTPUT FileSize).
       IF FileSize = -1
       THEN MESSAGE "Wrong file size" VIEW-AS ALERT-BOX.
       ELSE DO:
         SET-SIZE(pBuf) = FileSize.
 
         RUN ReadFile(hFile,pBuf,FileSize,OUTPUT iSize,0, OUTPUT X).
         IF X = 0
         THEN MESSAGE "Error reading file: " FILENAME VIEW-AS ALERT-BOX.
         ELSE DO:
           IF iSize = 0
           THEN MESSAGE "Attempt to read beyond end of file:" FILENAME VIEW-AS ALERT-BOX.
           ELSE DO:
             OutFileName = "".
             DataType = "RAW".
             SET-SIZE(pDocInfo) = 12.
             SET-SIZE(pFileName) = LENGTH(FILENAME) + 1.
             PUT-STRING(pFileName,1) = FILENAME.
             SET-SIZE(pOutFileName) = LENGTH(OutFileName) + 1.
             PUT-STRING(pOutFileName,1) = OutFileName.
             SET-SIZE(pDataType) = LENGTH(DataType) + 1.
             PUT-STRING(pDataType,1) = DataType.
             PUT-LONG(pDocInfo,1) = GET-POINTER-VALUE(pFileName).
             PUT-LONG(pDocInfo,5) = GET-POINTER-VALUE(pOutFileName).
             PUT-LONG(pDocInfo,9) = GET-POINTER-VALUE(pDataType).
 
             RUN StartDocPrinterA (hPrinter,1,pDocInfo,OUTPUT X).
             IF X = 0 THEN DO:
                 RUN GetLastError(OUTPUT X).
                 MESSAGE "Error : " X VIEW-AS ALERT-BOX.
             END.
 
             RUN WritePrinter(hPrinter,pBuf,iSize,OUTPUT xSize,OUTPUT X).
             IF X = 0 THEN DO:
                 RUN GetLastError(OUTPUT X).
                 MESSAGE "Error writing to printer: " PrinterName iSize xsize X VIEW-AS ALERT-BOX.
             END.
 
             RUN EndDocPrinter(hPrinter,OUTPUT X).
           END.
         END.
       END.
       RUN CloseHandle(hFile,OUTPUT X).
       IF X = 0 THEN MESSAGE "Error closing file: " FILENAME.
     END.
 
 
     RUN ClosePrinter(hPrinter,OUTPUT X).
     IF X = 0
     THEN MESSAGE "Error closing printer: " PrinterName VIEW-AS ALERT-BOX.
   END.
 
   SET-SIZE(pBuf) = 0.
   SET-SIZE(pDocInfo) = 0.
   SET-SIZE(pFileName) = 0.
   SET-SIZE(pOutFileName) = 0.
   SET-SIZE(pDataType) = 0.
 
 
/******************/
/* DLL Procedures */
/******************/
PROCEDURE GetLastError EXTERNAL "kernel32.dll" :
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE StartDocPrinterA EXTERNAL "winspool.drv" :
    DEFINE INPUT PARAMETER hPrinter AS LONG.
    DEFINE INPUT PARAMETER Level AS LONG.
    DEFINE INPUT PARAMETER pDocInfo AS MEMPTR.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE EndDocPrinter EXTERNAL "winspool.drv" :
    DEFINE INPUT PARAMETER hPrinter AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE CreateFileA EXTERNAL "kernel32.dll" :
    DEFINE INPUT PARAMETER lpFileName AS CHARACTER.
    DEFINE INPUT PARAMETER dwDesiredAccess AS LONG.
    DEFINE INPUT PARAMETER dwShareMode AS LONG.
    DEFINE INPUT PARAMETER lpSecurityAttributes AS LONG.
    DEFINE INPUT PARAMETER dwCreationDistribution AS LONG.
    DEFINE INPUT PARAMETER dwFlagsAndAttributes AS LONG.
    DEFINE INPUT PARAMETER hTemplateFile AS LONG.
    DEFINE RETURN PARAMETER hFile AS LONG.
END PROCEDURE.
 
PROCEDURE ReadFile EXTERNAL "kernel32.dll" :
    DEFINE INPUT PARAMETER hFile AS LONG.
    DEFINE INPUT PARAMETER lpBuffer AS MEMPTR.
    DEFINE INPUT PARAMETER nNumberOfBytesToRead AS LONG.
    DEFINE OUTPUT PARAMETER  lpNumberOfBytesRead AS LONG.
    DEFINE INPUT PARAMETER lpOverlapped AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE WritePrinter EXTERNAL "winspool.drv" :
    DEFINE INPUT PARAMETER hPrinter AS LONG.
    DEFINE INPUT PARAMETER  pBuf AS MEMPTR.
    DEFINE INPUT PARAMETER cbBuf AS LONG.
    DEFINE OUTPUT PARAMETER lpdwWritten AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE OpenPrinterA EXTERNAL "winspool.drv" :
    DEFINE INPUT PARAMETER pPrinterName AS CHARACTER.
    DEFINE OUTPUT PARAMETER phPrinter AS LONG.
    DEFINE INPUT PARAMETER pDefault AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE ClosePrinter EXTERNAL "winspool.drv" :
    DEFINE INPUT PARAMETER hPrinter AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE GetFileSize EXTERNAL "kernel32.dll" :
    DEFINE INPUT PARAMETER hFile AS LONG.
    DEFINE INPUT PARAMETER lpFileSizeHigh AS LONG.
    DEFINE RETURN PARAMETER FileSize AS LONG.
END PROCEDURE.
 
PROCEDURE CloseHandle EXTERNAL "kernel32.dll" :
    DEFINE INPUT PARAMETER hObject AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.

notes

Note from Valther Bernardi:

The procedure fails if the printer is a local printer and not a network printer.
For local printers the variable OutFileName need to be initialized to the value of the device associated to printer (i.e. LPT1:). For retrieving the port name associated to the printer you can use EnumPrinters

Note from Brian Maher:

In Progress 4GL you could simply output to the printer and then do a PUT CONTROL NULL which switches us into "windows passthru printing" mode.


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


Get a List of Paper bins for a Printer

The attached code returns an array of Bin IDs and Bin Names for the printer/port specified in pcPrinterName and pcPrinterPort.


How to get the printer name

16 Bit

In 16-bit windows you can find the printer name in WIN.INI, section "windows" key "device".
For example:

[windows]
device=HP LaserJet 4L,HPPCL5MS,LPT1:

To read 'WIN.INI' you use the procedure GetProfileString.
To read any other ini-file you use the procedure GetPrivateProfileString.
Example:

{windows.i}
DEFINE VARIABLE printername AS CHARACTER    NO-UNDO.
DEFINE VARIABLE cchRet      AS INTEGER NO-UNDO.
 
printername = FILL(" ",100).  /* = allocate memory, don't forget! */
RUN GetProfileString{&A} IN hpApi ("windows",
                                   "device",
                                   "-unknown-,",
                                   OUTPUT printername,
                                   LENGTH(printername),
                                   OUTPUT cchRet).
 
/* split name from driver and port. Note that the 
   default "-unknown-," must have at leat one comma or 
   the ENTRY function may raise an error  */
printername = ENTRY(1,printername).
 
/* use the result */
IF printername="-unknown-" THEN
   MESSAGE "something is wrong with your WIN.INI" 
           VIEW-AS ALERT-BOX.
ELSE
   MESSAGE "your default printer is: " printername
           VIEW-AS ALERT-BOX.

The above example was used for an in-depth explanation of parameter types on page using a MEMPTR parameter for a CHAR. It also explains why the FILL statement is important.
Of course you can also use the 4GL procedure GET-KEY-VALUE to read this information, as shown in topic "printing: using StartDoc"

32 Bit

Here is the easiest way:

  Printername = SESSION:PRINTER-NAME.

Although INI files are obsolete in 32-bits Windows versions, you should still use GetProfileString to find the default printer. The function will not actually read the INI file but will read from Registry.
If you know the key where the printer name is stored in Registry, you could use the following code example but you should not do that. It is always a bad idea to use hardcoded paths because Microsoft does not commit to support those paths forever. For example: Windows 95 and NT 4.0 don't use the same key. Function GetProfileString however is guaranteed to return the right information on all 32-bit Windows versions.
but it's still interesting to have a registry example, so here it goes:

DEFINE VARIABLE Printername AS CHARACTER.
 
LOAD "System" BASE-KEY "HKEY_CURRENT_CONFIG".
USE "System".
GET-KEY-VALUE SECTION "CurrentControlSet\Control\Print\Printers"
    KEY "default" VALUE Printername.
UNLOAD "System".
 
/* code example by Joseph Richardson */

How to print an ASCII file

The example program printfile.p demonstrates how to print an ascii-file using GDI functions.
The program prints a header and a footer on every page using a proportional font. The page header contains the filename (right aligned). The page footer contains the date (left aligned), time (centered) and page number (right aligned).
The contents of the ascii-file is printed in a non-proportional font (Courier New).
The procedure printfile.p has currently only one input parameter: the filename of the ascii-file to print.
The definition section contains some variables who could also have been used as input parameters:

/* ================================================================= 
   file    : printfile.p
   purpose : print a text file
   usage   : RUN printfile.p ("printfile.p").
   tested  : only on Windows 98 with local printer (HP Laserjet 4)
   ================================================================= */
DEFINE INPUT PARAMETER p-Filename   AS CHARACTER NO-UNDO.
 
/* these variables could also be useful as input parameters: */
DEFINE VARIABLE p-Landscape   AS LOGICAL NO-UNDO INITIAL NO.   /* Orientation is Landscape or Portrait */
DEFINE VARIABLE p-Silent      AS LOGICAL NO-UNDO INITIAL YES.  /* show no message boxes (on error)     */
DEFINE VARIABLE p-Devicename  AS CHARACTER    NO-UNDO.         /* if "" then default printer           */
DEFINE VARIABLE p-docname     AS CHARACTER    NO-UNDO.         /* if "" then p-Filename                */
DEFINE VARIABLE p-Leftmargin  AS DECIMAL NO-UNDO INITIAL 1.0.  /* left margin (inches) from paper edge */
DEFINE VARIABLE p-fontsize    AS INTEGER NO-UNDO INITIAL 9.    /* pointsize for bodyfont               */

API-procedures used in printfile.zip: CreateDC, DeleteDC, DocumentProperties, GetProfileString, StartDoc, EndDoc, StartPage, EndPage, TextOut, SetTextAlign, GetDeviceCaps, MoveToEx, LineTo, SelectObject, DeleteObject, GetTextMetrics, CreateFont, MulDiv.

Attachments

printfile.zip : example


Print an HTML document

The attached example shows how to use the WebBrowser control (in shdocvw.dll) to print an HTML page. The example does __not__ show how to change headers/footers at run-time.

Attachments

printurl.zip : a dialog with WebBrowser control


Print Preview

by Nickolay Borshukov

Download attached file: preview.zip
The example programs in preview.zip are an extended version of printfile.zip (see topic how to print an ASCII file).
The most noticeable extensions are:
* long lines are wrapped to the next line.
* the print layout can be viewed in a Preview window. This window is made with P4GL and Windows API-calls

API-procedures used in preview.zip (in addition to the ones in printfile.p) : CreateCompatibleDC, CreateCompatibleBitmap, SetBkMode, SetTextColor, PatBlt, StretchBlt, GetTextExtentPoint32

Attachments

preview.zip : example by Nickolay Borshukov


printing: using StartDoc

Notice how this source uses the GET-KEY-VALUE function to retrieve information about the default printer. There are a couple of other methods to get the same information, see topic GetDefaultPrinter.
Besides using the default printer you can also use a different printer; those other printers can be picked using the EnumPrinters function.
code example by Roland the Pijper, converted to 32-bit by Jurjen

{windows.i}
 
  DEFINE VARIABLE windir        AS CHARACTER.
  DEFINE VARIABLE pdocname      AS MEMPTR.
  DEFINE VARIABLE poutbuf       AS MEMPTR.
  DEFINE VARIABLE lpdocinfo     AS MEMPTR.
  DEFINE VARIABLE pfilename     AS MEMPTR.
 
  DEFINE VARIABLE outsize       AS INTEGER   NO-UNDO.
  DEFINE VARIABLE printerhDC    AS INTEGER   NO-UNDO.     
  DEFINE VARIABLE apistatus     AS INTEGER   NO-UNDO.
 
  DEFINE VARIABLE docname       AS CHARACTER NO-UNDO.
  DEFINE VARIABLE devicebuf     AS CHARACTER NO-UNDO.
  DEFINE VARIABLE driverbuf     AS CHARACTER NO-UNDO.
  DEFINE VARIABLE initbuf       AS CHARACTER NO-UNDO.
  DEFINE VARIABLE outbuf        AS CHARACTER NO-UNDO.  
  DEFINE VARIABLE windowsdir    AS CHARACTER NO-UNDO.
  DEFINE VARIABLE winini        AS CHARACTER NO-UNDO.
  DEFINE VARIABLE printerdev    AS CHARACTER NO-UNDO.
  DEFINE VARIABLE FILENAME      AS CHARACTER NO-UNDO.
  DEFINE VARIABLE tekst         AS CHARACTER NO-UNDO.
 
  /* Get printer info from WIN.INI */
  windir = FILL("x", 260).
  RUN GetWindowsDirectoryA IN hpApi(OUTPUT windir,
                                    LENGTH(windir), 
                                    OUTPUT outsize).
  ASSIGN windowsdir = SUBSTRING(windir,1,outsize)
         winini     = windowsdir + "\WIN.INI".   
  LOAD winini.       
  USE winini.
  GET-KEY-VALUE SECTION "windows" KEY "device" VALUE printerdev. 
  UNLOAD winini.
  IF printerdev = "" THEN DO:
      RUN Err ("Could not locate printer device!"). 
      RETURN.
  END. 
  ASSIGN devicebuf = ENTRY(1,printerdev).
         driverbuf = ENTRY(2,printerdev).
         outbuf    = ENTRY(3,printerdev).  
 
  docname = "test".
  /* Setup pointers to the strings needed in the lpdocinfo STRUCT */
  SET-SIZE(pdocname)     = LENGTH(docname) + 1.
  PUT-STRING(pdocname,1) = docname.  
  SET-SIZE(poutbuf)      = LENGTH(outbuf) + 1.
  PUT-STRING(poutbuf,1)  = outbuf.
 
  /* Load up the lpdocinfo STRUCT */
  SET-SIZE(lpdocinfo)    =   4    /* INTEGER cbSize          */
                           + 4    /* pointer lpszDocName */
                           + 4.   /* pointer lpszOutput  */
  PUT-LONG(lpdocinfo,1) =  12.    /* size of the STRUCT  */ 
  PUT-LONG(lpdocinfo,5)  = GET-POINTER-VALUE(pdocname). /* pointer to CHARACTER */ 
  PUT-LONG(lpdocinfo,9)  = GET-POINTER-VALUE(poutbuf).  /* pointer to CHARACTER */
 
  tekst = "bla bla".
 
  /* Print it! */
  RUN adecomm/_setcurs.p ("WAIT").
  RUN CreateDCA (driverbuf, devicebuf, outbuf, 0, OUTPUT printerhDC). 
  RUN StartDocA IN hpApi(INPUT printerhDC, 
                         INPUT GET-POINTER-VALUE(lpdocinfo), 
                         OUTPUT apistatus). /* is printjob id */
  RUN StartPage IN hpApi(INPUT printerhDC, OUTPUT apistatus).
 
  RUN TextOutA IN hpApi(printerhDC, 800, 450, tekst, LENGTH(tekst), OUTPUT apistatus).
 
  IF apistatus=0  /* 0=FALSE */ THEN 
     MESSAGE "There was an error during TextOut "
             VIEW-AS ALERT-BOX ERROR.
 
  RUN EndPage IN hpApi(printerhDC, OUTPUT apistatus).
  RUN EndDoc IN hpApi(printerhDC, OUTPUT apistatus).
  RUN DeleteDC(printerhDC, OUTPUT apistatus).
 
  /* Clean Up */
  SET-SIZE(lpdocinfo) = 0.
  SET-SIZE(pdocname)  = 0.
  SET-SIZE(poutbuf)   = 0.
  RUN adecomm/_setcurs.p ("").

Printscreen

by Jurjen, improved by Ian Keene

/* ==============================================================
   file    : printscreen.p
   by      : Jurjen Dijkstra (Modified by Ian Keene Oct 2003)
   dd      : 05/16/1999
   purpose : draw a window to the default printer.
   usage   : RUN printscreen.p ({&WINDOW-NAME}:HWND, YES).
   parms   : hWindow (integer)
                HWND of the 'widget' to be drawn
             GetParent (logical)
               -Use YES if HWND is a Progress window widget
                so printscreen.p will draw the border/titlebar.
               -Use NO for all other widgets.
   ============================================================== */
DEFINE INPUT PARAMETER hWindow   AS INTEGER NO-UNDO.  /* HWND                        */
DEFINE INPUT PARAMETER GetParent AS LOGICAL NO-UNDO.  /* hWindow=GetParent(hWindow)? */

RUN MakeDocument.

/* API definitions used in this proc have been moved to printscreen.i */
{printscreen.i}

PROCEDURE MakeDocument :
  DEFINE VARIABLE hDC         AS INTEGER   NO-UNDO.
  DEFINE VARIABLE lpDocInfo   AS MEMPTR    NO-UNDO.
  DEFINE VARIABLE lpDocName   AS MEMPTR    NO-UNDO.
  DEFINE VARIABLE ReturnValue AS INTEGER   NO-UNDO.
  DEFINE VARIABLE prt-buf     AS CHARACTER NO-UNDO.
  DEFINE VARIABLE prt-out     AS CHARACTER NO-UNDO.
  DEFINE VARIABLE err-check   AS INTEGER   NO-UNDO.
  DEFINE VARIABLE drbuf       AS CHARACTER NO-UNDO.
  DEFINE VARIABLE out-buf     AS CHARACTER NO-UNDO.
  DEFINE VARIABLE prt-hwnd    AS INTEGER   NO-UNDO.

  ASSIGN prt-out = FILL(" ",127). /* ALLOCATE MEMORY */
  RUN GetProfileStringA (INPUT "WINDOWS",
                         INPUT "DEVICE",
                         INPUT "-unknown-,",
                         OUTPUT prt-out,
                         INPUT LENGTH(prt-out),
                         OUTPUT err-check). 

  IF prt-out = "-unknown-," THEN DO:
     MESSAGE "Aborted, Cannot Determine Default Printer." SKIP 
             VIEW-AS ALERT-BOX INFORMATION.
     RETURN. 
  END. 

  ASSIGN prt-buf = ENTRY(1,prt-out)
         drbuf   = ENTRY(2,prt-out)
         out-buf = ENTRY(3,prt-out).

  /* OPEN THE PRINTER */
  RUN OpenPrinterA (INPUT prt-buf,
                    OUTPUT prt-hwnd,
                    INPUT 0).
  RUN CreateDCA ( "WINSPOOL",
                  prt-buf,
                  0,
                  0,
                  OUTPUT hDC) .
  IF hDC = 0 THEN DO:
      MESSAGE "Error during CreateDCA in PrintScreen procedure" 
              view-as ALERT-BOX INFORMATION.
      RETURN.
  END.

  SET-SIZE  (lpDocName)   = LENGTH("PrintScreen") + 1.
  PUT-STRING(lpDocName,1) = "PrintScreen".
  SET-SIZE  (lpDocInfo)   = 12.
  PUT-LONG  (lpDocInfo,1) = 12.
  PUT-LONG  (lpDocInfo,5) = GET-POINTER-VALUE(lpDocName).
  PUT-LONG  (lpDocInfo,9) = 0.

  RUN StartDocA ( hDC,
                  GET-POINTER-VALUE(lpDocInfo),
                  OUTPUT ReturnValue).
  IF ReturnValue < 1 THEN
     MESSAGE "Error during StartDoc in PrintScreen procedure" 
             view-as ALERT-BOX INFORMATION.
  ELSE DO:
     RUN StartPage (hDC, OUTPUT ReturnValue).
     IF ReturnValue < 1 THEN
        MESSAGE "Error during StartPage in PrintScreen procedure" 
                view-as ALERT-BOX INFORMATION.
     ELSE DO:
        RUN PrintWindow (hDC, hWindow).
        RUN EndPage     (hDC, OUTPUT ReturnValue).
     END.
     RUN EndDoc   (hDC, OUTPUT ReturnValue).
  END.

  /* cleanup */
  RUN DeleteDC (hDC, OUTPUT ReturnValue).
  SET-SIZE(lpDocInfo) = 0.
  SET-SIZE(lpDocName) = 0.

END PROCEDURE. /* MakeDocument */

PROCEDURE PrintWindow :
/*------------------------------------------------------------------------------
  Notes: a couple of extra parameters would be nice, like:
         input  leftmargin, rightmargin, Ytop
         output Ybottom
------------------------------------------------------------------------------*/
  define input parameter hdcDest as INTEGER NO-UNDO.  /* Printer                    */
  define input parameter hWin    as INTEGER NO-UNDO.  /* windows handle to progress window to print */

  DEFINE VARIABLE hdcWin        as integer no-undo.   /* hdc window                      */
  DEFINE VARIABLE PrintHorzRes  as integer no-undo.   /* printer resolution              */
  DEFINE VARIABLE PrintVertRes  as integer no-undo.
  DEFINE VARIABLE WinHorzRes    as integer no-undo.   /* display resolution              */
  DEFINE VARIABLE WinVertRes    as integer no-undo.
  DEFINE VARIABLE hdcComp       as integer no-undo.   /* hdc memory                      */
  DEFINE VARIABLE hbmpComp      as integer no-undo.   /* bitmap in memory                */
  DEFINE VARIABLE hbmpDest      as integer no-undo.   /* bitmap on paper                 */
  DEFINE VARIABLE WinWidth      as INTEGER NO-UNDO.   /* dimensions of window            */
  DEFINE VARIABLE WinHeight     as INTEGER NO-UNDO.
  DEFINE VARIABLE PictureWidth  as integer no-undo.   /* dimensions of picture on paper  */
  DEFINE VARIABLE PictureHeight as integer no-undo.
  DEFINE VARIABLE Scale         as decimal no-undo.
  DEFINE VARIABLE xMargin       as INTEGER NO-UNDO.   /* center picture horizontally     */
  DEFINE VARIABLE numrows       as integer no-undo.   /* split large pics into rows/cols */
  DEFINE VARIABLE numcols       as integer no-undo.
  DEFINE VARIABLE rw            as integer no-undo.
  DEFINE VARIABLE cl            as integer no-undo.
  DEFINE VARIABLE lpRect        as MEMPTR  NO-UNDO.
  DEFINE VARIABLE ReturnValue   as integer no-undo.
  DEFINE VARIABLE lpOrigin      as MEMPTR  NO-UNDO.

  IF getParent THEN
     RUN GetParent(hWin, OUTPUT hWin).

  /* get the window resolution */
  run GetDC (hWin, output hdcWin).
  run GetDeviceCaps(hdcWin,  8 /* = HorzRes */, output WinHorzRes).
  run GetDeviceCaps(hdcWin, 10 /* = VertRes */, output WinVertRes).
  
  /* get the printer resolution */
  run GetDeviceCaps(hdcDest,  8 /* = HorzRes */, output PrintHorzRes).
  run GetDeviceCaps(hdcDest, 10 /* = VertRes */, output PrintVertRes).

  /* determine dimensions of the window */
  /* Also determine the coordinates of the upper-left corner. 
     This is (0,0) for a client window, but will be somewhere around (-4,-20) for
     a window with titlebar  */
  SET-SIZE (lpOrigin)   =  8.
  SET-SIZE (lpRect)     = 16.
  RUN GetWindowRect     (hWin, GET-POINTER-VALUE(lpRect) , OUTPUT ReturnValue).
  WinWidth              = GET-LONG(lpRect, 9) - GET-LONG(lpRect, 1).
  WinHeight             = GET-LONG(lpRect,13) - GET-LONG(lpRect, 5).
  PUT-LONG(lpOrigin, 1) = GET-LONG(lpRect, 1).
  PUT-LONG(lpOrigin, 5) = GET-LONG(lpRect, 5).
  RUN ScreenToClient    (hWin, GET-POINTER-VALUE(lpOrigin), OUTPUT ReturnValue).
  SET-SIZE (lpRect)     = 0.
  Scale         = MINIMUM(PrintHorzRes / WinWidth,
                          PrintVertRes / WinHeight).
  IF Scale < 1 THEN Scale = 1.
  
  run CreateCompatibleDC     (hdcWin, output hdcComp).
  run CreateCompatibleBitmap (hdcComp, 
                              integer(winWidth  * Scale),
                              integer(winHeight * Scale), 
                              output hbmpComp).
  run CreateCompatibleBitmap (hdcDest, 
                              integer(winWidth  * Scale),
                              integer(winHeight * Scale), 
                              output hbmpDest).
  run SelectObject (hdcComp, hbmpComp, output ReturnValue).
  run SelectObject (hdcDest, hbmpDest, output ReturnValue).
  run StretchBlt (hdcDest,
                  0,
                  0,
                  integer(winwidth  * Scale) ,
                  integer(winHeight * Scale) ,
                  hdcWin,
                  get-long(lpOrigin,1),
                  get-long(lpOrigin,5),
                  winwidth,
                  winheight,
                  13369376,  /* = SRCCOPY */
                  output ReturnValue).

  /* cleanup */
  run ReleaseDC    (hWin, hdcWin, output ReturnValue).
  run DeleteDC     (hdcComp, output ReturnValue).
  run DeleteObject (hbmpComp, output ReturnValue).
  run DeleteObject (hbmpDest, output ReturnValue).
  SET-SIZE(lpOrigin)=0.
  
END PROCEDURE. /* PrintWindow */
/* ==============================================================
   file    : printscreen.i
   by      : Jurjen Dijkstra (Modified by Ian Keene Oct 2003)
   dd      : 05/16/1999
   purpose : API definitions used in printscreen.p
   ============================================================== */
&GLOB GDI "gdi32.dll"
&GLOB USER "user32.dll"
&GLOB BOOL LONG
&GLOB COLORREF LONG
&GLOB DWORD LONG
&GLOB HANDLE LONG
&GLOB HDC LONG
&GLOB HGDIOBJ LONG
&GLOB HWND LONG
&GLOB INTEGER LONG
&GLOB LONG LONG
&GLOB LP LONG
&GLOB LPCSTR CHARACTER
&GLOB LPCTSTR CHARACTER
&GLOB LPSECURITY_ATTRIBUTES LONG
&GLOB LPSTRUCT LONG
&GLOB LPTSTR CHARACTER
&GLOB SHORT SHORT
&GLOB UINT LONG
&GLOB WORD SHORT

PROCEDURE CreateDCA EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER lpszDriver   AS {&LPCTSTR} NO-UNDO.
  DEFINE INPUT  PARAMETER lpszDevice   AS {&LPCTSTR} NO-UNDO.
  DEFINE INPUT  PARAMETER lpszOutput   AS {&LP} NO-UNDO.
  DEFINE INPUT  PARAMETER lpInitData   AS {&LPSTRUCT} NO-UNDO.
  DEFINE RETURN PARAMETER hDC          AS {&HDC} NO-UNDO.
END PROCEDURE.

PROCEDURE StartDocA EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdc   AS {&HDC} NO-UNDO.
  DEFINE INPUT  PARAMETER lpdi  AS {&LPSTRUCT} NO-UNDO.
  DEFINE RETURN PARAMETER JobId AS {&int} NO-UNDO.
END PROCEDURE.

PROCEDURE StartPage EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdc         AS {&HDC} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue AS {&int} NO-UNDO.
END PROCEDURE.

PROCEDURE EndPage EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdc         AS {&HDC} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue AS {&int} NO-UNDO.
END PROCEDURE.

PROCEDURE EndDoc EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdc         AS {&HDC} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue AS {&int} NO-UNDO.
END PROCEDURE.

PROCEDURE GetParent EXTERNAL {&USER} :
  DEFINE INPUT  PARAMETER hWinHdl    AS {&HWND} NO-UNDO.
  DEFINE RETURN PARAMETER hwndParent AS {&HWND} NO-UNDO.
END PROCEDURE.

PROCEDURE GetDC EXTERNAL {&USER} :
  DEFINE INPUT  PARAMETER hWinHdl AS {&HWND} NO-UNDO.
  DEFINE RETURN PARAMETER hdc     AS {&HDC} NO-UNDO.
END PROCEDURE.

PROCEDURE GetDeviceCaps EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdc    AS {&HDC} NO-UNDO.
  DEFINE INPUT  PARAMETER nIndex AS {&int} NO-UNDO.
  DEFINE RETURN PARAMETER dwCaps AS {&int} NO-UNDO.
END PROCEDURE.

PROCEDURE GetWindowRect EXTERNAL {&USER} :
  DEFINE INPUT  PARAMETER hWinHdl     AS {&HWND} NO-UNDO.
  DEFINE INPUT  PARAMETER lpRect      AS {&LPSTRUCT} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO.
END PROCEDURE.

PROCEDURE ScreenToClient EXTERNAL {&USER} :
  DEFINE INPUT  PARAMETER hWinHdl     AS {&HWND} NO-UNDO.
  DEFINE INPUT  PARAMETER lpPoint     AS {&LPSTRUCT} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO.
END PROCEDURE.

PROCEDURE CreateCompatibleDC EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdc      AS {&HDC} NO-UNDO.
  DEFINE RETURN PARAMETER hdcComp  AS {&HDC} NO-UNDO.
END PROCEDURE.

PROCEDURE CreateCompatibleBitmap EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdc     AS {&HDC} NO-UNDO.
  DEFINE INPUT  PARAMETER nWidth  AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nHeight AS {&int} NO-UNDO.
  DEFINE RETURN PARAMETER hbmp    AS {&HANDLE} NO-UNDO.
END PROCEDURE.

PROCEDURE SelectObject EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdc         AS {&HDC} NO-UNDO.
  DEFINE INPUT  PARAMETER hgdiobj     AS {&HGDIOBJ} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue AS {&HGDIOBJ} NO-UNDO.
END PROCEDURE.

PROCEDURE StretchBlt EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdcDest      AS {&HDC} NO-UNDO.
  DEFINE INPUT  PARAMETER nXOriginDest AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nYOriginDest AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nWidthDest   AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nHeightDest  AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER hdcSrc       AS {&HDC} NO-UNDO.
  DEFINE INPUT  PARAMETER nXOriginSrc  AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nYOriginSrc  AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nWidthSrc    AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nHeightSrc   AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER dwRop        AS {&DWORD} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue  AS {&BOOL} NO-UNDO.
END PROCEDURE.

PROCEDURE BitBlt EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdcDest      AS {&HDC} NO-UNDO.
  DEFINE INPUT  PARAMETER nXOriginDest AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nYOriginDest AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nWidthDest   AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nHeightDest  AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER hdcSrc       AS {&HDC} NO-UNDO.
  DEFINE INPUT  PARAMETER nXOriginSrc  AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER nYOriginSrc  AS {&int} NO-UNDO.
  DEFINE INPUT  PARAMETER dwRop        AS {&DWORD} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue  AS {&BOOL} NO-UNDO.
END PROCEDURE.

PROCEDURE ReleaseDC EXTERNAL {&USER} :
  DEFINE INPUT  PARAMETER hWinHdl     AS {&HWND} NO-UNDO.
  DEFINE INPUT  PARAMETER hdc         AS {&HDC} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO.
END PROCEDURE.

PROCEDURE DeleteDC EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hdc         AS {&HDC} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO.
END PROCEDURE.

PROCEDURE DeleteObject EXTERNAL {&GDI} :
  DEFINE INPUT  PARAMETER hObject     AS {&HGDIOBJ} NO-UNDO.
  DEFINE RETURN PARAMETER ReturnValue AS {&BOOL} NO-UNDO.
END PROCEDURE.

PROCEDURE GetProfileStringA EXTERNAL "kernel32.dll":
   DEFINE INPUT PARAMETER in-appname AS CHARACTER NO-UNDO.
   DEFINE INPUT PARAMETER in-keyname AS CHARACTER NO-UNDO.
   DEFINE INPUT PARAMETER in-default AS CHARACTER NO-UNDO.
   DEFINE OUTPUT PARAMETER in-ret-str AS CHARACTER NO-UNDO.
   DEFINE INPUT PARAMETER in-n-size AS LONG NO-UNDO.
   DEFINE RETURN PARAMETER out-nuchr AS LONG NO-UNDO. 
END PROCEDURE.

PROCEDURE OpenPrinterA EXTERNAL "winspool.drv":
   DEFINE INPUT PARAMETER in-prtname AS CHARACTER NO-UNDO.
   DEFINE OUTPUT PARAMETER out-hwnd AS LONG NO-UNDO.
   DEFINE INPUT PARAMETER in-def AS LONG NO-UNDO.
END PROCEDURE.

Set default printer

Method 1

Based on an idea by Richard Gordon.

The question was: how to get a list of available printers and set a new default printer. Here is a solution that uses the win.ini file.
It is recommended for a 32-bit application to use the EnumPrinters procedure to get a list of available printers.
One might think it's not modern to change the default printer by writing in win.ini but this is still the recommended way according to MS documentation.

{windows.i}
 
DEFINE VARIABLE list-of-printers AS CHARACTER.
DEFINE VARIABLE newdefault AS CHARACTER.
DEFINE VARIABLE driver-and-port AS CHARACTER.
 
RUN getkey
  (INPUT  "devices",    /* The section name */
   INPUT  "",           /* The key name  */
   INPUT  "win.ini",    /* Name of ini file */
   OUTPUT list-of-printers).     /* Returned stuff */
 
/* you now have a comma separated list of printer names. Check it: */
/* message list-of-printers view-as alert-box. */
/* allow the user to pick one, suppose he picks the third entry: */
 
newdefault = ENTRY(3, list-of-printers).
 
/* read driver and port for the new default printer */
RUN getkey
  (INPUT  "devices",        /* The section name */
   INPUT  newdefault,       /* The key name  */
   INPUT  "win.ini",        /* Name of ini file */
   OUTPUT driver-and-port). /* Returned stuff */
 
/* and write it back */
RUN putkey 
  (INPUT "windows",
   INPUT "device",
   INPUT "win.ini",
   INPUT newdefault + "," + driver-and-port).
 
/* check it: 
message session:printer-name view-as alert-box.
*/
 
RETURN.
 
/* ------------- internal procedures ------------ */
 
PROCEDURE getkey :
 
DEFINE INPUT PARAMETER i-section AS CHARACTER.
DEFINE INPUT PARAMETER i-key AS CHARACTER.
DEFINE INPUT PARAMETER i-filename AS CHARACTER.
DEFINE OUTPUT PARAMETER o-value AS CHARACTER.
 
DEFINE VARIABLE EntryPointer AS INTEGER NO-UNDO.
DEFINE VARIABLE mem1 AS MEMPTR NO-UNDO.
DEFINE VARIABLE mem2 AS MEMPTR NO-UNDO.
DEFINE VARIABLE mem1size AS INTEGER NO-UNDO.
DEFINE VARIABLE mem2size AS INTEGER NO-UNDO.
DEFINE VARIABLE i       AS INTEGER    NO-UNDO.
DEFINE VARIABLE cbReturnSize  AS INTEGER    NO-UNDO.
 
ASSIGN
  SET-SIZE(mem1)  = 4000
  mem1size = 4000.
 
IF i-key = "" THEN EntryPointer = 0.
 
ELSE DO:
  /* Must fill memory with desired key name and EntryPointer must point to it */
 
  ASSIGN
  SET-SIZE(mem2) = 128
  mem2size = 128
  EntryPointer = GET-POINTER-VALUE(mem2)
  PUT-STRING(mem2,1) = i-key.
END.
 
RUN getprivateprofilestring{&A} IN hpApi
                              (i-section, 
                               EntryPointer, 
                               "",
                               GET-POINTER-VALUE(mem1),
                               INPUT mem1size, 
                               i-filename,
                               OUTPUT cbReturnSize).
 
/* if i-key was "", Windows will return a list of all keys in i-section.
   This list is not comma-separated but separated by CHR(0). Progress
   can not handle that easily so we'll now replace every 0 by a comma: */ 
 
DO i = 1 TO cbReturnSize:
  /* If this is a list convert null character into a comma to generate a csv
     type variable */
  o-value = IF (GET-BYTE(mem1, i) = 0 AND i NE cbReturnSize) 
               THEN o-value + ","
               ELSE o-value + CHR(GET-BYTE(mem1, i)).
END.
 
  SET-SIZE(mem1) = 0.
  SET-SIZE(mem2) = 0.
 
END PROCEDURE.
 
 
PROCEDURE putkey :
DEFINE INPUT PARAMETER i-section AS CHARACTER.
DEFINE INPUT PARAMETER i-key AS CHARACTER.
DEFINE INPUT PARAMETER i-filename AS CHARACTER.
DEFINE INPUT PARAMETER i-value AS CHARACTER.
 
DEFINE VARIABLE cbReturnSize AS INTEGER.
 
RUN writeprivateprofilestring{&A} IN hpApi
                               (i-section, 
                                i-key, 
                                i-value,
                                i-filename, 
                                OUTPUT cbReturnSize ).
 
END PROCEDURE.

Method 2

By Nenad Orlovic

Nenad Orlovic writes: "Here is another example for same purpose that uses SetPrinter API function. I don't know which way is better. Both have the same problem, they can't change SESSION:PRINTER-NAME when it was already changed by SYSTEM-DIALOG PRINTER-SETUP command."
usage:

   run SetDefaultPrinter("HP LaserJet 4L").

Additional note from Jurjen: Microsoft knowledgebase article Q140560 says this SetPrinter() call does not work on Windows NT4. Probably because NT4 does not support the PRINTER_INFO_5 structure. The good news is: Windows 2000 finally has the new API function we have all been waiting for: "SetDefaultPrinterA". The usage of this new API function is exactly similiar to the SetDefaultPrinter procedure by Nenad.
I did not try but it may be a good idea to call SendNotifyMessage(HWND_BROADCAST,WM_SETTINGCHANGE,0,"windows") to solve the SESSION:PRINTER-NAME problem.

/* Nenad Orlovic */
/* Change default printer example */
 
FUNCTION CheckBit RETURNS LOGICAL
  ( INPUT ip_dword AS INT,
    INPUT ip_bit AS INTEGER ) :
/*------------------------------------------------------------------------------
  Purpose:  Checks if ip_bit in ip_dword is set
    Notes:  ip_bit = 0,1,...,31
------------------------------------------------------------------------------*/
DEFINE VARIABLE iBit AS INTEGER EXTENT 32 INIT 
    [
     1       ,2       ,4       ,8        ,16       ,32       ,64        ,128,
     256     ,512     ,1024    ,2048     ,4096     ,8192     ,16384     ,32768,
     65536   ,131072  ,262144  ,524288   ,1048576  ,2097152  ,4194304   ,8388608,
     16777216,33554432,67108864,134217728,268435456,536870912,1073741824,-2147483648
    ] NO-UNDO.
 
    IF ip_bit > 31 OR ip_bit < 0 then return ?.
 
    ip_dword = TRUNC(ip_dword / iBit[ip_bit + 1],0).
    RETURN (ip_dword MOD 2 = 1).
 
END FUNCTION.
 
PROCEDURE SetDefaultPrinter:
/*------------------------------------------------------------------------------
  Purpose:     Sets ip_printer to be windows default printer 
  Parameters:  input - Printer name
  Notes:       
------------------------------------------------------------------------------*/
DEFINE INPUT PARAMETER ip_printer AS CHARACTER NO-UNDO.
 
DEFINE VARIABLE iRet AS INTEGER NO-UNDO.
DEFINE VARIABLE pPrinter AS MEMPTR NO-UNDO.
DEFINE VARIABLE hPrinter AS INTEGER NO-UNDO.
DEFINE VARIABLE iSize AS INTEGER NO-UNDO.
DEFINE VARIABLE iAttr AS INTEGER NO-UNDO.
 
DEFINE VARIABLE cMessage AS CHARACTER NO-UNDO.
 
  RUN OpenPrinterA (ip_printer,OUTPUT hPrinter,0, OUTPUT iRet).
  IF iRet = 0 THEN cMessage = "ERROR: OpenPrinterA " + ip_printer.
  ELSE DO:
    SET-SIZE(pPrinter) = 1. 
    /* First call is only to get needed size for pPrinter */
    RUN GetPrinterA(hPrinter,5,pPrinter,1,OUTPUT iSize,OUTPUT iRet). 
    SET-SIZE(pPrinter) = 0.
    SET-SIZE(pPrinter) = iSize. 
    RUN GetPrinterA(hPrinter,5,pPrinter,iSize,OUTPUT iSize,OUTPUT iRet).
    /* pPrinter points to PRINTER_INFO_5 */
 
    IF iRet = 0 THEN cMessage = "ERROR: GetPrinterA " + ip_printer.
    ELSE DO:
        iAttr = GET-LONG(pPrinter,9).
 
        IF NOT CheckBit(iAttr,2) THEN DO:
            PUT-LONG(pPrinter,9) = iAttr + 4. /* 4 = PRINTER_ATTRIBUTE_DEFAULT */
            RUN SetPrinterA(hPrinter,5,pPrinter,0,OUTPUT iRet).
            IF iRet = 0 THEN cMessage = "ERROR: SetPrinterA " + ip_printer.
        END.
 
        RUN ClosePrinter(hPrinter,OUTPUT iRet).
        IF iRet = 0 THEN cMessage = cMessage + "ERROR: ClosePrinter " + ip_printer.
    END.        
  END.
  SET-SIZE(pPrinter) = 0.
  RETURN cMessage.
END PROCEDURE.
 
PROCEDURE OpenPrinterA EXTERNAL "WINSPOOL.DLL":
   DEFINE INPUT PARAMETER pPrinterName AS CHARACTER.
   DEFINE OUTPUT PARAMETER phPrinter AS LONG.
   DEFINE INPUT PARAMETER pDefault AS LONG.
   DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE ClosePrinter EXTERNAL "WINSPOOL.DLL":
   DEFINE INPUT PARAMETER hPrinter AS LONG.
   DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE SetPrinterA EXTERNAL "WINSPOOL.DLL":
    DEFINE INPUT PARAMETER hPrinter AS LONG.
    DEFINE INPUT PARAMETER Level AS LONG.
    DEFINE INPUT PARAMETER pPrinter AS MEMPTR.
    DEFINE INPUT PARAMETER COMMAND AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.
 
PROCEDURE GetPrinterA EXTERNAL "WINSPOOL.DLL":
    DEFINE INPUT PARAMETER hPrinter AS LONG.
    DEFINE INPUT PARAMETER Level AS LONG.
    DEFINE INPUT PARAMETER pPrinter AS MEMPTR.
    DEFINE INPUT PARAMETER cbBuf AS LONG.
    DEFINE OUTPUT PARAMETER pcbNeeded AS LONG.
    DEFINE RETURN PARAMETER X AS LONG.
END PROCEDURE.

The printer properties dialog

by Johan Bouduin

The next procedure calls the properties dialog for any available printer.
The properties dialog is more or less part of the printer driver, so the appearance will be different for each printer.

the source

It's only tested on Win95.

/********************************************************************
  name        : prg/de/de_prop.p
  author      : Johan Bouduin
  date        : 13/02/1998
  purpose     : get printers
  syntax      : run prg/de/de_prop.p
  parameters  : input PC_PRINTER_NAME as character
                input PH_CALLER as handle
  internal procedures : 
                PROC_FREEMEM : de-allocate reserved memory
  internal functions :
  external functions :
                OpenPrinter "winspool.drv"
                ClosePrinter "winspool.drv"
                PrinterProperties "winspool.drv"
  modifications :
                Jurjen moved external procedures to windows.i/p
*********************************************************************/
 
/***** Parameter definitions ****************************************/
  DEFINE INPUT PARAMETER PC_PRINTER_NAME AS CHARACTER NO-UNDO.
  DEFINE INPUT PARAMETER PH_CALLER       AS HANDLE NO-UNDO.
 
/***** Variable definitions *****************************************/
  DEFINE VARIABLE VM_PRINTER_HANDLE AS MEMPTR NO-UNDO.
  DEFINE VARIABLE VI_RETURN_VALUE   AS INTEGER NO-UNDO.
  DEFINE VARIABLE VM_PRINTER_NAME   AS MEMPTR NO-UNDO.
  DEFINE VARIABLE VM_DEFAULTS       AS MEMPTR NO-UNDO.
 
/***** External procedures ******************************************/
 
{windows.i}
 
/***** Main-block ***************************************************/
DO:
 
  /***** get a printer handle ***************************************/
  SET-SIZE(VM_PRINTER_HANDLE) = 4. 
  RUN OpenPrinter{&A} IN hpApi(
    INPUT PC_PRINTER_NAME,
    INPUT GET-POINTER-VALUE(VM_PRINTER_HANDLE),
    INPUT GET-POINTER-VALUE(VM_DEFAULTS),
    OUTPUT VI_RETURN_VALUE).
 
  IF  VI_RETURN_VALUE EQ 0
  THEN DO:
    MESSAGE "An error occurred while trying to open the printer"
      VIEW-AS ALERT-BOX.
    RETURN "not open".
  END.
 
  /***** call printerproperties *************************************/
  RUN PrinterProperties IN hpApi(
    INPUT PH_CALLER:HWND,
    INPUT GET-LONG(VM_PRINTER_HANDLE,1),
    OUTPUT VI_RETURN_VALUE).
 
  /***** close the printer ******************************************/
  RUN ClosePrinter IN hpApi(
    INPUT GET-LONG(VM_PRINTER_HANDLE,1),
    OUTPUT VI_RETURN_VALUE).    
  IF  VI_RETURN_VALUE EQ 0
  THEN DO:
    MESSAGE "An error occurred while trying to close the printer"
      VIEW-AS ALERT-BOX.
    RETURN "not closed".
  END.
  RUN PROC_FREEMEM.
  RETURN.
END.
 
/***** internal procedures ******************************************/
 
PROCEDURE PROC_FREEMEM:   
  SET-SIZE(VM_PRINTER_HANDLE) = 0. 
END.
 

Usage

Suppose you have used the EnumPrinters function to populate a selection list widget (SELECT-1) with names of all available printers:


On choose of the button do:

  run prg/de/de_prop.p (select-1:screen-value,  c-win:handle).

The first parameter is the name of the printer, the second parameter is the Progress widget handle of the current window. This window will be the parent window for the property dialog; you will not be able to activate c-win while the property dialog is opened.