OLE Interface

The index for the Allegro CL Documentation is in index.htm. The documentation is described in introduction.htm.

This document contains the following sections:

1.0 Introduction
2.0 Sample Programs
3.0 System Structure
   3.1 Basic Concepts
   3.2 Important CLOS Issues for an ACL OLE Application
   3.3 Special OLE Data Types
   3.4 Language and Locale
   3.5 Interfaces and Objects
   3.6 The Registry
4.0 Writing an Automation Client
   4.1 Dynamic naming
   4.2 Unique ids
   4.3 Classes
   4.4 The Lisp remote-autotool Class
   4.5 Set-up
5.0 Writing a Server
6.0 Defining Interfaces
7.0 Low Level View
   7.1 Class Hierarchies
   7.2 Control Flow

1.0 Introduction

The facility described in this document simplifies OLE programming without imposing any limit on the programmer's access to OLE functionality. To a CLOS program, Microsoft's COM/OLE/ActiveX facilities look like a foreign library consisting of data types, named API entries, and interfaces, the latter being C++-like objects with associated virtual tables. Allegro's OLE support provides tools for dealing with these foreign entities. Every API point is reachable, every interface can be used. The support also includes a library of CLOS classes and functions that make it easier to manage an application's OLE component in a CLOS development environment. This CLOS-OLE layer is not yet complete as we are continuing to extend it. All the most useful OLE capabilities will be as readily available in CLOS as in any other environment, and will benefit from the unique dynamic power inherent in CLOS.

This file contains an overview of ACL OLE 's treatment of OLE concepts. It lays out the overall organization of the ACL OLE tools and an ACL OLE application. Detailed documentation of each element in the ACL OLE system appears in the reference document, ole_reference.htm. Look there for information about individual CLOS functions, macros, classes and data types.

2.0 Sample Programs

You will find a set of sample ACL OLE programs to illustrate writing client and server applications using ACL OLE. Each sample appears in a directory ole/samples/samplenn, and includes a readme.txt file explaining how to compile and run the example.

3.0 System Structure

3.1 Basic Concepts

Basic Unit
of OLE
Treatment in ACL OLE
Data TypesMost of OLE's primitive data structures are defined and manipulated using the Allegro foreign data interface. A few receive special treatment. See the entries for Unicode, GUID's, BSTR's, and Interface Pointers.
Interface DefinitionAn OLE Interface definition specifies the methods of an interface, giving their order in the VTBL and the arguments and return-value type for each. An interface is seen from two sides, client and server. On the client side, an interface allows the program to invoke its methods without knowing how they are implemented. The method implementations exist on the the server side. ACL OLE provides separate macros to
  • define an interface: def-ole-interface
  • generate client-side linkage to an interface: def-client-interface
  • generate server-side linkage to an interface: def-server-interface
Reference CountingOLE uses a reference counting scheme to allow it to do garbage collection on OLE resources. Every OLE interface inherits from the IUnknown interface, which provides AddRef and Release methods to record object usage. ACL OLE reflects these OLE methods as the CLOS generic functions add-ref and release.

Functions in the ACL OLE subsystem make add-ref and release calls at the appropriate times. Allegro garbage collection finalizations perform a release call on each client-interface object that dies without already having been released.

Starting an OLE SessionBefore using any OLE facilities, an application has to call certain OLE API functions to initialize state. ACL OLE provides start-ole and stop-ole, functions that perform all necessary initialization calls and disconnect from OLE, respectively.

3.2 Important CLOS Issues for an ACL OLE Application

3.3 Special OLE Data Types

ACL OLE gives these OLE data types special treatment.

3.4 Language and Locale

Many OLE API functions use codes to allow localization of labels and user-readable data. The ACL OLE interface uses two parameters to provide default values for these codes: ole:*ole-language* and ole:*ole-locale*. Currently, these are set to the machine-dependent-default values that are OLE's lowest-common-denominator.

3.5 Interfaces and Objects

ACL OLE distinguishes between those interfaces that lisp implements as a server and those that lisp uses as a client. CLOS objects represent interfaces, and different CLOS classes exist for the client and server views of the same interface. This is important because we often have to deal with an interface from both sides in the same program. While developer-defined interface classes can be given any names, the ACL OLE classes are named using the following convention: ACL OLE supports OLE interface IAbcde with client-side interfaces of class IAbcde-client and server-side interfaces of class IAbcde-server.

Example: When an ACL OLE application obtains an IUnknown interface from some external object, it will be of type IUnknown-client. An ACL OLE server application will generate IUnknown-server objects in response to requests for the IUnknown interface.

