ToCDocOverviewCGDocRelNotesFAQIndexPermutedIndex
Allegro CL version 10.1
Unrevised from 10.0 to 10.1.
10.0 version

OLE Interface

This document contains the following sections:

1.0 Introduction to OLE support
2.0 Introduction to high-level OLE/OCX interface
   2.1 OCX objects and Allegro CL
   2.2 Type Libraries
   2.3 Typelib entity: CoClass
   2.4 Typelib entity: Enum
   2.5 Typelib entity: Interface
   2.6 OLE/OCX low-level internals discussion
   2.7 Reference guide to interface operators
   2.8 Index to high-level interface operators
3.0 Introduction to the low-level OLE interface
   3.1 Sample Programs
   3.2 System Structure
      3.2.1 Basic Concepts
      3.2.2 Important CLOS Issues for an Allegro CL OLE Application
      3.2.3 Special OLE Data Types
      3.2.4 Language and Locale
      3.2.5 Interfaces and Objects
      3.2.6 The Registry
   3.3 Writing an Automation Client
      3.3.1 Dynamic naming
      3.3.2 Unique ids
      3.3.3 Classes
      3.3.4 The Lisp remote-autotool Class
      3.3.5 Set-up
   3.4 Writing a Server
   3.5 Defining Interfaces
   3.6 Low Level View
      3.6.1 Class Hierarchies
      3.6.2 Control Flow


1.0 Introduction to OLE support

The high-level OLE/OCX interface, introduced in 2005, is described in Section 2.0 Introduction to high-level OLE/OCX interface.

For some time, Allegro CL has supported a low-level OLE interface. This is still supported and described in Section 3.0 Introduction to the low-level OLE interface.



2.0 Introduction to high-level OLE/OCX interface

This documentation for the OLE/OCX interface is preliminary. It will be updated from time to time.

The high-level OLE/OCX interface is implemented in a number of modules. The primary module is the :ole-dev module. The remaining modules are loaded automatically at the first call to the macro ole:def-ole-linkage.

This document describes the interface. The related document, ole_reference.htm, lists all the operators.

To start using the interface, then, evaluate the following require form:

(require :ole-dev)

And then make a call to ole:def-ole-linkage.

You might need ole/client/autotool.fasl. This form in a file will load that file:

