Win32 API samples

This collection of code snippets used to be hosted on www.global-shared.com.
It is an old collection: it all began in 1996 or 1997, when Progress 8 was new and many of us were still using Windows 3.1 !!
Unlike wine or paintings, program code doesn't get better when it ages. You can find fragments that can be improved because Microsoft continuously expands their API, or are outdated because Progress has added features to the ABL so we don't need to use the WIN32 API anymore.


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.

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:


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


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


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.

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.

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 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:


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",

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:


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).


Using a MEMPTR parameter for a CHAR

I have received many code examples (thank you) but several use parameters of type MEMPTR where a CHARACTER would be more effective, in my opinion.
Since this seems to be a common issue, I will try to explain what's going on.
Most procedure declarations are derived from text in Windows API reference books or helpfiles that are aimed at C programmers. A typical example would be (this is 32-bit but the theory also applies to 16-bit) :

The GetProfileString function retrieves the string associated with the specified key in 
the given section of the WIN.INI file. This function is provided for compatibility with 
16-bit Windows-based applications. Win32-based applications should store initialization 
information in the registry. 
DWORD GetProfileString(
    LPCTSTR  lpAppName,        // points to section name
    LPCTSTR  lpKeyName,        // points to key name
    LPCTSTR  lpDefault,        // points to default string
    LPTSTR   lpReturnedString, // points to destination buffer
    DWORD    nSize,            // size of destination buffer
);   

All these typedefs starting with 'lp' are 'long pointer' to something and the linecomments also clearly say "points to..."
So it is fully understandeble that you would translate this to Progress like this:

PROCEDURE GetProfileStringA EXTERNAL "kernel32" :
  DEFINE INPUT PARAMETER lpAppName        AS MEMPTR.
  DEFINE INPUT PARAMETER lpKeyName        AS MEMPTR.
  DEFINE INPUT PARAMETER lpDefault        AS MEMPTR.
  DEFINE INPUT PARAMETER lpReturnedString AS MEMPTR.
  DEFINE INPUT PARAMETER nSize            AS LONG.
END PROCEDURE.

When you use this function to read a value from an ini file, you probably have character variables (or literals) for the section, key name and default string.And now you have to convert them to and from MEMPTR variables first. So you would have to declare variables of type MEMPTR, allocate them (with set-size) and put the strings in them (with put-string). Then you can call the GetPrivateProfileString procedure. Finally you would have to use get-string to get the answer from lpReturnedString.
To me that looks like a lot of work for passing some strings.

The good news is that it does not have to be so difficult.


Memptr or character parameters?

Here is an alarming e-mail from Brent Wardle to the Peg. (I know one should no copy e-mails from one forum to antoher, but this one really needs attention in the context of this website).
Hi Peg,
We are upgrading from 9.1D to 10.B and I found an issue tonight that you
may or may not know about.
You can no longer define and use a character output parameter for a
windows DLL call.
You do not get an error message during compile but at run time you get:
"You cannot use OUTPUT to return CHAR or LONGCHAR data. Use MEMPTR
Instead (12200).
Error 12200 is not available on KB error code search.


How to receive events from an OLE Automation object

by Theo Albers

When you write your own OLE Automation component in C++ (ATL 3.0 of Microsoft Visual Studio 6+) you will experience the problem that Progress doesn't handle the OLE-events. The "OLE COM"-viewer of Progress is able to show proper events and methods, but the 4GL code simply won't be triggered.

In cooperation with Progress I was able to figure out the problem: when Progress registers for event subscription, it needs to be called back using IDispatch. This is in contrast to other clients like Visual Basic or Windows Scripting Host, which implement the event interface. For more information see the Progress knowledge base entry P56004. For more information on IConnectionPointContainer.Advise() see for instance http:builder.com.com/5100-6373-1050003.html and http:www.techvanguards.com/com/concepts/connectionpoints.asp.


How is COM/OLE/ActiveX different from DLL?

The original question was: where do I learn about the differences between DLL and COM/OLE and so on, so I don't confuse one with the other and know the limitations/features of each.

Jeez I dunno... where to start? Please help me out by editing this topic whenever you want to change something!

an introduction

A DLL (Dynamic Link Library) is much like a persistent procedure in Progress. In Progress, the persistent procedure contains one or more internal procedures and one or more functions, private or not. The IP's and UDF's that are not private can be called "from outside", provided that you know the name and the parameters and that you have any clue what kind of action the IP/UDF will perform.
Likewise, a DLL contains one or more functions, published or not. (it is uncommon to have procedures instead of functions). You can load the DLL persistent with LoadLibrary, call the published functions and finally use FreeLibrary to get rid of the persistent DLL. As with a Progress persistent procedure, you need to have some sort of written documentation to find the function names, their parameters and expected behaviour.
DLL's work well although problems may rise - especially in setup, maintenance and documentation.
Setup: where should you install these things - before an application can load a DLL it must first try to locate it. You could put it in a subdirectory of your application, but that is not practical if you have many applications (like Microsoft has) and want to reuse common components. You could put them in a shared directory, like windows/system32, but then you enter the maintenance nightmare.
Maintaining a DLL can become a problem both for the programmer and for the end-user. How can you add a new function to an existing DLL, or even a new parameter to an existing fuction, and still make sure that you don't get in trouble with the user base? Since a single DLL can be used by several different applications, there is no way of forcing all those application vendors to simultaniously create an update that matches with the new DLL version.
Documentation: a DLL is a black box, it does not contain its own documentation. It becomes messy when you have a lot of programmers writing DLL's, especially if the company is so big that there are different cultures between departments and different parameter styles you can't get used to.
So even though DLL work well technically, there was a need to invent a way to have DLL's document themselves, publish their file locations to the apps that needed to use them, and to be somewhat version independent.
An ActiveX control is basically just a DLL with some extra functions in it. One of those functions is RegisterServer, which writes the location of the DLL in the registry database under some unique identifier. A calling application does not need to know beforehand where the DLL is located, it only needs to know the unique identifier and can then locate the DLL by looking in the registry, before it loads that DLL into memory. (I say DLL but most of these have the extension OCX, although they are really just DLL with extras). So you don't call LoadLibrary directly, you call it through a wrapper function (not sure but I believe it is CoCreateInstance).
An application is not supposed to call any of the built-in functions directly, because of the risk that you call the wrong version of a function. Instead, the app needs to query the supported interfaces first. To make a long story short, the OCX sends the application a table of built-in functions that can be used. The OCX might support several versions but not all versions can be mixed. Suppose for example an OCX that contains functions AddCustomer and DeleteCustomer, and both have a version 1 and a version 2. One application might want to call AddCustomer version 1 and DeleteCustomer version 1, an other app might want to use version 2 of the interface. A mix is not allowed, e.g. AddCustomer version 2 and DeleteCustomer version 1 will have undesired results. So the OCX sends a table of alllowed functions to the app, with functionpointers to the functions that match the version of the calling application. (in reality there is probably only one function that has different versions. In that case, version 2 of the interface just points to the one and only version of the other functions)
An OCX is typically linked to a TLB resource (Type Library) that contains documentation for the interfaces. Not just for human eyes, but also for the calling application that can now automatically validate the number and type of parameters. This also means that parameter types needed to be standarized, which was not the case with bare DLL's.
Responding to events:


#
Syndicate content