An OLE object may reveal any number of interfaces to the outside world. The interfaces and the object are distinct entities, and in an ACL OLE application these will be represented by instances of different CLOS classes.

The ACL OLE object on the server side will be an instance of a class that inherits from the lisp-ole-object class and from several mixin classes, one for each OLE Interface the object supports. These mixin classes are named by the OLE interface name, e.g., IClassFactory or IOleObject. The object itself is completely under the server's control; only the interfaces are exported to the rest of the world. As a server, the CLOS application must implement the methods of these interfaces.

A developer will often do this by using def-ocx-class to define the object class, specifying the interface mixins. Here, for example, is the definition for ACL OLE's class-factory class .

(ole:def-ocx-class class-factory (:interfaces IClassFactory)
   ((registration-code :initform nil)
    (locked :initform nil)
    (children :initform nil)
    (product-class :initarg :product-class)
    (allow-aggregation :initform nil :initarg :allow-aggregation)
    ))

Here we are saying that a class-factory has the usual semantics for an OLE object implemented in lisp, and that it exports two interfaces: IUnknown (supported by default) and IClassFactory. These interfaces have been defined previously with def-ole-interface and are implemented by IUnknown-server and IClassFactory-server instances, respectively. This class definition form arranges that class-factory objects will respond to QueryInterface requests for the IUnknown and IClassFactory interfaces, constructing and caching each interface the first time it is needed.

3.6 The Registry

The registry is where most system information is kept in Windows. An ole server must store information about itself in the registry if it wants to be invoked automatically or allow certain automation clients (such as Visual Basic) to create its objects. This section will describe the registry and how it is manipulated from Lisp.

The registry is stored as a tree. Each node in the tree is called a key. Each key has a name, a collection of zero or more values, and a set of zero or more child keys. Each value stored in a key is also named (except for one value, which has no name, and is called the default value). The name of a registry key must consist of printable characters and no spaces.

Manipulating the registry from lisp consists of first getting a pointer to the particular key you want to modify. This is done by starting with an existing open registry key and traversing down the tree by using the names of successive keys that should be followed. Since you have to start somewhere in this process you can use one of the pre-opened registry keys to begin the registry scan. These pre-opened keys are

The following functions and macros are provided to open keys and to read and modify the registry. See the reference document for their definitions.

4.0 Writing an Automation Client

Automation allows one application to communicate with and control another. The server application offers a set of objects to control. The client application controls the objects offered by the server. The server can be a standalone application (often called an 'exe' or local server) or it can be a so-called in-proc server, i.e., one that is implemented in a dll that is loaded into the client application. The client need not know which method is being used, but it can set limits, refusing to use a local server, for example.

An automation object has a set of properties and a set of methods. Properties can be read or set (although the object server can ignore an attempt to set a property the server considers read-only). Methods can be called on an object and a value returned from the method. Properties and methods are named. The name is a case-insensitive string; ACL OLE functions to access them can use strings or symbols.

A property and a method can have the same name because when that name is used it is clear from the use whether the property or method is intended.

4.1 Dynamic naming

Automation is similar to lisp itself in that the binding of name to property or method is done at runtime. Some automation objects support early-binding of names to properties and methods using a type library. The lisp-ole interface does not support this yet.

4.2 Unique ids

In order for applications to talk about classes and interfaces and to be sure that they are talking about the same ones, they use GUIDs. You'll often see a guid written this way

