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.

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
- Creating a COM proxy object
- Sending Messages to a COM Object
- Finalizing a COM Proxy Object
- COM Automation Exceptions
- Using Type Library Information
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:
- 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.
- 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}" . - 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.

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:
- 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" ... - 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 aHandlerAn EntryCallback looks like this:
invoke EntryCallback "new" using z"entryPointname" returning aHandler - 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.