Admin Production rocket
Current Publication

4 Creating COM Clients

NetExpress Bookshelf Help V5.1 WrapPack 1
Rocket® Net Express™ (formerly a product of Micro Focus or formerly a product of Open Text) documentation, created before Rocket Software acquired certain products from OpenText, may include outdated references to "Micro Focus" or "OpenText", both of which are trademarks of OpenText or its affiliates. Rocket Software is not affiliated with Micro Focus or OpenText and has since rebranded these products as Rocket® products. You may also encounter outdated links in this documentation. If you do, please contact Rocket Support (support@rocketsoftware.com) for assistance.

Chapter 4: Creating COM Clients

This chapter deals with writing COM clients from COBOL.

Overview

From an COBOL progam you can:

  • Send messages to a COM object
  • Set or get COM properties

This enables you to control any windows application that has an COM automation interface, directly from COBOL. A program which uses COM objects like this is known as a COM client.

COM enables the COM object to be written in a different language to the client, and defines the type and format of data which can be sent between clients and objects. Any data sent to COM objects from COBOL programs is type-coerced according to the rules outlined in the chapter COM Data Types.

COBOL COM automation support represents COM objects as COBOL objects, by providing a proxy for each COM object a client is using. The client sends messages to the proxy, and these are forwarded on to the COM object by the COBOL run-time system.

A Client Sending Messages to a COM Proxy

Figure 4-1: A Client Sending Messages to a COM Proxy

The following sections cover the process of writing a COM client:

Enabling a Program as a COM Client

Any COBOL program can be made into a COM client; you don't need to write an COBOL class. All you need to do is:

  1. Set Compiler directive OOCTRL(+P) in your program:
    $set ooctrl(+p)

    Note: This limits the number of parameters in any method invoked or declared to 31, plus an optional RETURNING parameter.

  2. Map each COM object you want to use to an COBOL class name, in the CLASS-CONTROL paragraph:
    class-control.
         class-name IS CLASS "$OLE$windows-registry-name" 

    where windows-registry-name is the server name under which the COM object is entered in the Windows system registry. You can use either the ProgID, which is a human-readable name, or the CLSID, which is a 16-byte number guaranteed to be unique for each COM object in the world. You would normally use the ProgID, as the CLSID is likely to change for each new version of the COM object released. For example:

    class-control
        Word is class "$OLE$word.basic"
        HTMLHelp is class 
                   "$OLE${adb880a6-d8ff-11cf-9377-00aa003b7a11}"
        . 
  3. Declare an OBJECT REFERENCE data item to hold the handle to the proxy for the COM object.

Example

$set ooctrl(+P)
 class-control.
*> comploan is the name (ProgID) of the COM object
     comploan is class "$OLE$comploan"
     ...
     .

 working-storage section. 
 01 theCompLoanServer            object reference.

Creating a COM Proxy Object

You need to create a proxy object for each COM object you want to use. When you create the proxy, two things happen:

  • The COBOL run-time system requests Windows to find the COM object you want. If the object isn't yet running, Windows starts it.
  • The COBOL run-time system creates a proxy to the object, and gives you an object handle to the proxy.

To create a proxy, send the message "new" to the COBOL proxy class. You can now start sending messages to the COM object through the proxy.

Example

 working-storage section. 
 01 theCompLoanServer            object reference.
 ...
 procedure division. 
   ...
     invoke comploan "new" returning theCompLoanServer
   ...

You can also specify the location of the server when you create it. To specify the server location, send the message "newWithServer" to the COBOL proxy class, together with the server's machine name.

Example

 working-storage section. 
 01 theCompLoanServer            object reference.
  ...
 procedure division. 
  ...
     invoke comploan "newWithServer" using z"machine1" 
                                 returning theCompLoanServer
  ...

Sending Messages to a COM Object

Once you have a COM object, you can send it messages and set or get its properties. Both message sends and property get/set operations are handled by using the INVOKE verb to send messages to the proxy object. Whenever you send a message to the proxy object, where the name begins with "get" or "set", the COBOL run-time system automatically converts that to a property get or set on the COM object, as shown in the diagram below.

Sending Messages and Setting Properties

Figure 4-2: Sending Messages and Setting Properties

