As in C++, an OLE application is written by defining subclasses for the various interface classes to be supported, and the methods to implement the prescribed behavior. Multiple inheritance of interface classes is not allowed, but <IUnknown> is implicitly included as a superclass of all of the other interfaces. The library provides a complete implementation of the IUnknown interface, so the user is not required to define the methods QueryInterface, AddRef, and Release, although they may be overridden if necessary. When make is called to instantiate an interface class, the optional keyword controlling-unknown: may be used to designate the object handling the IUnknown protocol on behalf of an aggregate object.
Other methods for which default implementations are provided: LockServer
For example, where C++ might say:
interface myOLEobject : public IOleObject
{
...
};
the corresponding Dylan code would be:
define COM-interface <myOLEobject> ( <IOleObject> ) ... end;
The macro define COM-interface has the same syntax and semantics as define class, but compiler limitations require it to be used to perform special handling for interface classes.
For convenience, it is permissible to call AddRef or Release on a null interface, so it is not necessary to check first.
<IUnknown> also implements a function called SubRef, which is like Release in that it undoes the effect of a call to AddRef, but differs from Release in that it will not terminate the object if the count is returned to 0. This is sometimes needed within an initialize method to return the reference count to 0 before the AddRef is performed by the caller of make. (In C/C++ code, this situation is typically handled by directly incrementing and decrementing the reference count slot, without using AddRef or Release, but the Dylan implementation does not directly expose that slot.)
If the user class is to define a new interface (as opposed to implementing a pre-defined interface), then for QueryInterface to recognize the new interface, it is sufficient to define an interface ID and a method like:
define method initialize ( self :: <IFoo> ) next-method(); add-interface(self, $IID-IFoo); end initialize;
However, the methods of the new interface will not be callable from code in other languages without the use of additional tools that are not currently provided. Such tools are expected to be needed, but are not yet specified.
For defining the interface ID, where C code would do like:
DEFINE_GUID(GUID_SIMPLE, 0xBCF6D4A0, 0xBE8C, 0x1068, 0xB6, 0xD4,
0x00, 0xDD, 0x01, 0x0C, 0x05, 0x09);
in Dylan do:
define constant $GUID-SIMPLE :: <REFGUID> =
make-GUID(#xBCF6D4A0, #xBE8C, #x1068, #xB6, #xD4, #x00, #xDD,
#x01, #x0C, #x05, #x09);
Alternatively, the internal GUID can also be converted from the string representation like this:
define constant $GUID-SIMPLE :: <REFGUID> =
as(<REFGUID>, "{BCF6D4A0-BE8C-1068-B6D4-00DD010C0509}");
There is also an as method for converting a GUID to a <string>.
Since Dylan does not have destructors, any cleanup code that would have been done in a C++ destructor must instead be done by providing a method on generic function terminate. The default method for Release will call terminate when the object's reference count is decremented to zero. It may also be called before the object is garbage collected, if GC finalizations are available and enabled.
For example, where a C++ OLE application class might do:
myApp::~myApp()
{
if (IsInitialized())
OleUninitialize();
DestroyWindow(m_hAppWnd);
}
the corresponding Dylan code would look like:
define method terminate (this :: <my-app>) => ();
next-method();
if ( IsInitialized() )
OleUninitialize();
end if;
DestroyWindow(this.m_hAppWnd);
values();
end terminate;
The Dylan function pointer-value can be used to convert between a Dylan integer and a LARGE_INTEGER or ULARGE_INTEGER. For example:
let li :: make( <PLARGE-INTEGER> ); pointer-value(li) := 0;
allocates a LARGE_INTEGER and sets its value to 0, without needing to be concerned with the individual fields of the internal representation.
Note that this makes the C macros LISet32 and ULISet32 unnecessary in Dylan.
When an interface pointer is received from an API call, it could represent an interface implemented in either C/C++ or Dylan, so any calls to its methods need to first go through the C/C++ method table, and then through the Dylan generic function dispatch if it is actually implemented in Dylan. If the interface is going to be used in a series of method calls, it may be more efficient to first call function dylan-interface on it to obtain a direct Dylan representation of the interface that uses Dylan dispatch directly. The argument is returned unchanged if it is not a Dylan interface. For example:
let interface = dylan-interface( object.foo-interface ); foo(interface, ...); bar(interface, ...);
Some other helper functions provided to make it easier to use Dylan data types:
IStream/Write-integer( stream, integer-value ) => ( status, count )
IStream/Read-integer( stream ) => ( status, integer-value, count )
For convenience, the class <LPSTGMEDIUM> has pointer-value and pointer-value-setter methods provided which use Dylan's run-time typing to automatically manage the tymed field.