Module: COM Synopsis: Fundamental infrastructure and utility routines for COM interface. Author: David N. Gray Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND //==================================================== // temporary debugging hack //==================================================== define constant $Continue-message :: = "Continue anyway"; /* define method local-debug-assert(ok? :: ) => (); unless (ok?) cerror($Continue-message, "assert failure in \"com.dylan\""); end unless; values(); end local-debug-assert; */ define inline-only function local-debug-assert(ok? :: ) => (); end local-debug-assert; //==================================================== // basic types //==================================================== // note: assuming that the following FFI types have been defined by // the Win32-common module: // define constant = ; // define constant = ; // define constant = ; // define C-struct ... // define C-struct ... // define C-struct ... // integer subranges used internally: define constant = ; define constant = ; define constant = ; define constant = ; /* define constant = limited(, min: 0, max: #xFF); define constant = limited(, min: 0, max: #xFFFF); define constant = limited(, min: 0, max: #xFFFFFF); define constant = type-union(, = ; define C-pointer-type => ; define constant = ; define constant $NULL-OLESTR :: = null-pointer(); // String Name Block define constant = ; define variable *ole-string-table* :: = make(
); define method OLESTR (string :: ) => value :: ; element(*ole-string-table*, string, default: #f) | (*ole-string-table*[string] := as(, string)) end OLESTR; define inline method OLESTR (string :: ) => value :: ; string end OLESTR; define method OLESTR (string :: ) => value :: ; // make sure we don't just coerce the pointer type of a C-string error("bug in OLESTR"); // ??? next-method(); end OLESTR; define open generic null? ( object ) => (value :: ); define inline method null? ( object :: ) => value :: ; null-pointer?(object); end; define inline sealed method null? ( object == #f ) => value :: ; #t end; define sealed domain null? (); define inline constant = ; define inline constant = ; // should be if that were supported: //define constant = ; define constant = ; //==================================================== // result and status codes //==================================================== // status code: define constant = ; // result handle: (considered obsolete, but still pervasively documented.) define constant = ; // The following methods take the place of macros defined in "winerror.h": define function SCODE-CODE(sc :: ) => value :: ; LOWORD(sc) end; define function SCODE-FACILITY(sc :: ) => value :: ; logand( HIWORD(sc), #x1fff ) end; define function SCODE-SEVERITY(sc :: ) => value :: ; as(, u%shift-right(sc, 31)) end; // Used internally, do not export: define constant $severity-bit :: = as(,#x80000000); /* See companion file "com-err.dylan" for error codes collected by: gema -f com-err.pat winerror.h */ define constant $NOERROR :: = as(,0); define inline sealed method SUCCEEDED?( sc :: ) => value :: ; ~ negative?(sc) end SUCCEEDED?; define inline sealed method FAILED?( sc :: ) => value :: ; negative?(sc) end FAILED?; define inline sealed method SUCCEEDED?(sc :: ) => value :: ; sc >= 0 end SUCCEEDED?; define inline sealed method FAILED?( sc :: ) => value :: ; sc < 0 end FAILED?; define function MAKE-SCODE(severity :: , facility :: , code :: ) => value :: ; let n :: = logior( ash(facility,16), code); let s :: = as(, n); unless ( zero?(severity) ) s := %logior( $severity-bit, s ); end; s end MAKE-SCODE; define C-subtype ( ) pointer-type-name: ; end; // Construct an HRESULT representation of a Win32 error code define method HRESULT-FROM-WIN32(error-code :: ) => scode :: ; if ( error-code = $NO-ERROR ) $S-OK else MAKE-SCODE(1, $FACILITY-WIN32, logand(error-code, #xFFFF)) end if end; // Map an NT status value into a HRESULT define function HRESULT-FROM-NT( nt-code ) => scode :: ; %logior(nt-code, #x10000000) end; //==================================================== // object handles for C (temporary until supported by C-FFI) //==================================================== define constant $handle-offset = #x7000; // for validation define constant $null-object-handle = #xDEAD; define variable *handled-objects* :: = make(, size: 10, fill: #f); define method intern-object ( object :: ) => handle :: ; let n :: = size(*handled-objects*); block(return) for ( i :: from 0 below n ) if ( *handled-objects*[i] == object ) return(i); end if; end for; for ( i :: from 1 below n ) // reserve 0 as handle for #f if ( *handled-objects*[i] == #f ) *handled-objects*[i] := object; return(i); end if; end for; *handled-objects* := concatenate( *handled-objects*, vector( object, #f, #f, #f ) ); return(n); end block + $handle-offset end method intern-object; define method unintern-object ( handle :: ) => (); if ( handle ~= $null-object-handle ) *handled-objects*[handle - $handle-offset] := #f; end if; values(); end method unintern-object; define function object-from-handle ( handle :: ) => object :: ; *handled-objects*[handle - $handle-offset]; end object-from-handle; //==================================================== // interfaces //==================================================== define C-subtype ( ) end; define C-struct slot vtbl :: ; // pointer to method table used by C code. slot handle :: ; // way to find the corresponding Dylan object. pointer-type-name: ; end ; define constant $null-vtable :: = null-pointer(); // is conceptually an abstract class -- to be used for declaring // the type of variables that could be either a or // define method make( class == , #rest args, #key address = #f, #all-keys ) => ( interface :: ); if ( address & zero?(address) ) $NULL-interface else apply(make, , args); end if end method make; define C-pointer-type => ; // is an interface that will be accessed through the C++ virtual // table. This doesn't necessarily mean that the class is actually // implemented in C, just that we don't currently know that it isn't. define open primary C-subtype ( ) pointer-type-name: ; end ; // is conceptually an abstract base class from which all // interfaces implemented in Dylan inherit. define open primary C-subtype ( ) end ; /* Note: $NULL-interface is implemented as a instead of an so that an attempt to de-reference it will be caught as a Dylan dispatch error instead of crashing the C code. A separate sub-type is used simply for clarity in the debugger. */ define sealed C-subtype ( ) end ; define constant $NULL-interface :: = null-pointer(); define C-mapped-subtype ( ) import-map , import-function: import-interface; end ; define function import-interface (ptr :: ) => value :: ; // When calling from C to Dylan, we get the address of the C structure // and need to find the Dylan interface object which is a wrapper for // that address. let object-handle = ptr.handle; if ( object-handle == $null-object-handle ) error("Importing C pointer to terminated Dylan OLE object"); end if; let obj :: = object-from-handle( object-handle ); local-debug-assert( pointer-address(obj) = pointer-address(ptr) ); obj end; define sealed domain make (singleton()); define sealed domain make (singleton()); define sealed domain initialize (); // It should be possible to define interfaces by just using `define class', // but that doesn't work yet. So for now use this macro to keep track of // the affected code. -- D.N.G. 1/22/96 [still needed with DFMC Jan. '97] // (Can't just say `define interface' because that is used by Collage.) define macro COM-interface-definer { define ?mods:* COM-interface ?name:name (?supers:*) ?specs:* end } => { define ?mods C-subtype ?name (?supers) ?specs end } end macro; //==================================================== // mapping a C interface pointer to the Dylan object //==================================================== // This uses the QueryInterface mechanism so that a C-implemented interface // can be harmlessly queried, but this is not a real interface. // In particular, it does not follow the usual conventions for use count // and for controlling unknown delegation. But that doesn't matter because // it is only used internally here. define method dylan-interface( obj :: ) => obj :: ; if ( null-pointer?(obj) ) $null-interface else let ( status :: , dobj :: ) = IUnknown/QueryInterface(obj,$IID-IdylanObject); if ( status = $S-OK ) // Note that even though QueryInterface has returned the Dylan object, // it has been returned through a C call which only passes through the // pointer address. But now that we know that it is a Dylan object, // we can map the pointer to find it. import-interface(dobj) else obj end if end if end method; define method dylan-interface( obj :: ) => obj :: ; // already have the Dylan object so just return it. obj end method; //==================================================== // "IUnknown" interface //==================================================== define open primary COM-interface ( ) sealed slot ref-count :: , init-value: 0; // Use the following keyword to specify the ``controlling unknown'' of a // member of an aggregate object. sealed slot controlling-unknown :: , init-keyword: controlling-unknown:, init-value: $NULL-interface; sealed slot interface-table :: , init-value: #(); end ; define C-struct -vstruct sealed inline-only slot vtbl-QueryInterface :: ; sealed inline-only slot vtbl-AddRef :: ; sealed inline-only slot vtbl-Release :: ; end C-struct; define C-address IUnknown-DW-vtbl :: c-name: "IUnknown_DW_vtbl"; end; define C-address $IID-IUnknown :: c-name: "IID_IUnknown"; end; define method initialize ( self :: , #rest ignore, #key ) if ( null-pointer?(self) ) error( "Can't make null-pointer(%=)", object-class(self) ); else next-method(); if ( null?(self.controlling-unknown) ) self.controlling-unknown := self; end if; self.vtbl := IUnknown-DW-vtbl; self.handle := intern-object(self); end if; values() end initialize; define method add-interface ( self :: , iid :: ) let c :: = self.controlling-unknown; c.interface-table := pair( pair(iid,self), c.interface-table ); if ( c ~= self ) AddRef(self); SubRef(c); end if; end add-interface; define open generic IUnknown/AddRef (self :: ) => value :: ; define C-callable-wrapper of IUnknown/AddRef input parameter self :: ; result count :: ; export: #t, c-name: "DW_IUnknown_AddRef", c-modifiers: "__stdcall"; end; define method IUnknown/AddRef( self :: ) => value :: ; let new-count :: = self.ref-count + 1; self.ref-count := new-count; let cu :: = self.controlling-unknown; unless ( cu == self ) if ( cu == $null-interface & self.handle == $null-object-handle ) cerror($Continue-message, "AddRef on terminated object %=", self); end if; IUnknown/AddRef(cu); end unless; new-count end; define inline-only C-function IUnknown_AddRef parameter self :: ; result count :: ; c-name: "C_IUnknown_AddRef"; end; define method IUnknown/AddRef( self :: ) => value :: ; // Do nothing when called on $NULL-interface if ( null-pointer?(self) ) 0 else IUnknown_AddRef(self) end if; end; define open generic IUnknown/Release (self :: ) => value :: ; define C-callable-wrapper of IUnknown/Release input parameter self :: ; result count :: ; export: #t, c-name: "DW_IUnknown_Release", c-modifiers: "__stdcall"; end; define method IUnknown/Release( self :: ) => value :: ; let new-count :: = self.ref-count - 1; if ( new-count < 0 ) cerror($Continue-message, if ( self.handle == $null-object-handle ) "Release called more times than AddRef on %=" else "Release called before AddRef on %=" end if, self); end if; if ( zero?(new-count) ) // Don't store the new count until after calling `terminate' in order // to avoid the possibility of an AddRef / Release pair causing a // recursive terminate. terminate(self); // clean up references to other objects // Make sure no new references were created during termination. if ( self.ref-count > 1 ) cerror($Continue-message, "AddRef within terminate(%=)", self); end if; self.ref-count := 0; unintern-object(self.handle); // allow object to be GC'd self.handle := $null-object-handle; // trap attempt to use dead object self.controlling-unknown := $NULL-interface; destroy(self); // de-allocate the else self.ref-count := new-count; let cu :: = self.controlling-unknown; unless ( cu == self ) IUnknown/Release(cu); end unless; end if; new-count end; define open generic SubRef (self :: ) => value :: ; define method SubRef( self :: ) => value :: ; // Like Release, except can decrement to 0 without terminating. // Use this to un-do an AddRef during initialization before the AddRef // for the real user of the object. let new-count :: = self.ref-count - 1; if ( new-count < 0 ) cerror($Continue-message, "Negative ref count on %=", self); end if; self.ref-count := new-count; let cu :: = self.controlling-unknown; unless ( cu == self ) SubRef(cu); end unless; new-count end; // The original intent was that `terminate' would be called either when the // reference count is decremented to zero or by GC finalization prior to // garbage-collecting the object. However, the use of `intern-object' // prevents the object from being GC'd before the ref count becomes 0. // If the object is being exported to C code or to another process, then we // have to prevent GC, and there is no reliable way to know whether the // pointer is going to be used that way. So there does not appear to be // any safe way to take advantage of finalization. -- D.N.G. 9/23/97 define open generic terminate ( self :: ) => (); define method terminate ( self :: ) => (); next-method(); let interface-list :: = self.interface-table; self.interface-table := #(); for ( item :: in interface-list ) let object :: = tail(item); if ( object ~== self ) if ( object.controlling-unknown == self ) object.controlling-unknown := object; end if; IUnknown/Release(object); // undo increment in add-interface end if; end for; // Note: if this were to be called from finalization instead of `Release' // (and if we could tell the difference), we would also want to do // `destroy(self)' here to release the C storage space. values() end; // last resort for next-method does nothing: define inline method terminate ( self :: ) => (); values() end terminate; define inline-only C-function IUnknown_Release parameter self :: ; result count :: ; c-name: "C_IUnknown_Release"; end; define method IUnknown/Release( self :: ) => value :: ; // Do nothing when Release is called on $NULL-interface if ( null-pointer?(self) ) 0 else IUnknown_Release(self) end if; end; define open generic IUnknown/QueryInterface ( self :: , riid :: ) => ( status :: , Object :: ); define C-callable-wrapper of IUnknown/QueryInterface input parameter self :: ; input parameter riid :: ; output parameter Object :: ; result status :: ; export: #t, c-name: "DW_IUnknown_QueryInterface", c-modifiers: "__stdcall"; end; define method IUnknown/QueryInterface( self :: , rrid :: ) => ( status :: , Object :: ); if ( pointer-address(rrid) = pointer-address($IID-IdylanObject) ) // Note: can compare addresses here instead of using `IsEqualIID?' because // this special IID is only used locally within this file. Also, it // helps ensure that we don't try to return a Dylan object to a client // in a different process. values( $S-OK, self ) else let c :: = self.controlling-unknown; if ( c == self ) if ( IsEqualIID?(rrid, $IID-IUnknown) ) AddRef(self); values( $S-OK, self ) else block (return) for ( el :: in self.interface-table ) if ( IsEqualIID?( head(el), rrid ) ) let x :: = tail(el); local-debug-assert( x.handle ~= $null-object-handle ); AddRef(x); return( $S-OK, x ); end if; end for; if ( self.ref-count <= 0 & empty?(self.interface-table) ) cerror($Continue-message, "QueryInterface on terminated object %=", self); end if; return( $E-NOINTERFACE, $NULL-interface ); end block end if else // recurse in case the user defined an override method IUnknown/QueryInterface(c, rrid ); end if end if end method IUnknown/QueryInterface; define inline-only C-function IUnknown_QueryInterface parameter self :: ; input parameter riid :: ; output parameter Object :: ; result status :: ; c-name: "C_IUnknown_QueryInterface"; end; define method IUnknown/QueryInterface( self :: , rrid :: ) => ( status :: , Object :: ); IUnknown_QueryInterface(self, rrid) end; define method IUnknown/QueryInterface( self :: , id-string :: ) => ( status :: , Object :: ); let id-struct :: = as(, id-string); block () IUnknown/QueryInterface(self, id-struct) cleanup destroy(id-struct); end end; define constant AddRef = IUnknown/AddRef; define constant Release = IUnknown/Release; define constant QueryInterface = IUnknown/QueryInterface; // Trap invalid pointer type conversions: define method as (class :: subclass(), value :: ) => (dummy :: ); error("Can't convert %= to %=", value, class); end; //==================================================== /* Users can create their own interface by defining a subclass of one of the pre-defined interfaces: define COM-interface ( ) // ... slots ... end; And defining either: define method initialize ( self :: , #rest args ) next-method(); self.add-interface($IID-IFoo); end initialize; Or: define method QueryInterface( self :: , rrid :: ) => ( status :: , Object :: ); if ( riid = $IID-IFoo) self.AddRef(); values( $S-OK, self ); else next-method(); end if; end method QueryInterface; And then defining the methods. However, these methods will not be callable from C code unless additional hacks are done. (The object can however be passed to C functions that accept a pointer to an instance of the base class interface.) Similarly, additional hacks would be needed for a Dylan program to be able to use a custom interface implemented in C. */ //==================================================== // helper functions for more convenient use from Dylan //==================================================== define variable int-ptr :: = make(); define constant $int-size :: = size-of(); define method IStream/Write-integer( stream :: , value :: type-union(, ) ) => ( status :: , bytes :: ); pointer-value(int-ptr) := value; /* return */ IStream/Write(stream, int-ptr, $int-size ) end; define method IStream/Read-integer( stream :: ) => ( status :: , value :: type-union(, ), bytes :: ); let ( status, bytes ) = IStream/Read(stream, int-ptr, $int-size ); values( status, pointer-value(int-ptr), bytes ) end;