To send a message:

invoke proxyObject "messagename" [using param-1 [param-2...]] 
                                 [returning result]
proxyObject The proxy for the COM object. Creating a proxy and starting the COM object is explained in the section Creating a COM proxy object.
messagename The message you want to send.
param-1 Any parameters needed for the message. COBOL data types are converted to COM data types as explained in the chapter COM Data Types. Pass all parameters by reference (COBOL default).
result The result if this method returns one. COBOL data types are converted to COM data types as explained in the chapter COM Data Types.

To set a property:

invoke proxyObject "setPropertyName" using value 
proxyObject The proxy for the COM object. Creating a proxy and starting the COM object is explained in the section Creating a COM proxy object
PropertyName The name of the property you want to set.
value The new value for the property. COBOL data types are converted to COM data types as explained in the chapter COM Data Types. Pass the value by reference (COBOL default).

To get a property:

invoke proxyObject "getPropertyName" returning value 
proxyObject The proxy for the COM object. Creating a proxy and starting the COM object is explained in the section Creating a COM proxy object.
PropertyName The name of the property you want to set.
value The value of the property. COM data types are converted to COBOL data types as explained in the chapter COM Data Types. The value is returned to you in an area of memory which can be overwritten by COM, so take a copy of the value if you need to keep it.

The following example sets a property, sends a message, and then gets a property.

working-storage section.
  ...
 01 theCompLoanServer       object reference.
 01 AnAmount                pic 9(7).99.
 01 ARate                   pic 99.99.
 01 Amount20                pic $9(7).99. 
  ...

 procedure division. 
   ... 
     invoke theCompLoanServer "SetLoanTermYears" using "20"
     invoke theCompLoanServer "calculate"
     invoke theCompLoanServer "GetYearPayment" 
                                         returning Amount20
   ...

Forcing the COM Message Type

You can override the run-time system default of assuming that all COM messages sent from COBOL beginning "set" or "get" are property set or get operations. You can also force a message which is not prefixed "set" or "get" to be a property set or get. To force the message type, send the message "setDispatchType" to class OLEsup (filename OLEsup):

invoke OLEsup "setDispatchType" using by value lsType 

where:

lsType PIC X(4) COMP-5

value 0 = next message forced to invoke a method value 1 = next message forced to invoke a property set value 2 = next message forced to invoke a property get

The message type is only forced for the next COM message send, after which it reverts to the default behavior until you "setDispatchType" again. Class OLEsup encapsulates several useful functions for COM automation programming, and is fully documented in the COM Automation Class Library reference.

Using Type Library Information

The type library for a COM object defines information that can be useful when you are writing a client. You can generate a COBOL copyfile for any type library by using the Type Library Assistant. Click Type Library Assistant on the Net Express Tools menu.

Depending on the information in the type library, the COBOL copyfile includes the following:

  • COBOL type definitions for any data types defined by the type library
  • For each automation interface defined by the library:
    • Level-78 data items containing the IID (interface IDs) of any interfaces defined by the type library
    • A comment showing the interface description
    • A commented-out pointer data item with the name of the interface
    • Comments showing how to set and get all the properties of the interface
    • Comments showing how to invoke all the methods of the interface
  • For each class defined by the type library:
    • Level-78 data items containing the CLSID and ProgID of classes defined by the library
    • A comment showing the class-control entry needed to use the class
    • A comment showing the class description
  • Data items for each enumeration

Finalizing a COM Proxy Object

The proxy objects created to represent COM objects are not automatically garbage collected by the COBOL run-time system. When you have finished with a COM object, you should send the "finalize" message to its proxy. When a COM object has no connections remaining, it will usually shut itself down unless it is visible or requires an explicit method call (for example "quit" shuts down Microsoft Word).

Example

invoke theCompLoanServer "finalize" 
                         returning theCompLoanServer

COM Automation Exceptions

