Skip to main content
Previous section

Using Caché Activate

This chapter describes how to create a Caché wrapper class for an ActiveX component and how to use this wrapper class within an application.

The Caché Activate Wizard

The Caché Activate Wizard automatically creates one or more Caché wrapper classes for a given set of ActiveX interfaces.

To use the Wizard:

  1. Start Atelier.

  2. Select a project for your application.

  3. Select Tools > Add-Ins... from the main menu and press the Next button.

  4. Expand the item Standard Add-Ins and select Activate Wizard.

  5. Press the Finish button to start the Activate Wizard:

    generated description: actwiz1

    Enter the package name you wish to use for the generated classes, and press the Next button.

  6. The Wizard displays a list of available COM interfaces (these are interfaces available on the Caché server, not the machine on which Atelier is running):

    generated description: actwiz2

    Choose one or more interfaces and press the Next button.

  7. The Wizard automatically generates wrapper classes within the selected package and compiles them:

    generated description: actwiz3

Note:
64–bit ActiveX Controls

On a 64-bit version of Windows with a 64-bit cache, you can call 64-bit ActiveX controls. This will not enable a 64-bit cache to use 32-bit ActiveX controls (which is impossible due to operating system constraints). However, some companies are now releasing 64-bit versions of their existing 32-bit controls, allowing customers to migrate to 64-bit systems.

Using the Generated Wrapper Classes

The classes that are generated in Caché are proxy classes for the COM objects. Once the classes have been generated and compiled, you can then use them in Caché applications.

For example, using the Activate Wizard, you can generate wrapper classes for the Microsoft SysInfo Control, which provides some information regarding system resources.

The Caché Activate Wizard creates the following classes for the SysInfo COM object:

  • Activate.SysInfoLib.ISysInfo — An abstract interface class that defines the methods and properties which the ISysInfo interface provides. It cannot be instantiated.

    Among others it has a calculated property called BatteryLifePercent along with corresponding get and set methods for that property.

  • Activate.SysInfoLib.SysInfo — This is a concrete class that inherits from the ISysInfo class. It contains the code that finds and instantiates the external COM object and maintains a “connection” to that object. You use this concrete class to manipulate the external object. When the object is closed, the external COM object is closed (released) also.

Example: Accessing a Property

Here is an example that uses the SysInfo wrapper object to obtain the remaining battery life percentage for a laptop computer:

 Set obj = ##class(Activate.SysInfoLib.SysInfo).%New()
 Write obj.BatteryLifePercent,!
 Set obj = ""
Copy code to clipboard

The object is created in the same manner as any other within Caché. The BatteryLifePercent property is written out and finally the object is closed.

Example: Enumerating COM Interfaces

The Caché Activate Wizard enumerates the type libraries on a Caché Server by using a COM object called TL.dll (or TL64.dll on 64-bit systems. The file is placed in the <CacheRoot>\Bin directory and automatically registered during Caché installation). The Caché classes that are generated from this object are preloaded into the %Activate.TLLib package.

These classes consists of:

  • %Activate.TLLib.IUtils — an abstract interface class that has a single property, libraries of type ILibraries. Use this property to retrieve the ILibraries interface for enumerating the type libraries on the system.

  • %Activate.TLLib.ILibraries — an abstract interface class that exposes the Count and Item properties. Use these properties to enumerate the type libraries on the system.

  • %Activate.TLLib.Utils — a concrete subclass that expresses the IUtils interface. Instantiate this class to access the Libraries property

Here is an example ObjectScript method that enumerates the type libraries on the system by using these classes. A concrete instance of the Utils class is created and the objlibs property is retrieved. Notice that the Item property is called via the ItemGet method, because Caché does not currently support calculated, indexed properties:

Class MyApp.ActivateTest
{
// ...

/// Demonstrate COM object Access and provide type library enumeration  ;
ClassMethod ListTypeLibs() {
    Set objUtils = ""
    Set objLibs = ""
    Set $ZT = "tlerr"
    Set objUtils = ##class(%Activate.TLLib.Utils).%New()
    Set objLibs = objUtils.Libraries
    For i = 1:1:objLibs.Count {
       Set tld = objLibs.ItemGet(i)
       // tld is a | delimited string
       Write !, $Piece(tld,"|"), !, $Piece(tld,"|",2), !, $Piece(tld,"|",3), !!
    }

xit      ; Exit point          
    If objLibs'="" Set objLibs = ""
    If objUtils'="" Set objUtils = ""
    Quit

tlerr      ; Exception handler
    Set $ZT = ""
    Goto xit
}
}
Copy code to clipboard

