The index for the Allegro CL Documentation is in index.htm. The documentation is described in introduction.htm.
This document contains the following sections:
1.0 IntroductionThe facility described in this document simplifies OLE programming without imposing any limit on the programmer's access to OLE functionality. To a CLOS program, Microsoft's COM/OLE/ActiveX facilities look like a foreign library consisting of data types, named API entries, and interfaces, the latter being C++-like objects with associated virtual tables. Allegro's OLE support provides tools for dealing with these foreign entities. Every API point is reachable, every interface can be used. The support also includes a library of CLOS classes and functions that make it easier to manage an application's OLE component in a CLOS development environment. This CLOS-OLE layer is not yet complete as we are continuing to extend it. All the most useful OLE capabilities will be as readily available in CLOS as in any other environment, and will benefit from the unique dynamic power inherent in CLOS.
This file contains an overview of ACL OLE 's treatment of OLE concepts. It lays out the overall organization of the ACL OLE tools and an ACL OLE application. Detailed documentation of each element in the ACL OLE system appears in the reference document, ole_reference.htm. Look there for information about individual CLOS functions, macros, classes and data types.
You will find a set of sample ACL OLE programs to illustrate writing client and server applications using ACL OLE. Each sample appears in a directory ole/samples/samplenn, and includes a readme.txt file explaining how to compile and run the example.
Basic Unit of OLE | Treatment in ACL 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 the server side. ACL 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. ACL OLE
reflects these OLE methods as the CLOS generic functions
add-ref and release .
Functions in the ACL OLE subsystem make add-ref and release calls at the appropriate times. Allegro garbage collection finalizations perform a release call on each client-interface object that dies without already having been released. |
Starting an OLE Session | Before using any OLE facilities, an application
has to call certain OLE
API functions to initialize state. ACL OLE provides
start-ole and stop-ole ,
functions that perform all necessary initialization calls
and disconnect from OLE,
respectively. |
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 ACL 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.ole
package;
exported symbols name the documented CLOS functions,
macros, constants, classes and
foreign data types that ACL 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 ACL
OLE symbols, as in (ole:query-interface
foo ole:IID_IUnknown)
.
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 standard Allegro CL
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
ACL OLE type name is all lower
case, e.g. pInterface
, bstr
.
Important: An ACL 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 ACL OLE system into Allegro by evaluating
(require :ole)
or
(require :ole-dev)
automatically sets the correct convert-case-mode.
An ACL 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 ACL 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 ACL OLE applications in modern mode,
so the compiled application
can be loaded and run by a lisp running in either mode.
Once ACL OLE has been loaded into lisp, it is likely that subsequently changing the system's case mode will render ACL 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.
An ACL 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. ACL OLE provides three macros to include these modules:
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 ACL 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 ACL 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.
ACL 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, ACL
OLE checks the current
directory and the value of
*require-search-list*
.
ACL OLE gives these OLE data types special treatment.
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.
Many OLE API functions use codes to allow localization of labels and
user-readable data. The ACL OLE interface uses two parameters to
provide default values for these codes: ole:*ole-language*
and ole:*ole-locale*
. Currently, these are set to the
machine-dependent-default values that are OLE's
lowest-common-denominator.
ACL OLE distinguishes between those interfaces that lisp implements as a server and those that lisp uses as a client. CLOS objects represent interfaces, and different CLOS classes exist for the client and server views of the same interface. This is important because we often have to deal with an interface from both sides in the same program. While developer-defined interface classes can be given any names, the ACL OLE classes are named using the following convention: ACL OLE supports OLE interface IAbcde with client-side interfaces of class IAbcde-client and server-side interfaces of class IAbcde-server.
Example: When an ACL OLE application obtains an IUnknown interface
from some external object, it will be of type
IUnknown-client
. An ACL OLE server application will
generate IUnknown-server
objects in response to requests
for the IUnknown interface.
An OLE object may reveal any number of interfaces to the outside world. The interfaces and the object are distinct entities, and in an ACL OLE application these will be represented by instances of different CLOS classes.
The ACL OLE object on the server side
will be an instance of a class that
inherits from the lisp-ole-object
class and from several
mixin classes, one for each OLE Interface the object supports. These
mixin classes are named by the OLE interface name, e.g.,
IClassFactory
or IOleObject
. The object
itself is completely under the server's control; only the interfaces
are exported to the rest of the world. As a server, the CLOS
application must implement the methods of these interfaces.
A developer will often do this by using def-ocx-class
to
define the object class, specifying the interface mixins. Here, for
example, is the definition for ACL OLE's class-factory
class .
(ole:def-ocx-class class-factory (:interfaces IClassFactory) ((registration-code :initform nil) (locked :initform nil) (children :initform nil) (product-class :initarg :product-class) (allow-aggregation :initform nil :initarg :allow-aggregation) ))
Here we are saying that a class-factory has the usual semantics for an
OLE object implemented in lisp, and that it exports two interfaces:
IUnknown (supported by default) and IClassFactory. These interfaces
have been defined previously with def-ole-interface and are
implemented by IUnknown-server
and
IClassFactory-server
instances, respectively. This class
definition form arranges that class-factory
objects will
respond to QueryInterface requests for the IUnknown and IClassFactory
interfaces, constructing and caching each interface the first time it
is needed.
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; ACL OLE functions to access them can use strings or symbols.
A property and a method can have the same name because when that name is used it is clear from the use whether the property or method is intended.
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.
ACL OLE defines the ole:remote-autotool
class to
facilitate control of automation objects. An instance of
ole:remote-autotool
holds the Ole machinery
that communicates with the server application.
To establish a connection to an automation object you can use the
function ask-for-autotool
.
(setq autoinstance (ole:ask-for-autotool (ole:unique-guid "{dbce6200-e0a3-11cf-b565-00aa0064595a}") ole:CLSCTX_INPROC_SERVER))
is the form to use if you know the OLE class id. If the application that supports the class is registered, you can bypass the class id and use the registered application name:
(setq autoinstance (ole:ask-for-autotool "MSCAL.Calendar" ole:CLSCTX_INPROC_SERVER))
If you expect to create more than one instance of the same object type then it's more efficient to ask for the class factory and then ask the factory for the specific objects you want to create.
(setq factory (ole:get-class-object "{dbce6200-e0a3-11cf-b565-00aa0064595a}" ole:CLSCTX_LOCAL_SERVER ole:IID_IClassFactory)) (setq autoinstance (ole:ask-for-autotool factory))
If xdi
is an IDispatch-client
interface for an object that supports automation, you can build an
autotool object for it like this:
(setq autoinstance (make-instance 'ole:remote-autotool :dispatch xdi))
Whichever route you take to acquire the
remote-autotool
instance, you can then use the
functions auto-getf
, (setf
auto-getf)
, and auto-method
to read
properties, set properties, and call functions on the automation
object.
For example,
(ole:auto-getf autoinstance :x)
will ask for the value of the x property of the automation object.
(setf (ole:auto-getf autoinstance :x) 555)
will set the x property value to 555.
(ole:auto-method autoinstance :foob 3 4)
will call the foob
method on the
automation object, passing in two extra arguments, 3 and 4.
When you've completed using the remote object do (ole:release autoinstance) to free it on the server side. After the call to ole:release, don't use the autoinstance object again since it no longer refers to an object on the server.
Writing an automation server is simplified by using the ACL OLE automation and factory interfaces. See ole/samples/sample04/server.cl for an example.
Suppose you, a lisp programmer, have some functionality you want to offer to clients via OLE/COM.
First figure out which interfaces you want to support in this
object. You don't have to declare them all immediately, you can add
more later on. You must support IUnknown at least. Each interface must
be defined in your application. Many interfaces are already
defined. Check the ole/defifc directory to see which. If there's
already a definition, you probably want to use it. If the interface
doesn't appear in ole/defifc, then you will need to provide a
definition using def-ole-interface
and
def-server-interface
. You can put the definitions in
your application source code if the interface is unique to that
application. Alternatively, you can add it to the ACL OLE library if
you expect to use it in more than one application.
Next you define a CLOS class whose instances will represent objects
allocated on behalf of the client. This CLOS class should be defined
with def-ocx-class
or be a subclass of such a class. The
def-ocx-class
form will name each interface that this
object will export to clients, except possibly IUnknown, which gets
put in automatically if you don't name it. The interface names are
symbols like IStorage
, IOleObject
,
etc. Example:
(ole:def-ocx-class my-class (:interfaces IFoo) ....)
Put in all the interfaces you want to support after the :interfaces keyword. If you want to support two different interfaces with the same interface object, where one is based on the other, include a list of the related interfaces as one of the entries after the :interfaces keyword, as in
(ole:def-ocx-class my-class (base1 :interfaces (IViewObject IViewObject2) IOleObject) ((local-slot ..) ...))
Here, a request for either IViewObject or IViewObject2 will be satisfied with the same object, which will be of type IViewObject2-server. The last named interface in a set is the one that is used for any of them.
With the CLOS class my-class
defined you must now make
sure that the methods for each exported interface are defined over
this type of data object. Suppose your class supports the IFoo
interface, an interface that has four methods: the three from
IUnknown, plus the method 'addem' that adds its two integer arguments
together and returns an integer result. The interface definition might
look like this:
(def-ole-interface IFoo (:iid "{12345678-1234-5678-1234-123456781234}") (addem (in.arg1 integer) (in.arg2 integer))) (def-server-interface IFoo)
When a client allocates an object of your class and gets a pointer
to the IFoo interface and then calls addem, control will eventually
reach your server. When that happens, the generic function
addem
will be called with three arguments: the first
argument is the CLOS object of your class that the client has remotely
allocated, and the other two arguments are the integers to add. You
could thus write your server method in this way:
(defmethod addem ((obj my-class) x y) (+ x y))
In this particular case, as in most cases, the function of the addem method in the IFoo interface doesn't depend on the object itself, so we might just want to write that method for all classes that export that interface:
(defmethod addem ((obj IFoo) x y) (+ x y))
this works because all classes that export the IFoo interface are a subclass of IFoo. Or we could write it for the interface object itself and not bother to look for an object-specific function.
(defmethod addem ((ifc IFoo-server) x y) (+ x y))
ACL OLE uses this ability to write methods over an interface to define the three methods inherited from IUnknown: add-ref, release, and query-interface. Thus the server class writer generally doesn't have to worry about writing these methods. (And in fact, should not replace the primary methods, ever. :before, :after, and :around methods are OK.)
The def-ole-interface
macro defines each interface. With
it you name the interface (without the "-server" or
"-client" as that is added by
def-server-interface
and
def-client-interface
). The def-ole-interface
macro allows you to specify the IID and the methods for the interface,
possibly specifying a base interface to inherit methods from. You list
the methods by name and give an argument map (name and type) for each
argument of each method. The macro expands into code that creates a
structure containing all this information. The argument-type
encoding is the one used by ff:def-foreign-type. An older form,
approximating what appears in the C header files, is also accepted,
but is deprecated.
With def-server-interface
you specify the interface
you're generating linkage code for, as in
(def-server-interface IFoo)
The result is to define two classes: IFoo-server
is the
server interface class. IFoo
is the mixin class for all
objects that support the IFoo interface.
It may be confusing to have two classes representing an interface on
the server side: IFoo-server
and IFoo
. The
difference between the classes is this: Instances of
IFoo-server
are interface objects, which have relatively
simple and unchanging functionality. These methods are called first
when a client call comes in so you might want to write methods that do
argument transformation before passing the call to the object-specific
code.
An object could reasonably be both IViewObject
and
IOleObject
. That would mean that it made both types of
interfaces available to clients and responded appropriately to methods
on either interface. However, an IOleObject-server
object
is definitely *not* an IViewObject-server
object. They
have completely different vtables attached to their proxies.
Thus if you want to write methods over your server objects that depend
on them having an IFoo interface, then you write those methods over
IFoo. As an example, the IUnknown reference counting methods would be
best written over IUnknown
, since all objects that
support an IUnknown interface will inherit from
IUnknown
.
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) 1998-2000, Franz Inc. Berkeley, CA., USA. All rights reserved. Created 2000.10.5.