COM clients get notified of COM errors through COBOL exceptions. Any COM errors raised by a COM object (for instance, when you send a message that the object doesn't recognize), are returned to those COM clients written in COBOL, as exceptions. The default exception behavior is for the client to display a message warning of the exception, and then terminate. You can trap the exception yourself though, and handle it with your own error processing code.

The instructions below assume you have first read the information on Exception Handling Frameworks .

To trap COM exceptions:

  1. Declare the ExceptionManager, OLEExceptionManager, OLEsup and Callback or EntryCallback (see step 2) classes in the Class-Control paragraph of your COM client:
    class-control. 
        ...
        OLEExceptionManager is class "OLEexpt"
        ExceptionManager is class "exptnmgr"
        OLEsup is class "OLEsup"
        Callback is class "callback"
        EntryCallback is class "entrycll"
        ...
  2. Write an exception handler (either a method, or an entry-point), and create a Callback or EntryCallback to it. For example, a Callback looks like this:
    invoke Callback "new" using anObject z"methodName"
                      returning aHandler

    An EntryCallback looks like this:

    invoke EntryCallback "new" using  z"entryPointname"
                      returning aHandler
  3. Register the Callback or EntryCallback against the OLEExceptionManager class. For example:
    invoke ExceptionManager "register"
             using OLEExceptionManager aHandler

Now all COM exceptions sent through to the client get sent to your exception handler.

Your exception handler receives three parameters; the first is the object handle to the OLEExeptionManager, and the second is an exception ID. The third parameter is an OrderedCollection object whose first element is a CharacterArray object containing information about the exception. To get the COM error code, you need to subtract the base COM Exception error number from the exception ID. You can get the value of the base COM Exception error number by sending the "getBaseOLEException" message to class OLEsup. To get the base number:

invoke OLEsup "getBaseOLEException" returning lsBase 

where:

lsBase PIC X(4) COMP-5

The table below gives brief descriptions of the exception numbers returned through the ExceptionManager by the COBOL run-time system.

Value Description
0 Server defined COM exception
1 Parameter count mismatch
2 COM type mismatch error
3 COM name not found
4 Out of memory for COM operation
5 Name is a method
6 Name is a property
7 COM automation error
8 COM server unavailable
9 The COM server threw an exception

The exception method can get more information about the COM error which occurred by sending the message "getLastSCode" to OLEsup.

invoke OLEsup "getLastSCode" returning lsErrorCode 

where:

lsErrorCode PIC X(4) COMP-5

For information about COM error codes, see the Microsoft Platform SDK documentation.

The first piece of example code below is a short COBOL subroutine for handling COM exceptions, called COM-err1.cbl. The second piece wraps the subroutine in an EntryCallback, and registers it as an COM exception handler.

The exception handler:

 class-control.
     OLEsup is class "OLEsup"
     .
 working-storage section.
 01 wsOffset                 pic x(4) comp-5. 
 01 wsOLEException           pic x(4) comp-5.

 linkage section.
 01 lnkExceptionObject       object reference. 
 01 lnkExceptionNumber       pic x(4) comp-5. 
 01 lnkErrorText             object reference.
 procedure division using lnkExceptionObject
                          lnkExceptionNumber
                          lnkErrorText. 
     ... 
     invoke OLEsup "getBaseOLEException" returning wsOffset 
     subtract wsOffset from lnkExceptionNumber 
                     giving wsOLEException  *> COM exception 
     ...      
     exit program.

Registering the exception handler:

 class-control.
    ...
    EntryCallback is class "entrycll"
    OLEexpt is class "OLEexpt"
    ExceptionManager is class "exptnmgr"
          .

 working-storage section.
 ...
 01 exceptionHandler        object reference.
 ...
 procedure division.
 ...
*>   Set up exception COM exception handler
     invoke EntryCallBack "new" using z"exception-section"
                            returning exceptionHandler
     invoke ExceptionManager "register" using OLEexpt
                                              exceptionHandler
     ...

In the above example, you can optionally provide a third parameter, as shown below. This parameter will be an ordered collection containing a character array. The COM domain generates the exception via the raiseExceptionWithText method in the eBase base class library, and that creates an ordered collection as the third parameter when it invokes the exception handler routine.

01 lnkExceptionObject   object reference.
01 lnkExceptionNumber   pic x(4) comp-5.
01 lnkErrorText         object reference.

procedure division using lnkExceptionObject lnkExceptionNumber lnkErrorText.
...

Copyright © 2006 Micro Focus (IP) Ltd. All rights reserved.