Special Considerations for Properties

As shown in the previous example, in COM, some properties have parameters. Furthermore, some objects have what is known as a “default property,” which means you can reference that property without specifying its name explicitly.

For example, collections (as in the previous example) always have the Count and Item property. You will note that the Item property is (obviously) not a method but that it does take an argument. An Item property is often the default property of a collection. Consider an example with Microsoft Excel. If we have a collection of workbooks, then in Visual Basic, we can access a specific workbook by name in this manner:

Application.Workbooks("Sheet1")
Copy code to clipboard

Although we are accessing the Item called “Sheet1”, Item is not explicitly referenced. What the code is really doing is calling:

Application.Workbooks.Item("Sheet1")
Copy code to clipboard

Caché distinguishes between method call and property reference by the presence or absence of parentheses. This means that it interprets “person.Name” as a property and “person.RaiseSalary()” as a method. This makes default properties awkward because, unlike Visual Basic, Caché does not have the ability to define a default parameter nor the ability to do a property reference while passing parameters. For example, Caché cannot support the following Visual Basic syntax that has an implicit reference to an property:

Workbooks("Sheet1") ' Implicit reference to Item property
Copy code to clipboard

Neither can Caché support the following syntax, where Item is a property:

Workbooks.Item("Sheet1") ' Item is a property!
Copy code to clipboard

This does not work, because the Caché Interpreter considers Item to be a method. To work around this difference in the languages, use the following syntax:

Workbooks.ItemGet("Sheet1")
Copy code to clipboard

This works because ItemGet is the method that retrieves the Item property.

Exception Handling

Any COM object may raise an exception as the result of some operation, be it a method call or a property set/get. When an exception is raised, the exception is propagated into Caché via the ZTrap mechanism. The calling code will receive an error with the error code <ZACTX> and the local variable %objlasterror will contain a complete textual description of the error. Programmers should plan for this error and take action accordingly.

Example: Exception Handling

Here is an example of using a COM object which retrieves files by FTP. The object is created and the CurrentDirectory property is queried. The COM object throws an exception because it is not valid to try to determine the current directory until the FTP connection has been made. We will try this from a Caché command line (terminal session):

 Set obj = ##class(Activate.RETRIEVERLib.FtpRetriever).%New()
 Write obj.CurrentDirectory
Copy code to clipboard

In this case, this will throw an error:

<ZACTX>CurrentDirectoryGet+4^Activate.RETRIEVERLib.FtpRetriever.1
Copy code to clipboard

The error code associated with the <ZATCX> error should be in the local variable %objlasterror. We can retrieve the complete text of the error message by calling $system.OBJ.DisplayError:

 Do $system.OBJ.DisplayError(%objlasterror)
Copy code to clipboard

Which will result in the following output:

ERROR #1101: Com Exception: '-2147220888 Ftp Retriever Connection must
be established before attempting this operation'
Copy code to clipboard

%Activate.IDispatch and %Activate.GenericObject

Some COM objects do not come with a type library or you may find that the return type of a method or a property type of a COM object is just an IDispatch interface. How do you call methods and access properties for such objects?

Caché Activate provides two classes which assist with this problem, %Activate.IDispatch and %Activate.GenericObject.

Many COM objects are identified by what is called a “ProgId”, a string usually consisting of a library/object name which can be used to identify an object. In Visual Basic there is a CreateObject call which takes a Progid and returns an object reference which can be used to manipulate the object. Caché provides a CreateObject method too, as a class Mmethod of the %Activate.GenericObject class. Here is how it is used:

Example: Using CreateObject

Using the same Microsoft SysInfo object as above, we instantiate the object via its ProgId. Because the object is generic, that is, we have no type information for this object when instantiated in this manner, we must call the generic methods from the IDispatch interface which get and set properties and invoke methods by name:

 Set obj = ##class(%Activate.GenericObject).CreateObject("SYSINFO.SysInfo")
 Write obj.GetProperty("BatteryLifePercent")
 Set obj = ""
Copy code to clipboard

Monikers

