racket/collects/scribblings/foreign/com-auto.scrbl
Matthew Flatt eb7fd51d02 ffi/com: add `any ...' support for method arguments
Also allow `?' for the length of an array and suport VT_SAFEARRAY
arguments (mostly the same as VT_ARRAY).
2012-09-01 09:48:35 -06:00

546 lines
19 KiB
Racket

#lang scribble/doc
@(require scribble/manual
scribble/bnf
"com-common.rkt"
(for-label racket/base
(except-in racket/contract ->)
ffi/unsafe/com
ffi/com-registry))
@title[#:tag "com-auto"]{COM Automation}
@defmodule[ffi/com #:use-sources (ffi/unsafe/com)]{The
@racketmodname[ffi/com] library builds on COM automation to provide a
safe use of COM objects that support the @as-index{@cpp{IDispatch}}
interface.}
@margin-note{The @racketmodname[ffi/com] library is based on the
@deftech{MysterX} library by Paul Steckler. MysterX is included with
Racket but deprecated, and it will be replaced in the next version
with a partial compability library that redirects to this one.}
@; ----------------------------------------
@section{GUIDs, CLSIDs, IIDs, and ProgIDs}
@deftogether[(
@defproc[(guid? [v any/c]) boolean?]
@defproc[(clsid? [v any/c]) boolean?]
@defproc[(iid? [v any/c]) boolean?]
)]{
Returns @racket[#t] if @racket[v] is a structure representing a
@tech{GUID}, @racket[#f] otherwise. The @racket[clsid?] and
@racket[iid?] functions are the same as @racket[guid?].
A @tech{GUID} corresponds an a @racket[_GUID] structure at the unsafe
layer.}
@deftogether[(
@defproc[(string->guid [str string?]) guid?]
@defproc[(string->clsid [str string?]) clsid?]
@defproc[(string->iid [str string?]) iid?]
)]{
Converts a string of the form
@racket["{00000000-0000-0000-0000-0000000000}"], where each @tt{0} can
be a hexadecimal digit, to a @tech{GUID}. If @racket[str] does not
have te expected form, the @racket[exn:fail] exception is raised.
The @racket[string->clsid] and @racket[string->iid] functions are the
same as @racket[string->guid].}
@defproc[(guid->string [g guid?]) string?]{
Converts a @tech{GUID} to its string form.}
@defproc[(guid=? [g1 guid?] [g2 guid?]) boolean?]{
Determines whether @racket[g1] and @racket[g2] represent the same @tech{GUID}.}
@deftogether[(
@defproc[(progid->clsid [progid string?]) clsid?]
@defproc[(clsid->progid [clsid clsid?]) (or/c string? #f)]
)]{
Converts a @tech{ProgID} to a @tech{CLSID} or vice versa. Not evey
@tech{COM class} has a @tech{ProgID}, so the result of
@racket[clsid->progid] can be @racket[#f].
The @racket[progid->clsid] function accepts a versionless
@tech{ProgID}, in which case it produces the @tech{CLSID} of the most
recent available version. The @racket[clsid->progid] function always
produces a @tech{ProgID} with its version.}
@; ----------------------------------------
@section{COM Objects}
@defproc[(com-object? [obj com-object?]) boolean?]{
Returns @racket[#t] if the argument represents a @tech{COM object}, @racket[#f]
otherwise.}
@defproc[(com-create-instance [clsid-or-progid (or/c clsid? string?)]
[where (or/c (one-of/c 'local 'remote) string?) 'local])
com-object?]{
Returns an instance of the @tech{COM class} specified by
@racket[clsid-or-progid], which is either a @tech{CLSID} or a
@tech{ProgID}.
The optional @racket[where] argument indicates a location for
running the instance, and may be @racket['local], @racket['remote],
or a string indicating a machine name. See @secref["remote"] for
more information.
An object can be created this way for any COM class, but functions
such as @racket[com-invoke] work only if the object supports the
@cpp{IDispatch} COM automation interface.
The resulting object is registered with the current custodian, which
retains a reference to the object until it is released with
@racket[com-release] or the custodian is shut down.}
@defproc[(com-release [obj com-object?]) void?]{
Releases the given @tech{COM object}. The given @racket[obj] is
subsequently unusable, and the underlying COM object is destroyed
unless its reference count has been incremented (via COM methods or
unsafe operations).
If @racket[obj] has already been released, @racket[com-release] has
no effect.}
@defproc[(com-get-active-object [clsid-or-progid (or/c clsid? string?)])
com-object?]{
Like @racket[com-create-instance], but gets an existing
active object (always local) instead of creating a new one.}
@defproc[(com-object-clsid [obj com-object?]) clsid?]{
Returns the @racket{CLSID} of the COM class instantiated by
@racket[obj], or raises an error if the COM class is not known.}
@defproc[(com-object-set-clsid! [obj com-object?] [clsid clsid?]) void?]{
Sets the COM @tech{CLSID} for @racket[obj] to @racket[clsid]. This
is useful when COM event-handling procedures can obtain only
ambiguous information about the object's COM class.}
@defproc[(com-object-eq? [obj1 com-object?] [obj2 com-object?])
boolean?]{
Returns @racket[#t] if @racket[obj1] and @racket[obj2] refer to the
same @tech{COM object}, @racket[#f] otherwise.
If two references to a COM object are the same according to
@racket[com-object-eq?], then they are also the same according to
@racket[equal?]. Two @racket[com-object-eq?] references are not
necessarily @racket[eq?], however.}
@defproc[(com-type? [v any/c]) boolean?]{
Returns @racket[#t] if @racket[v] represents reflective information
about a COM object's type, @racket[#f] otherwise.}
@defproc[(com-object-type [obj com-object?]) com-type?]{
Returns a representation of a COM object's type that is independent of
the object itself.}
@defproc[(com-type=? [t1 com-type?] [t2 com-type?]) boolean?]{
Returns @racket[#t] if @racket[t1] and @racket[t2] represent the same
type information, @racket[#f] otherwise.}
@; ----------------------------------------
@section{COM Methods}
@defproc[(com-methods [obj/type (or/c com-object? com-type?)])
(listof string?)]{
Returns a list of strings indicating the names of methods on
@racket[obj/type].}
@defproc[(com-method-type [obj/type (or/c com-object? com-type?)]
[method-name string?])
(list/c '-> (listof type-description?)
type-description?)]{
Returns a list indicating the type of the specified method in
@racket[obj/type]. The list after the @racket['->] represents the
argument types, and the final value represents the result type. See
@secref["com-types"] for more information.}
@defproc[(com-invoke [obj com-object?] [method-name string?] [v any/c])
any/c]{
Invokes @racket[method-name] on @racket[obj] with @racket[v]s as the
arguments. The special value @racket[com-omit] may be used for
optional arguments, which useful when values are supplied for
arguments after the omitted argument(s).
The types of arguments are determined via @racket[com-method-type],
if possible, and @racket[type-describe] wrappers in the @racket[v]s
are simply replaced with the values that they wrap. If the types are
not available from @racket[com-method-type], then types are inferred
for each @racket[v] with attention to descriptions in any
@racket[type-describe] wrappers in @racket[v].}
@defthing[com-omit any/c]{
A constant for use with @racket[com-invoke] in place of an optional
argument.}
@; ----------------------------------------
@section{COM Properties}
@defproc[(com-get-properties [obj/type (or/c com-object? com-type?)])
(listof string?)]{
Returns a list of strings indicating the names of readable
properties in @racket[obj/type].}
@defproc[(com-get-property-type [obj/type (or/c com-object? com-type?)]
[property-name string?])
(list/c '-> '() type-description?)]{
Returns a type for @racket[property-name] like a result of
@racket[com-method], where the result type corresponds to the
property value type. See @secref["com-types"] for information on the
symbols.}
@defproc[(com-get-property [obj com-object?] [property string?] ...+)
any/c]{
Returns the value of the final property by following the indicated
path of @racket[property]s, where each intermediate property must be a
COM object.}
@defproc[(com-set-properties [obj/type (or/c com-object? com-type?)])
(listof string?)]{
Returns a list of strings indicating the names of writeable
properties in @racket[obj/type].}
@defproc[(com-set-property-type [obj/type (or/c com-object? com-type?)]
[property-name string?])
(list/c '-> (list/c type-description?) 'void)]{
Returns a type for @racket[property-name] like a result of
@racket[com-method], where the sole argument type corresponds to the
property value type. See @secref["com-types"] for
information on the symbols.}
@defproc[(com-set-property! [obj com-object?]
[string? property] ...+
[v any/c])
void?]{
Sets the value of the final property in @racket[obj] to @racket[v]
by following the @racket[property]s, where the value of each
intermediate property must be a COM object.
The type of the property is determined via
@racket[com-property-type], if possible, and
@racket[type-describe] wrappers in @racket[v] are then replaced
with the values that they wrap. If the type is not available from
@racket[com-property-type], then a type is inferred for @racket[v]
with attention to the descriptions in any @racket[type-describe]
wrappers in @racket[v].}
@; ----------------------------------------
@section{COM Events}
@defproc[(com-events [obj/type (or/c com-object? com-type?)])
(listof string?)]{
Returns a list of strings indicating the names of events on
@racket[obj/type].}
@defproc[(com-event-type [obj/type (or/c com-object? com-type?)]
[event-name string?])
(list/c '-> (listof type-description?) 'void)]{
Returns a list indicating the type of the specified events in
@racket[obj/type]. The list after the @racket['->] represents the
argument types. See @secref["com-types"] for more information.}
@defproc[(com-event-executor? [v any/c]) boolean?]{
Returns @racket[#t] if @racket[v] is a @deftech{COM event executor},
which queues event callbacks. A @tech{COM event executor}
@racket[_com-ev-ex] is a synchronizable event in the sense of
@racket[sync], and @racket[(sync _com-ev-ex)] returns a thunk for a
ready callback.}
@defproc[(com-make-event-executor) com-event-executor?]{
Creates a fresh @tech{COM event executor} for use with
@racket[com-register-event-callback].}
@defproc[(com-register-event-callback [obj com-object?]
[name string?]
[proc procedure?]
[com-ev-ex com-event-executor?])
void?]{
Registers a callback for the event named by @racket[name] in
@racket[obj]. When the event fires, an invocation of @racket[proc] to
event arguments (which depends on @racket[obj] and @racket[name]) is
queued in @racket[com-ev-ex]. Synchronizing on @racket[com-ev-ex]
produces a thunk that applies @racket[proc] to the event arguments and
returns the result.
Only one callback can be registered for each @racket[obj] and
@racket[name] combination.
Registration of event callbacks relies on prior registration of the
COM class implemented by @filepath{myssink.dll} as distributed with
Racket. (The DLL is the same for all Racket versions.)}
@defproc[(com-unregister-event-callback [obj com-object?]
[name string?])
void?]{
Removes any existing callback for @racket[name] in @racket[obj].}
@; ----------------------------------------
@section{Interface Pointers}
@deftogether[(
@defproc[(com-object-get-iunknown [obj com-object?]) com-iunkown?]
@defproc[(com-object-get-idispatch [obj com-object?]) com-idispatch?]
)]{
Extracts an @cpp{IUnknown} or @cpp{IDispatch} pointer from
@racket[obj]. The former succeeds for any @tech{COM object} that has
not been relased via @racket[com-release]. The latter succeeds
only when the @tech{COM object} supports @cpp{IDispatch}, otherwise
@racket[exn:fail] is raised.}
@defproc[(com-iunknown? [v any/c]) boolean?]{
Returns @racket[#t] if @racket[v] corresponds to an unsafe
@racket[_IUnknown-pointer], @racket[#f] otherwise. Every @tech{COM
interface} extends @cpp{IUnknown}, so @racket[com-iunknown?] returns
@racket[#t] for every interface pointers.}
@defproc[(com-idispatch? [v any/c]) boolean?]{
Returns @racket[#t] if @racket[v] corresponds to an unsafe
@cpp{IDispatch}, @racket[#f] otherwise.}
@; ----------------------------------------
@section[#:tag "remote"]{Remote COM servers (DCOM)}
The optional @racket[_where] argument to @racket[com-create-instance]
can be @racket['remote]. In that case, the server instance is run at
the location given by the Registry key
@centerline{@tt{HKEY_CLASSES_ROOT\AppID\@nonterm{CLSID}\RemoteServerName}}
where @nonterm{CLSID} is the CLSID of the application. This key may
be set using the @exec{dcomcnfg} utility. From @exec{dcomcnfg}, pick
the application to be run on the @onscreen{Applications} tab, then
click on the @onscreen{Properties} button. On the @onscreen{Location}
tab, choose @onscreen{Run application on the following computer}, and
enter the machine name.
To run a COM remote server, the registry on the client machine must
contain an entry at
@centerline{@tt{HKEY_CLASSES_ROOT\CLSID\@nonterm{CLSID}}}
where @nonterm{CLSID} is the CLSID for the server. The server
application itself need not be installed on the client machine.
There are a number of configuration issues relating to DCOM. See
@centerline{@link["http://www.distribucon.com/dcom95.aspx"]{http://www.distribucon.com/dcom95.html}}
for more information on how to setup client and server machines for DCOM.
@; ----------------------------------------
@section[#:tag "com-types"]{COM Types}
In the result of a function like @racket[com-method-type], symbols are
used to represent various atomic types:
@itemlist[
@item{@racket['int] --- a 32-bit signed integer}
@item{@racket['unsigned-int] --- a 32-bit unsigned integer}
@item{@racket['short] --- a 16-bit signed integer}
@item{@racket['unsigned-short] --- a 16-bit unsigned integer}
@item{@racket['char] --- an 8-bit signed integer}
@item{@racket['unsigned-char] --- an 8-bit unsigned integer}
@item{@racket['long-long] --- a 64-bit signed integer}
@item{@racket['unsigned-long-long] --- a 64-bit unsigned integer}
@item{@racket['float] --- a 32-bit floating-point number}
@item{@racket['double] --- a 64-bit floating-point number}
@item{@racket['currency] --- an exact number that, when multiplied by 10,000,
is a 64-bit signed integer}
@item{@racket['boolean] --- a boolean}
@item{@racket['string] --- a string}
@item{@racket['date] --- a @racket[date] or @racket[date*]}
@item{@racket['com-object] --- a @tech{COM object} as in @racket[com-object?]}
@item{@racket['iunknown] --- like @racket['com-object], but also accepts an @cpp{IUnknown} pointer as in @racket[com-iunknown?]}
@item{@racket['com-enumeration] --- a 32-bit signed integer}
@item{@racket['any] --- any of the above, or an array when not nested in an array type}
@item{@racket['...] --- treated like @racket['any], but when it appears at the end of the sequence of types for
arguments, allows the preceding type 0 or more times}
@item{@racket['void] --- no value}
]
A type symbol wrapped in a list with @racket['box], such as
@racket['(box int)], is a call-by-reference argument. A box supplied
for the argument is updated with a new value when the method returns.
A type wrapped in a list with @racket['opt], such as @racket['(opt
(box int))], is an optional argument. The argument can be omitted or
replaced with @racket[com-omit].
A type wrapped in a list with @racket['array] and a positive exact
integer, such as @racket['(array 7 int)], represents a vector of
values to be used as a COM array. A @racket['?] can be used in place
of the length integer to support a vector of any length. Array types
with non-@racket['?] lengths can be nested to specify a
multidimensional array as represented by nested vectors.
A type wrapped in a list with @racket['variant], such as
@racket['(variant (array 7 int))], is the same as the wrapped type,
but a @racket['variant] wrapper within an @racket['array] type prevents
construction of another array dimension. For example, @racket['(array 2 (array 3
int))] is a two-dimensional array of integers, but @racket['(array 2
(variant (array 3 int)))] is a one-dimensional array whose elements
are one-dimensional arrays of integers.
When type information is not available, functions like @racket[com-invoke]
infer type descriptions from arguments. Inference chooses @racket['boolean]
for booleans; the first of @racket['int], @racket['unsigned-int],
@racket['long-long], @racket['unsigned-long-long] that fits for an exact integer;
@racket['double] for inexact real numbers; @racket['string] for a string;
@racket['com-object] and @racket['iunknown] for corresponding COM object references;
and an @racket['array] type for a vector, where the element type is inferred
from vector values, resorting to @racket['any] if any two elements have different
inferred types.
@defproc[(type-description? [v any/c]) boolean?]{
Return @racket[#t] if @racket[v] is a COM argument or result type
description as above, @racket[#f] otherwise.}
@deftogether[(
@defproc[(type-described? [v any/c]) boolean?]
@defproc[(type-describe [v any/c] [desc type-description?])
type-described?]
@defproc[(type-described-value [td type-described?]) any/c]
@defproc[(type-described-description [td type-described?])
type-description?]
)]{
The @racket[type-described?] predicate recognizes wrappers produced
with @racket[type-describe], and @racket[type-described-value] and
@racket[type-described-description] extract the value and description
parts of a @racket[type-describe] value.
A @racket[type-describe] wrapper combines a base value with a type
description. The description is used instead of an automatically
inferred COM argument type when no type is available for from COM
automation a method for @racket[com-invoke] or a property for
@racket[com-set-property!]. A wrapper can be placed on an immediate
value, or it can be on a value within a box or vector.}
@; ----------------------------------------
@section{Class Display Names}
@defmodule[ffi/com-registry]{The @racketmodname[ffi/com-registry]
library provides a mapping from @tech{coclass} names to @tech{CLSIDs}
for compatibility with the older @tech{MysterX} interface.}
A @deftech{coclass} name corresponds to the display name of a COM
class; the display name is not uniquely mapped to a COM class, and
some COM classes have no display name.
@defproc[(com-all-coclasses) (listof string?)]{
Returns a list of @tech{coclass} strings for all @tech{COM class}es
registered on a system.}
@defproc[(com-all-controls) (listof string?)]{
Returns a list of @tech{coclass} strings for all COM classes in the
system registry that have the @racket["Control"] subkey.}
@deftogether[(
@defproc[(coclass->clsid [coclass string?]) clsid?]
@defproc[(clsid->coclass [clsid clsid?]) string?]
)]{
Converts a @tech{coclass} string to/from a @tech{CLSID}. This
conversion is implemented by an enumeration an @tech{COM class}es from
the system registry.}