|
Allegro CL version 11.0 |
The high-level OLE/OCX interface, introduced in 2005, is described in 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 Introduction to the low-level OLE interface.
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 def-ole-linkage.
This document describes the interface. The related document, ole_reference.html, lists all the operators.
To start using the interface, then, evaluate the following require form:
(require :ole-dev)
And then make a call to 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.
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.
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 load-typelib). The load-typelib function does not itself define the foreign function linkage needed to activate and control the objects from Lisp. The def-ole-linkage macro, also described below, invokes 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 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. 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 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 def-ole-linkage).
The 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 def-ole-linkage, which reads and analyzes the type library, is only needed when the form is compiled or interpreted. Once a file containing 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.
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 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:
:interface
: can be a symbol naming any control interface the CoClass supports (default is the symbol naming the default interface for the CoClass) This is the interface that will be acquired when the OCX connection is established.:event-interface
: can be a symbol naming any event interface the CoClass supports (default is the symbol naming the default source interface for the CoClass) This is the interface that will be used when establishing the event channel.:server-context
: can be one of :inproc
or :local
(default is to try :inproc
, then :local
if :inproc
fails) Specifies the type of service to request from OLE.Making an instance of a coclass class does not automatically get the OCX object created. That's done by 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 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, 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);
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 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, 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 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 connect-to-server, 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. 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 connect-to-server on a set of instantiated Lisp objects either before or after a dumplisp.
Because the InternetExplorer CoClass defines an event channel, 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.
For each value in an enum, def-ole-linkage generates a corresponding defconstant form. 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)
For each interface defined in a typelib, 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.
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 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 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)))))
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.
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.
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.
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.
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:
The pathname of a typelibrary file can be given as a string or pathname.
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
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.
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.
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.
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.html. Look there for information about individual CLOS functions, macros, classes and data types.
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.
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
|
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. |
Classes: Some confusion can arise between CLOS classes and COM/OLE classes. CLOS classes define objects in the Lisp world. Their semantics are specified by the ANSI standard and the application code. COM/OLE classes define objects that are supported by a server application and made available to client applications that may reside in a different address space or on a different machine. Instances of CLOS classes are created by invoking the CLOS make-instance
function. Instances of COM/OLE classes are created by asking OLE to locate the server and ask it to make an instance. The point of Allegro CL OLE is to provide CLOS classes and functions that let Allegro applications be clients and servers of COM/OLE objects. There will be CLOS objects representing OLE/COM objects and vice versa. It should be clear from context which meaning of "class" is appropriate each time the word appears.
Package Structure: The Allegro CL OLE symbols reside in the ole
package; exported symbols name the documented CLOS functions, macros, constants, classes and foreign data types that Allegro CL OLE supports.Users should not place (in-package :ole)
forms in their source files. They should either include a (use-package :ole)
form or use explicit qualification on Allegro CL OLE symbols, as in (ole:query-interface foo ole:IID_IUnknown)
.
Case Conventions: ANSI Common Lisp specifies that when reading symbols, the default action is to convert unescaped lower case letters to upper case; all standard Common Lisp symbols (e.g., FORMAT
and DEFUN
) appear in upper case. Allegro CL can operate in this mode (and does so with the executables/images named alisp and alisp8); but Allegro CL also operates in the modern mode, consistent with UNIX and C usage. In the modern mode, used by executables/images named mlisp and mlisp8, symbol names are read without case conversion and all Common Lisp symbols (e.g., format
and defun
) appear in lower case. When operating in ANSI mode, symbols in the OLE package, including function, macro and CLOS class names, are all upper case. The case in which they are written in a source file is not significant. Two symbols that differ only in case cannot be used to name separate entities without escaping the lowercase letters. These last two points mean that errors due to inconsistent case usage disappear, but at the same time, the common C convention of using a capitalized symbol for a structure or class name and lower case for an object, as in Party
and party
, introduces a naming conflict. When operating in modern mode, Allegro CL allows source files to contain distinct symbols that differ only in the case of individual letters. In this mode, all symbols that represent interfaces, OLE functions and OLE constants appear in Lisp with the same case configuration they have in C, e.g., IUnknown
, GetClassObject
, DISP_E_UNKNOWNINTERFACE
. OLE types appear in the same case they have in C unless the C type is all upper case, in which case the Allegro CL OLE type name is all lower case, e.g. pInterface
, bstr
. Important: An Allegro CL OLE application running in ANSI mode must direct the fasl loader to convert mixed-case symbol names to upper case, something the fasl loader does not do by default. The way to specify this directive is by evaluating (convert-mixed-case-symbols t)
Loading the Allegro CL OLE system into Allegro by evaluating (require :ole)
or (require :ole-dev)
automatically sets the correct convert-case-mode. An Allegro CL OLE file that has been compiled in modern, case-sensitive-lower mode can be successfully loaded into a Lisp running in ANSI mode, as long as the shift to all upper case doesn't introduce any name clashes. The reverse is not true. An Allegro CL OLE application file compiled in ANSI mode will not successfully load into a modern Lisp, because there is no way to recover the mixture of upper and lower case in symbols such as IUnknown
. For this reason, it is a good plan to compile Allegro CL OLE applications in modern mode, so the compiled application can be loaded and run by a Lisp running in either mode. Once Allegro CL OLE has been loaded into Lisp, it is likely that subsequently changing the system's case mode will render Allegro CL OLE inoperable. Attempting to call set-case-mode in this environment will raise a continuable error warning the user of the problem. Continuing from this error allows the system to change its case convention despite the danger to OLE.
Modules The Allegro CL OLE subsystem is partitioned into two major modules and a number of separate minor modules. The major modules are
ole: Run-time support. Definitions needed to run an Allegro CL OLE application.
An Allegro CL OLE application file should include the following two forms to take advantage of this separation:
(eval-when (compile eval) (require :ole-dev))
(eval-when (compile load eval) (require :ole))
The minor modules include special CLOS classes and functions used to support automation clients and servers, a few other special OLE areas that are not needed in every application, and separate files for each client and server interface. Allegro CL OLE provides three macros to include these modules:
require-client-interfaces: to note client interfaces used.
require-server-interfaces: to note server interfaces supported.
require-modules: to note non-interface modules used.
These macros generate code to load the associated modules when they are needed. The macros require-server-interfaces
and require-client-interfaces
are generated as necessary by Allegro CL OLE macros that refer to interfaces by name, such as def-ocx-class
, and so are rarely coded explicitly. (The macro def-ocx-class
defines a CLOS server class that supports a named set of interfaces. It generates the appropriate require-server-interfaces
forms, so no explicit requires are needed for those server interface modules.) The most common situation in which these macros must be coded is when a program refers to symbols or classes belonging to an interface that it neither defines itself nor names in some other Allegro CL OLE macro. Programs that define part of an OLE server generally need to use require-modules
to ensure the presence of the server-support functions. A typical automation server would include the form
(eval-when (compile load eval)
(ole:require-modules :automation-server :factory-server))
A server that did not provide an IDispatch interface would not need the :automation-server
support, and could get by with just the :factory-server
. Currently there are no client-side modules that need to be loaded this way. Allegro CL OLE includes a library of interface definition modules grouped into three directories
The IUnknown interface, for example, has a base definition as .../defifc/iunknown.{cl,fasl}, while the associated client-side definitions appear in .../client/iunknown.{cl,fasl} and the server-side definitions are in .../server/iunknown.{cl,fasl}. The defifc/* files are only needed during compilation of the associated server/* and client/* files. The machinery for defining interfaces generates code to ensure the loading of all interface files on which a given interface or module depends. When an interface module is to be loaded, Allegro CL OLE checks the current directory and the value of *require-search-list*.
Allegro CL OLE gives these OLE data types special treatment.
Unicode: Many string arguments to OLE functions and interfaces are Unicode strings. Unicode is a 2-byte-per-character encoding for all the world's character sets (used, for example, by Microsoft). When operating in standard Allegro CL (which uses 16 bits per character), Lisp character strings are represented as Unicode strings, so there is no problem in passing Unicode data between Lisp and COM/OLE interfaces. When operating in an 8-bit Allegro CL, Lisp strings are ASCII. They are translated to UNICODE when passed as COM/OLE arguments or return values, by widening the 8-bit unsigned ASCII codes to 16-bit unsigned UNICODE characters. When converting a UNICODE string for Lisp's use, encountering any non-ASCII character signals an error.
GUIDs: OLE uses 128-bit keys called GUID's whenever it wants a universal name for something. For example, there is a unique key for each interface type and for each OLE object in the system. OLE defines a standard readable representation for a GUID, based on hexadecimal encoding: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. In Allegro CL OLE we represent a GUID by a lisp-guid structure, whose slots record the standard form of the GUID as well as an instance of the GUID's binary encoding. Incoming guid's are usually translated at the interface so that Lisp functions implementing server code see the lisp-guid structure. This allows eql comparison on guid values, as in case statements and (eql ...) discrimination for generic functions.
Bstrings: Many OLE functions use BSTR's when passing string arguments. These are Basic-style string data. Allegro CL OLE has a small set of functions that give reasonable access to the OLE-supplied bstring manipulation functions.
Interface Pointers: In Allegro CL OLE each interface instance (pInterface) appears as a CLOS object. There are two different CLOS interface classes for each OLE interface type, a client-interface class and a server-interface class. See Interfaces and Objects for more details.
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.
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.
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
rkey-classes-root
- opened to HKEY_CLASSES_ROOT, for OLE class informationrkey-current-user
- opened to HKEY_CURRENT_USER, for user profile informationrkey-local-machine
- opened to HKEY_LOCAL_MACHINE, for machine configuration informationrkey-users
- opened to HKEY_USERS, for information about all usersThe following functions and macros are provided to open keys and to read and modify the registry. See the reference document for their definitions.
open-registry-key
with-open-registry-key
do-registry-subkey-names
do-registry-value-names
registry-value-present-p
registry-value
(setf registry-value)
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.
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.
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.
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.
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.
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.
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.)
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
.
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
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
owner
handle
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) Franz Inc. Lafayette, CA., USA. All rights reserved.
|
Allegro CL version 11.0 |