{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

where the x's are hex digits. In Lisp we represent guids as lisp-guid structures and provide functions for converting between any of the common guid representations.

There is a Microsoft program to generate unique ids that are guaranteed to be (almost-but-not-quite certainly) distinct from any other on Earth for all time. You'll want to use this program if you plan on distributing your automation server. For just experimenting you can choose any random sequence of digits and chances are it will be different than anything else on your machine.

4.3 Classes

An OLE class describes a collection of objects. Each COM/OLE class has a unique guid. The class is the key to getting communication started between the client and the server. The client initiates the communication by asking for a pointer to a class object's class factory. Once the class factory is returned, the client can ask the factory to create one or more automation objects.

4.4 The Lisp remote-autotool Class

ACL OLE defines the ole:remote-autotool class to facilitate control of automation objects. An instance of ole:remote-autotool holds the Ole machinery that communicates with the server application.

4.5 Set-up

To establish a connection to an automation object you can use the function ask-for-autotool.

(setq autoinstance
  (ole:ask-for-autotool
    (ole:unique-guid "{dbce6200-e0a3-11cf-b565-00aa0064595a}")
    ole:CLSCTX_INPROC_SERVER))

is the form to use if you know the OLE class id. If the application that supports the class is registered, you can bypass the class id and use the registered application name:

(setq autoinstance 
      (ole:ask-for-autotool "MSCAL.Calendar" ole:CLSCTX_INPROC_SERVER))

If you expect to create more than one instance of the same object type then it's more efficient to ask for the class factory and then ask the factory for the specific objects you want to create.

(setq factory
      (ole:get-class-object
         "{dbce6200-e0a3-11cf-b565-00aa0064595a}"
         ole:CLSCTX_LOCAL_SERVER
         ole:IID_IClassFactory))
(setq autoinstance (ole:ask-for-autotool factory))

If xdi is an IDispatch-client interface for an object that supports automation, you can build an autotool object for it like this:

(setq autoinstance 
      (make-instance 'ole:remote-autotool :dispatch xdi))

Whichever route you take to acquire the remote-autotool instance, you can then use the functions auto-getf, (setf auto-getf), and auto-method to read properties, set properties, and call functions on the automation object.

For example,

(ole:auto-getf autoinstance :x)

will ask for the value of the x property of the automation object.

(setf (ole:auto-getf autoinstance :x) 555)

will set the x property value to 555.

(ole:auto-method autoinstance :foob 3 4)

will call the foob method on the automation object, passing in two extra arguments, 3 and 4.

When you've completed using the remote object do (ole:release autoinstance) to free it on the server side. After the call to ole:release, don't use the autoinstance object again since it no longer refers to an object on the server.

5.0 Writing a Server

Writing an automation server is simplified by using the ACL OLE automation and factory interfaces. See ole/samples/sample04/server.cl for an example.

Suppose you, a lisp programmer, have some functionality you want to offer to clients via OLE/COM.

First figure out which interfaces you want to support in this object. You don't have to declare them all immediately, you can add more later on. You must support IUnknown at least. Each interface must be defined in your application. Many interfaces are already defined. Check the ole/defifc directory to see which. If there's already a definition, you probably want to use it. If the interface doesn't appear in ole/defifc, then you will need to provide a definition using def-ole-interface and def-server-interface. You can put the definitions in your application source code if the interface is unique to that application. Alternatively, you can add it to the ACL OLE library if you expect to use it in more than one application.

Next you define a CLOS class whose instances will represent objects allocated on behalf of the client. This CLOS class should be defined with def-ocx-class or be a subclass of such a class. The def-ocx-class form will name each interface that this object will export to clients, except possibly IUnknown, which gets put in automatically if you don't name it. The interface names are symbols like IStorage, IOleObject, etc. Example:

(ole:def-ocx-class my-class (:interfaces IFoo) ....)

Put in all the interfaces you want to support after the :interfaces keyword. If you want to support two different interfaces with the same interface object, where one is based on the other, include a list of the related interfaces as one of the entries after the :interfaces keyword, as in

(ole:def-ocx-class my-class (base1 :interfaces
                                   (IViewObject IViewObject2)
                                   IOleObject)
  ((local-slot ..)
     ...))

Here, a request for either IViewObject or IViewObject2 will be satisfied with the same object, which will be of type IViewObject2-server. The last named interface in a set is the one that is used for any of them.

With the CLOS class my-class defined you must now make sure that the methods for each exported interface are defined over this type of data object. Suppose your class supports the IFoo interface, an interface that has four methods: the three from IUnknown, plus the method 'addem' that adds its two integer arguments together and returns an integer result. The interface definition might look like this:

(def-ole-interface IFoo
   (:iid "{12345678-1234-5678-1234-123456781234}")
   (addem (in.arg1 integer) (in.arg2 integer)))

(def-server-interface IFoo)

When a client allocates an object of your class and gets a pointer to the IFoo interface and then calls addem, control will eventually reach your server. When that happens, the generic function addem will be called with three arguments: the first argument is the CLOS object of your class that the client has remotely allocated, and the other two arguments are the integers to add. You could thus write your server method in this way:

(defmethod addem ((obj my-class) x y) (+ x y))

In this particular case, as in most cases, the function of the addem method in the IFoo interface doesn't depend on the object itself, so we might just want to write that method for all classes that export that interface:

(defmethod addem ((obj IFoo) x y) (+ x y))

this works because all classes that export the IFoo interface are a subclass of IFoo. Or we could write it for the interface object itself and not bother to look for an object-specific function.

(defmethod addem ((ifc IFoo-server) x y) (+ x y))

ACL OLE uses this ability to write methods over an interface to define the three methods inherited from IUnknown: add-ref, release, and query-interface. Thus the server class writer generally doesn't have to worry about writing these methods. (And in fact, should not replace the primary methods, ever. :before, :after, and :around methods are OK.)

6.0 Defining Interfaces

The def-ole-interface macro defines each interface. With it you name the interface (without the "-server" or "-client" as that is added by def-server-interface and def-client-interface). The def-ole-interface macro allows you to specify the IID and the methods for the interface, possibly specifying a base interface to inherit methods from. You list the methods by name and give an argument map (name and type) for each argument of each method. The macro expands into code that creates a structure containing all this information. The argument-type encoding is the one used by ff:def-foreign-type. An older form, approximating what appears in the C header files, is also accepted, but is deprecated.

With def-server-interface you specify the interface you're generating linkage code for, as in

(def-server-interface IFoo)

The result is to define two classes: IFoo-server is the server interface class. IFoo is the mixin class for all objects that support the IFoo interface.

It may be confusing to have two classes representing an interface on the server side: IFoo-server and IFoo. The difference between the classes is this: Instances of IFoo-server are interface objects, which have relatively simple and unchanging functionality. These methods are called first when a client call comes in so you might want to write methods that do argument transformation before passing the call to the object-specific code.

An object could reasonably be both IViewObject and IOleObject. That would mean that it made both types of interfaces available to clients and responded appropriately to methods on either interface. However, an IOleObject-server object is definitely *not* an IViewObject-server object. They have completely different vtables attached to their proxies.

Thus if you want to write methods over your server objects that depend on them having an IFoo interface, then you write those methods over IFoo. As an example, the IUnknown reference counting methods would be best written over IUnknown, since all objects that support an IUnknown interface will inherit from IUnknown.

7.0 Low Level View

7.1 Class Hierarchies

The mixin class hierarchy for the IUnknown interface:

ole:IUnknown ole::interface-mixin

The mixin class hierarchy for a random IFoo interface:

IFoo ole:IUnknown ole::interface-mixin

The server class hierarchy for a random interface IFoo is

IFoo-server ole:IUnknown-server ole::lisp-ole-interface

The application class hierarchy for a random class my-class supporting the IUnknown and IFoo interfaces is

my-class ole::lisp-ole-object IFoo ole:IUnknown

7.2 Control Flow

Now we'll look at what happens when you define a class like

(ole:def-ocx-class my-class (:interfaces IFoo) ....)

In this case my-class inherits from IFoo explicitly and from lisp-ole-object and IUnknown implicitly. The lisp-ole-object class contributes two instance slots:

ref-count
interfaces

The ref-count is used to count the number of users of this object; we don't track uses by each interface separately. The interfaces slot holds data that controls the allocation and caching of the interface objects for this instance of my-class. The data is built and stored in this slot automatically through some :around method magic; newly allocated interface objects are cached for later reuse.

When an instance of my-class is polled with QueryInterface, an interface object is either found or is built and cached. Each interface object has two slots

The owner slot points to the instance of my-class that has this interface object on its interfaces list. The handle slot points to a proxy object. A proxy object is a :c foreign array made to look like a C++ (COM/OLE) object whose first slot points to a vtbl of functions for this interface. This proxy object is created when first needed. The second slot of the proxy contains information that helps us quickly find the associated lisp interface object when a method call comes in from the outside world.

Here is how it all works: When a client makes a call on an interface method, and control reaches the lisp server, lisp is passed the address of the proxy object as the first argument (this is the C++ 'this' pointer). Lisp then can use some internal machinery to locate the interface object, and calls the generic function associated with that method, usually a function with the same name as the OLE interface method. The generic function's arguments are the instance of the interface object associated with the proxy object, and the rest of the method arguments. If the interface was created in the usual way (with def-server-interface), then a method was automatically written that specializes on the first argument being an instance of this interface, and that method calls the same generic function, this time with the first argument being the instance of my-class found in the interface object's owner slot.

Here's an example using our IFoo interface with its addem(int x, int y) function. When the client calls lpFoo->DoSomething (3, 4) control reaches our (automatically generated) defun-c-callable function

vtbl.addem(proxy_address, 3, 4)

this function finds the interface object, an IFoo-server, from the proxy_address and calls the addem generic function:

(addem interface-obj 3 4)

This in turn invokes the specialized method

(addem (obj IFoo-server) x y)

which is automatically defined to do (slot-value obj 'owner) to find the instance with this interface and call the generic function

(addem owner-obj 3 4)

This selects the specialized method

(addem (obj my-class) x y)

That method, defined explicitly in the application code, computes and returns a value, which is then returned to the client.

Copyright (c) 1998-2000, Franz Inc. Berkeley, CA., USA. All rights reserved. Created 2000.10.5.