(eval-when (load eval)
  (require :ole-autotool #p"sys:;ole;client;autotool.fasl"))

Symbols naming OLE functionality are in the :ole package.


2.1 OCX objects and Allegro CL

OCX objects are entities that provide services through OLE interfaces. They may be implemented in any language, and may be instantiated in the address space of the client, via loaded DLL's, or in separate address spaces as local or remote processes. A Lisp program can request the services of any number of OCX objects. A Lisp program can also implement OCX objects, but this document only addresses the use of externally implemented OCX objects. A forthcoming separate document discusses OCX controls used as widgets on an IDE form.

To use an OCX object, the Lisp application must request its creation and receive one or more interfaces to the object. The application can then invoke the object's methods, inspect and change its properties, and eventually release the object so that it can be destroyed. In order to code the method calls and property references, the programmer must know what methods and properties the object supports. To perform the actual invocations, Allegro CL must know the interface and object definitions. The programmer may be able to get the information he or she needs by reading documentation; Allegro CL gets the definitions from a type library supplied with the object.


2.2 Type Libraries

Type libraries, also called typelibs, are compiled representations of IDL files. IDL stands for Interface Definition Language; it is the C-like language Microsoft uses to describe the collections of interfaces supported by an OCX object. A typelib is sometimes provided directly in file format, often in a file with a .tlb extension; type libraries may or may not be recorded in the registry.

To have Allegro CL learn the definitions in a type library, we need to supply a pathname to the typelib file or else provide a registry key leading to the typelib. The basic function to read a type library is descibed below (see ole:load-typelib). The ole:load-typelib function does not itself define the foreign function linkage needed to activate and control the objects from Lisp. The ole:def-ole-linkage macro, also described below, invokes ole:load-typelib and then processes the resulting information to define Lisp classes, constants, and methods, giving the programmer convenient access to the objects.

The type library provides Allegro CL with definitions of structures (structs), symbolic constants (enums), interfaces, and objects (CoClasses). The ole:def-ole-linkage macro expands into a form that defines corresponding foreign structures, Lisp constants, classes, functions, and methods. These different components of the lisp-to-object linkage are named by symbols, and this introduces a problem. A name that appears as a constant or method in a type library may duplicate the name of some other entity in a different library, or the name of some existing common Lisp function. ole:def-ole-linkage resolves this by interning all names for typelib entities in a special package, a package that is normally specific to that type library or to OLE interfaces, and which normally uses no other packages. As a result, a method named delete would be associated with a delete symbol that was different from the Common Lisp symbol of the same name. Section Section 2.3 Typelib entity: CoClass will describe the various pieces of Lisp linkage machinery constructed to correspond to each item in the typelib. The following examples are taken from Microsoft's Internet Explorer, and assume the following form was used:

(ole:def-ole-linkage #:msx :application "InternetExplorer")

The meaning of each argument in this macro form is described below (see ole:def-ole-linkage).

The ole:def-ole-linkage macro expands into a form that defines the Lisp connection to the OCX object. As a result, the compile-time support for ole:def-ole-linkage, which reads and analyzes the type library, is only needed when the form is compiled or interpreted. Once a file containing ole:def-ole-linkage forms has been compiled, the resulting fasl file can be loaded later into a Lisp image that doesn't include the machinery for analyzing typelibs or compiling interface definitions.


2.3 Typelib entity: CoClass

The objects an application will manipulate are named and defined by the CoClass entries in the typelib; a typelib may define several CoClasses or just one. For our example we'll look at this CoClass definition:

    [
      uuid(0002DF01-0000-0000-C000-000000000046),
      helpstring("Internet Explorer Application."),
      helpcontext(0x00000000)
    ]
    coclass InternetExplorer {
        [default] interface IWebBrowser2;
        interface IWebBrowserApp;
        [default, source] dispinterface DWebBrowserEvents2;
        [source] dispinterface DWebBrowserEvents;
    };

This object supports two control interfaces and two source (or event) interfaces. The control interfaces are IWebBrowser2 and IWebBrowserApp; the event interfaces are DWebBrowserEvents and DWebBrowserEvents2. The ole:def-ole-linkage form defines a class msx:InternetExplorer which inherits from an internal ole::access-to-ole-control class, and whose instances will be the Lisp objects corresponding to the CoClass objects. An instance can be created like this:

(setq browser (make-instance 'msx:InternetExplorer))

All Lisp classes corresponding to OLE coclasses support these initialization arguments to make-instance:

Making an instance of a coclass class does not automatically get the OCX object created. That's done by ole:connect-to-server.

The next form will make the connection, once the ocx object has been created as above:

(ole:connect-to-server browser :inplace nil)

This will start a browser running, but it won't be visible because this particular browser starts up in hidden mode. It can be made visible by setting the appropriate property, as described below.

The ole:def-ole-linkage macro creates Lisp methods corresponding to the methods and property functions defined for the CoClass's interfaces. For each method mmmm or property-getter gggg or property-setter ssss of interface IIII, ole:def-ole-linkage defines a corresponding method for msx:mmmm, msx:gggg or (setf msx:ssss). In our example, InternetExplorer objects support by default the interface IWebBrowser2, which inherits from IWebBrowserApp. Whichever interface is used for a particular InternetExplorer instance, all the IWebBrowserApp methods will be available. For example, consider these methods from the idl for IWebBrowserApp:

   [id(0x00000192), propget,
    helpstring("Determines whether the application is visible or hidden."),
    helpcontext(0x00000000)]
   HRESULT Visible([out, retval] VARIANT_BOOL* pBool);

   [id(0x00000192), propput,
       helpstring("Determines whether the application is visible or hidden."),
       helpcontext(0x00000000)]
   HRESULT Visible([in] VARIANT_BOOL pBool);

ole:def-ole-linkage generates methods that look something like this:

(defmethod msx:Visible ((obj msx:InternetExplorer))
  ...)

and

(defmethod (setf msx:Visible) (val (obj msx:InternetExplorer))
  ...)

that invoke the appropriate OLE methods on obj's IWebBrowserApp interface.

The defined methods call internal functions ole.control::IWebBrowserApp.Visible, and (setf ole.control::IWebBrowserApp.Visible), also defined by the ole:def-ole-linkage form. These lower-level interface-specific functions use the ole.control:IWebBrowserApp function to get the appropriate client interface. Examples appear below in the section on interfaces.

For each control interface IIII the CoClass supports, ole:def-ole-linkage defines a method ole.control:IIII to return the IIII-client interface for that object. If x is an instance of msx:InternetExplorer, (ole.control:IWebBrowserApp x) will return an IWebBrowserApp-client interface on which low-level method calls can be made. Since IWebBrowser2 inherits from IWebBrowserApp, the interface returned by ole.control:IWebBrowserApp might actually be a more specific IWebBrowser2 interface, but either works for the IWebBrowserApp methods.

The application code doesn't normally need to use either the ole.control:IWebBrowserApp function or the low-level method calls because the higher-level functions in the msx package are more convenient.

In our example, after connecting the browser object to an external web browser, nothing was seen because this particular control starts up hidden. Evaluating (msx:Visible browser) returns nil, and evaluating (setf (msx:Visible browser) t) makes the browser window visible. (msx:Navigate2 browser "franz.com") makes the browser object display the Franz home page.

When the typelib defines a method that returns a coclass's control interface, the generated Lisp method returns an instance of the Lisp object class whose control interface is the returned value and whose interface-type is the corresponding type. This object does not have an event channel opened automatically. If the application wishes to receive events for any such object, it needs to explicitly call the function ole:connect-event-channel, passing the new object as the argument.

A technical note important in multiprocessing applications: The control methods defined in Lisp perform heap-dropping calls. There is more overhead in a heap-dropping call, but since a method call to a visible control object might result in messages being dispatched to windows owned by other Lisp threads, we have to allow them a chance to run or risk hanging our application.

Besides the interface-specific methods defined for msx:InternetExplorer objects, there are some methods applicable to all instances of access-to-ole-control subclasses. These include ole:connect-to-server, ole:disconnect, and the setf-able ole:event-tracing.

If x is an instance of msx:InternetExplorer, (ole:connect-to-server x :inplace nil) will ask OLE to create a new running instance of an InternetExplorer object. If the attempt is successful then the current dumplisp tick (a value that records how many time an image has been dumplisp'ed since first creation) is recorded in the Lisp object along with connection information. ole:connect-to-server will not try to establish a new connection if x looks like it is connected and its dumplisp tick matches the current Lisp dumplisp tick. A live connection doesn't survive a dumplisp restore, so this allows application initialization code to call ole:connect-to-server on a set of instantiated Lisp objects either before or after a dumplisp.

Because the InternetExplorer CoClass defines an event channel, ole:connect-to-server also tries to create and initialize an event channel for the object. To see unhandled events being generated by an object x,

(setf (ole:event-tracing x) t)

This will make each event received on one of x's event channels produce a message.

(setf (ole:event-tracing x) nil)

will muffle the event messages for x.

Events can be handled by defining methods for them. Each event named xxxx is associated with a generic function named -xxxx (prefix a minus sign to the method name). This generic function takes as arguments the object, the channel, and the arguments defined in the typelib. For example, this definition in the typelib for one of DWebBrowser2's events:

[id(0x00000066), helpstring("Statusbar text changed."), helpcontext(0x00000000)]
    void StatusTextChange([in] BSTR Text);

says we can define a method like this:

(defmethod msx:-StatusTextChange ((obj msx:InternetExplorer) channel text)
   (declare (ignore channel))
   (format *trace-output* "Statustextchange: ~s~%" text))

to get our own printout when the event is received.


2.4 Typelib entity: Enum

For each value in an enum, ole:def-ole-linkage generates a corresponding defconstant form. ole:def-ole-linkage also generates a function to convert the numeric values into the corresponding symbolic names. The Internet Explorer type library contains this enum:

    typedef [uuid(34A226E0-DF30-11CF-89A9-00A0C9054129),
     helpstring("Constants for WebBrowser CommandStateChange"),
     helpcontext(0x00000000)]
    enum {
        CSC_UPDATECOMMANDS = -1,
        CSC_NAVIGATEFORWARD = 1,
        CSC_NAVIGATEBACK = 2
    } CommandStateChangeConstants;

The def-ole-interface form produces the corresponding definitions:

       (defun msx:decode-CommandStateChangeConstants (ole::v)
         (case ole::v
           (2 'msx:CSC_NAVIGATEBACK)
           (1 'msx:CSC_NAVIGATEFORWARD)
           (-1 'msx:CSC_UPDATECOMMANDS)
           (t ole::v)))
       (defconstant msx:CSC_NAVIGATEBACK 2)
       (defconstant msx:CSC_NAVIGATEFORWARD 1)
       (defconstant msx:CSC_UPDATECOMMANDS -1)

2.5 Typelib entity: Interface

For each interface defined in a typelib, ole:def-ole-linkage generates a corresponding OLE def-ole-interface form and some additional method definitions at a higher level. The generated definitions depend on whether the interface is used by the client to call methods in the object or by the object to call methods in Lisp. The former we refer to as a control interface, the latter as an event interface. The exact definitions are not normally of interest to the application programmer, who uses the higher-level functions acting on the CoClass object.


2.6 OLE/OCX low-level internals discussion

Interfaces

The IWebBrowser2 control interface in the InternetExplorer typelib looks like this (many methods omitted to condense the example):

    [
      odl,
      uuid(D30C1661-CDAF-11D0-8A3E-00C04FC9E26E),
      helpstring("Web Browser Interface for IE4."),
      helpcontext(0x00000000),
      hidden,
      dual,
      oleautomation
    ]
    interface IWebBrowser2 : IWebBrowserApp {
        [id(0x000001f4), helpstring("Navigates to a URL or file or pidl."),
         helpcontext(0x00000000)]
        HRESULT Navigate2(
                        [in] VARIANT* URL, 
                        [in, optional] VARIANT* Flags, 
                        [in, optional] VARIANT* TargetFrameName, 
                        [in, optional] VARIANT* PostData, 
                        [in, optional] VARIANT* Headers);
	... ommitted methods ...
    };

These are the corresponding forms generated by ole:def-ole-linkage:

   ;;----- An ole interface definition

   (ole:def-ole-interface ole.control:IWebBrowserApp
     (:base ole.control:IWebBrowser)
     (:iid "{0002df05-0000-0000-c000-000000000046}"))

   ;;----- The client-side machinery for that interface

   (ole:def-client-interface ole.control:IWebBrowserApp)

   ;;----- A class to mix in to ocx objects that support the interface

   (defclass ole.control::object-with-IWebBrowserApp-client
	     (ole.control::object-with-IWebBrowser-client)
	     ((ole.control::i.IWebBrowserApp :initform nil)))

   ;;----- A method definition to get the appropriate interface when needed,
   ;;      caching the interface for efficiency

   (defmethod ole.control:IWebBrowserApp ((ole::object
					   ole.control::object-with-IWebBrowserApp-client))
     (or (slot-value ole::object 'ole.control::i.IWebBrowserApp)
	 (setf (slot-value ole::object
			   'ole.control::i.IWebBrowserApp)
	       (ole:query-interface (ole::client-interface
				     ole::object)
				    ole.control::IID_IWebBrowserApp))))

   ;;----- A method to return this interface when the less-specific interface
   ;;      is requested

   (defmethod ole.control:IWebBrowser ((ole::object
					ole.control::object-with-IWebBrowserApp-client))
     (or (slot-value ole::object 'ole.control::i.IWebBrowserApp)
	 (setf (slot-value ole::object
			   'ole.control::i.IWebBrowserApp)
	       (ole:query-interface (ole::client-interface
				     ole::object)
				    ole.control::IID_IWebBrowserApp))))

   ;;----- A low-level Lisp method corresponding to the CoClass method

   (defmethod ole.control::IWebBrowser2.Navigate2
	      ((ole::this.object ole.control::object-with-IWebBrowser2-client)
	       ole.control::URL
	       &key
	       (ole.control::Flags :missing)
	       (ole.control::TargetFrameName :missing)
	       (ole.control::PostData :missing)
	       (ole.control::Headers :missing))
     (ole::control.invoke.5-
       (ole.control:IWebBrowser2 ole::this.object) 500
       (logior ole:DISPATCH_METHOD ole:DISPATCH_PROPERTYGET)
       ole.control::URL ole.control::Flags
       ole.control::TargetFrameName ole.control::PostData
       ole.control::Headers))

One of the event interfaces in the typelib is this (again edited for a concise example):

 [
   uuid(20DD1B9D-87C4-11D1-8BE3-0000F8754DA1),
   helpcontext(0x00000000),
   hidden
 ]
 dispinterface DDTPickerEvents {
     properties:
     methods:
	 [id(0x00000001), helpstring("Occurs when the user presses a key ..."),
	   helpcontext(0x00035c66)]

	 void CallbackKeyDown(
			 [in] short KeyCode, 
			 [in] short Shift, 
			 [in] BSTR CallbackField, 
			 [in, out] DATE* CallbackDate);

	 [id(0x00000002), helpstring("Occurs when the user selects ..."),
	   helpcontext(0x00035c67)]

	 void Change();

     ... several more methods omitted here	

 };

The corresponding definitions from ole:def-ole-linkage are:

 ;;----- An ole interface definition

 (ole:def-ole-interface ole.control:DDTPickerEvents
   (:base ole:IDispatch)
   (:iid "{20dd1b9d-87c4-11d1-8be3-0000f8754da1}"))

 ;;----- The serverd-side machinery for that interface

 (ole:def-server-interface ole.control:DDTPickerEvents)

 ;;----- A class of objects that manage the event interface connection

 (ole:def-ocx-class ole.control::channel-DDTPickerEvents
     (ole::control-event-channel
	  :interfaces ole.control:DDTPickerEvents)
     ((ole::uuid :allocation :class
		 :initform (ole:unique-guid "{20dd1b9d-87c4-11d1-8be3-0000f8754da1}"))))

 ;;----- A function invoked when a CallbackKeyDown event occurs.
 ;;      It does the method tracing output if that's enabled, then
 ;;      invokes a generic function on which the user can define
 ;;      specialized methods.

 (defun ole.control::event.DDTPickerEvents.CallbackKeyDown
	    (ole::this.control ole::this.channel
	     ole.control::KeyCode ole.control::Shift
	     ole.control::CallbackField
	     ole.control::CallbackDate)
     (when (slot-value ole::this.channel 'ole::tracing)
	(format *trace-output*
		";; event event.DDTPickerEvents.CallbackKeyDown on ~s~%"
		ole::this.control))
     (xctl:-CallbackKeyDown ole::this.control
	   ole::this.channel ole.control::KeyCode
	   ole.control::Shift ole.control::CallbackField
	   ole.control::CallbackDate))

 ;;----- A default primary method that does nothing, in case the
 ;;      application doesn't define anything

 (defmethod xctl:-CallbackKeyDown ((ole::this.control t)
					  (ole::this.channel
					   ole.control::channel-DDTPickerEvents)
					  ole.control::KeyCode
					  ole.control::Shift
					  ole.control::CallbackField
					  ole.control::CallbackDate)
	  (declare
	   (ignore ole.control::KeyCode ole.control::Shift
	    ole.control::CallbackField
	    ole.control::CallbackDate))
	  nil)

 ;;----- A function invoked when a Change event occurs.
 ;;      It does the method tracing output if that's enabled, then
 ;;      invokes a generic function on which the user can define
 ;;      specialized methods.

 (defun ole.control::event.DDTPickerEvents.Change
	    (ole::this.control ole::this.channel)
	  (when (slot-value ole::this.channel 'ole::tracing)
	    (format *trace-output*
		    ";; event event.DDTPickerEvents.Change on ~s~%"
		    ole::this.control))
	  (xctl:-Change ole::this.control ole::this.channel))

 ;;----- A default primary method that does nothing, in case the
 ;;      application doesn't define anything

 (defmethod xctl:-Change ((ole::this.control t)
				 (ole::this.channel
				  ole.control::channel-DDTPickerEvents))
	  (declare (ignore)) nil)

 ;;----- dispatch-invocation is the top-level dispatcher for Invoke
 ;;      calls (to Lisp) on the event interface.
 ;;      A method specialized on the channel argument interprets the
 ;;      memid and decodes the arguments to call the right
 ;;      
 (defmethod ole::dispatch-invocation ((ole::this.channel
				       ole.control::channel-DDTPickerEvents)
				      ole::memid
				      ole::mode
				      ole::parms)
   (declare (ignore-if-unused ole::mode ole::parms))
   (let ((ole::this.control
	  (slot-value ole::this.channel 'ole::host)))
     (case ole::memid
       (1
	(let ((ole.control::CallbackDate
	       (ole::get-nth-arg ole::parms 3))
	      (ole.control::CallbackField
	       (ole::get-nth-arg ole::parms 2))
	      (ole.control::Shift
	       (ole::get-nth-arg ole::parms 1))
	      (ole.control::KeyCode
	       (ole::get-nth-arg ole::parms 0)))
	  (ole.control::event.DDTPickerEvents.CallbackKeyDown
	   ole::this.control ole::this.channel
	   ole.control::KeyCode ole.control::Shift
	   ole.control::CallbackField
	   ole.control::CallbackDate)))
       (2
	(let ()
	  (ole.control::event.DDTPickerEvents.Change
	   ole::this.control ole::this.channel)))

       ;; more cases corresponding to methods omitted for conciseness

       (t (signal 'ole::member-not-found)))))

2.7 Reference guide to interface operators


def-ole-linkage

Macro

Package: ole

Arguments: package-name &key guid major-version minor-version pathname application

The package-name, a string or symbol, provides the name of the package in which library-specific symbols will be interned. The package will be created when the macro is expanded if it does not already exist, and the expansion will include a defpackage form for it. The keyword arguments specify a type library, and have exactly the same meaning as for the ole:load-typelib function, which is called at macro expansion time but not by the form the macro expands into.



connect-to-server

Generic Function

Package: ole

Arguments: control-object &key inplace (default t)

The first argument must be an instance of one of the coclass classes defined by a def-ole-linkage form. This function attempts to form a connection between the control-object argument and a live OLE object. If the control-object appears to be serving a live OLE object, nothing else is done. Otherwise OLE is asked to provide a new connection. If the object class is defined to have an event channel, connect-to-server tries to open it. The inplace argument defaults to t, and must be specified nil for objects not being activated in place.



connect-event-channel

Function

Package: ole

Arguments: control-object &key event-interface-class

The first argument must be an instance of a coclass class defined by def-ole-linkage. The event-interface-argument, if present, must be a symbol naming one of the legitimate event classes for the coclass, and overrides the default class defined by the type library. The function attempts to establish the OLE linkage for the events, so that Lisp code can process the events.



disconnect

Function

Package: ole

Arguments: ocx-object

The argument must be an instance of a coclass class defined by def-ole-linkage. disconnect releases all the interfaces, including event-channel interfaces so that the OLE object and its resources can be freed.



load-typelib

Function

Package: ole

Arguments: &key guid major-version minor-version pathname application

This function attempts to loacte a type library and read its definitions. The type library can be specified in one of several ways:

  1. The pathname of a typelibrary file can be given as a string or pathname.
  2. A guid can be specified as a string or lisp-guid, along with major and minor version numbers, if necessary, to locate a type library known to the registry
  3. A string that uniquely specifies an application can be given as the application keyword. In this case, if there is just one entry under RKEY-CLASSES-ROOT that contains the given string, ignoring case, and that entry has an associated typelib, then that's the typelib that is loaded.

Type libraries loaded are cached in the Lisp, and not reloaded if already present. The value returned by the function is the internalized Lisp structure holding the typelib information.



event-tracing

Function

Package: ole

Arguments: object

This function returns non-nil if event-tracing is enabled for object, which must be an instance of a lisp coclass class defined by def-ole-linkage.

Event tracing defaults to nil at object creation. A non-nil value may be set with setf and this function. The default event-handling method for all objects will print a message on *trace-output* if event-tracing is non-nil. Any user-defined specialized methods will shadow the default method, so this can be used to see what events are arriving and not being handled.



format-idl

Function

Package: ole

Arguments: pathname &optional lisp-typelib

You must load the [Allegro-directory]/ole/client/client/idlout.fasl file before using this function. If the current directory is the Allegro directory, this can be done with the form (load "ole/client/idlout").

This function writes out an approximation of the idl that generated the typelib. The pathname argument, a string or pathname, specifies the file to be written. The optional second argument is the internalized Lisp form of a typelib. If omitted, the last typelib loaded provides the data to be written.



2.8 Index to high-level interface operators



3.0 Introduction to the low-level OLE interface

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 Allegro CL OLE 's treatment of OLE concepts. It lays out the overall organization of the Allegro CL OLE tools and an Allegro CL OLE application. Detailed documentation of each element in the Allegro CL OLE system appears in the reference document, ole_reference.htm. Look there for information about individual CLOS functions, macros, classes and data types.


3.1 Sample Programs

You will find a set of sample Allegro CL OLE programs to illustrate writing client and server applications using Allegro CL 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.2 System Structure


3.2.1 Basic Concepts

Basic Unit
of OLE
Treatment in Allegro CL OLE
Data Types Most 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 Definition An 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 server side. Allegro CL 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 Counting OLE 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. Allegro CL OLE reflects these OLE methods as the CLOS generic functions add-ref and release.

Functions in the Allegro CL 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 Session Before using any OLE facilities, an application has to call certain OLE API functions to initialize state. Allegro CL OLE provides start-ole and stop-ole, functions that perform all necessary initialization calls and disconnect from OLE, respectively.

3.2.2 Important CLOS Issues for an Allegro CL OLE Application


3.2.3 Special OLE Data Types

Allegro CL OLE gives these OLE data types special treatment.


3.2.4 Language and Locale

Many OLE API functions use codes to allow localization of labels and user-readable data. The Allegro CL 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.2.5 Interfaces and Objects

Allegro CL 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 Allegro CL OLE classes are named using the following convention: Allegro CL OLE supports OLE interface IAbcde with client-side interfaces of class IAbcde-client and server-side interfaces of class IAbcde-server.

Example: When an Allegro CL OLE application obtains an IUnknown interface from some external object, it will be of type IUnknown-client. An Allegro CL 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 Allegro CL OLE application these will be represented by instances of different CLOS classes.

The Allegro CL 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 Allegro CL 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.2.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.


3.3 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; Allegro CL 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.


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


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


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


3.3.4 The Lisp remote-autotool Class

Allegro CL 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.


3.3.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-21cf-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 nil))

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.


3.4 Writing a Server

Writing an automation server is simplified by using the Allegro CL 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 is 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 Allegro CL 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))

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


3.5 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 virtual function tables attached to their proxies. (A virtual function table, or vtable or Vtbl, is a C++ object (C++ is typically used to implement OLE) which is a table which associates functions with objects, allowing for method-like functionality.)

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.


3.6 Low Level View


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

3.6.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-2022, Franz Inc. Lafayette, CA., USA. All rights reserved.
This page was not revised from the 10.0 page.
Created 2019.8.20.

ToCDocOverviewCGDocRelNotesFAQIndexPermutedIndex
Allegro CL version 10.1
Unrevised from 10.0 to 10.1.
10.0 version