COM provides an alternative way of instantiating an object indirectly by using what is known as a moniker as a substitute for the ProgId. Visual Basic provides the GetObject call which takes a moniker and returns an object reference which can be used to manipulate the object. Caché provides a GetObject method as a Class Method in the %Activate.GenericObject class. Here is how it is used:

Example: Using GetObject

Here a moniker that accesses the LDAP protocol of the Active Directory service. It is used to return a reference to a collection of nodes which represents users in the current domain. The count of users is written out and the object closed:

 Set obj = ##class(%Activate.GenericObject).GetObject("LDAP://CN=USERS")
 Write obj.Count()
 Set obj = ""
Copy code to clipboard

The Become Method

Sometimes a type library specifies a method or a property which has a return type of the generic IDispatch interface. This can be very inconvenient because what you get is, in effect, an instance of %Activate.IDispatch on which you are forced to use generic methods (such as GetProperty) in order to get and set properties and invoke methods. If you know the interface that it really should be (from documentation or otherwise), then you can call the Become method on an instance of %Activate.IDispatch object and retrieve the new (now typed) interface. The Become method takes the name of a class as its argument. Effectively, %Activate.IDispatch becomes an instance of the class name you pass to the method. Become will throw an exception if the object you call does not support the new typed interface.

Events

Some COM components have the ability to fire events during the processing of a method. The events are grouped into an event or “source” interface given a name. For example, given a COM object called MyClass, the interface may be called “MyClassEvents” or in the case of a COM object created with Visual Basic “__MyClass”.

Caché Activate provides for the event handling via two classes: %Activate.RegisterEvents and %Activate.HandleEvents. If a COM object generates events, the generated Caché class will inherit from the %Activate.RegisterEvents interface class. This adds two methods %RegisterHandler and %UnRegisterHandler. In addition to the regular COM object proxy class, another class is generated which represents the Event interface. This will inherit from %Activate.HandleEvents and implements the %Advise and %UnAdvise methods as well methods to handle specific events as defined by the event interface.

Example: Using COM Events

An example may make things clearer. Suppose we have a hypothetical COM object which does an FTP transfer. As well as implementing methods such as Connect, Close, and Download, the object implements an Event interface which expresses a single method, BytesTransferred. Following a successful connection and initiation of a download, the FTP object will fire the “BytesTransferred” Event after each 1 kilobyte of data that it has downloaded. The Event will be represented by a BytesTransferred method which has two parameters, an integer, Bytes and a boolean, Cancel which is passed by reference. When the Event fires, the BytesTransferred method will be called passing the current value of the arguments, Bytes and Cancel. These values are then available for processing. Typically the Bytes argument will be displayed via the user interface. Because the Cancel argument has been passed by reference, its value may be set and returned to the COM object which fired the event. In this instance setting Cancel to True (-1 for COM) will indicate to the COM object that the current operation should be interrupted and the call to Download should return immediately. If the download completes normally, the call to Download will return control to the caller and no more events will be fired. In Caché, the FTP COM object would be represented by a generated class such as Activate.SomeLibrary.FTP and the event interface by the class Activate.SomeLibrary.FTPEvents.

This example would look something like this. First an instance of the FTP object would be created:

 Set FTP = ##class(Activate.SomeLibrary.FTP).%New()
Copy code to clipboard

We want to handle events so we create an instance of an event handler:

 Set FTPHandler = ##class(Activate.SomeLibrary.FTPEvents).%New()
Copy code to clipboard

Before events can be handled the event handler must be registered with the object that actually fires the events, so we call:

 Do FTP.%RegisterHandler(FTPHandler)
Copy code to clipboard

Now we connect and do a download:

 Do FTP.Connect("ftp.intersys.com")
 Do FTP.Download("/public/somefile.txt")
Copy code to clipboard

During the download the following method would be called on the Activate.SomeLibrary.FTPEvents class:

Class MyApp.Test
{
//...

Method BytesTransferred(Bytes As %Integer,Cancel As %Boolean)
{
    //...
}
}
Copy code to clipboard
Note:

It is up the developer to actually implement the BytesTransferred method by editing the Activate.SomeLibrary.FTPEvents class directly or preferably by subclassing the class and providing the implementation in the subclass.

Following the download, we do not want events to be handled anymore so we unregister the handler:

 Do FTP.%UnRegisterHandler(FTPHandler)
Copy code to clipboard

and tidy up:

 Set FTPHandler = ""
 Set FTP = ""
Copy code to clipboard