5.12.2 COM Classes and Interfaces

► |  | The Racket Foreign Interface

1 | Overview
2 | Loading Foreign Libraries
3 | C Types
4 | Pointer Functions
5 | Derived Utilities
6 | Miscellaneous Support
7 | Unexported Primitive Functions
| Bibliography
| Index

► | 5 | Derived Utilities

5.1 | Safe Homogenous Vectors
5.2 | Safe C Vectors
5.3 | Tagged C Pointer Types
5.4 | Serializable C Struct Types
5.5 | Defining Bindings
5.6 | Allocation and Finalization
5.7 | Custodian Shutdown Registration
5.8 | Atomic Execution
5.9 | Speculatively Atomic Execution
5.10 | Objective-C FFI
5.11 | Cocoa Foundation
5.12 | COM (Common Object Model)
5.13 | File Security-Guard Checks
5.14 | Windows API Helpers

▼ | 5.12 | COM (Common Object Model)

5.12.1 | COM Automation
5.12.2 | COM Classes and Interfaces
5.12.3 | Active X Controls

► | 5.12.2 | COM Classes and Interfaces

5.12.2.1 | Describing COM Interfaces
5.12.2.2 | Obtaining COM Interface References
5.12.2.3 | COM FFI Helpers
5.12.2.4 | COM Interface Example

On this page:

5.12.2.1`` Describing COM Interfaces
define-com-interface
5.12.2.2`` Obtaining COM Interface References
Query Interface
Add Ref
Release
make-com-object
5.12.2.3`` COM FFI Helpers
_ wfun
_ mfun
_ hfun
_ hmfun
current-hfun-retry-count
current-hfun-retry-delay
HRESULT-retry?
_ GUID
_ GUID-pointer
_ HRESULT
_ LCID
LOCALE_ SYSTEM_ DEFAULT
Sys Free String
Sys Alloc String Len
IID_ NULL
IID_ IUnknown
_ IUnknown
_ IUnknown-pointer
_ IUnknown_ vt
windows-error
5.12.2.4`` COM Interface Example

top ← prev up next →

##### 5.12.2`` COM Classes and Interfaces

(require ffi/unsafe/com) | package: base

The ffi/unsafe/com library exports all of ffi/com, and it also supports direct, FFI-based calls to COM object methods.

##### 5.12.2.1`` Describing COM Interfaces

> > syntax
> > (define-com-interface (_id _super-id)
> > ([method-id ctype-expr maybe-alloc-spec] ...))
> maybe-alloc-spec |  | = |  |
> |  | | |  | #:release-with-function function-id
> |  | | |  | #:release-with-method method-id
> |  | | |  | #:releases

Defines _id as an interface that extends _super-id, where _super-id is often _IUnknown, and that includes methods named by method-id. The _id and _super-id identifiers must start with an underscore. A _super-id _vt must also be defined for deriving a virtual-method table type.

The order of the method-ids must match the specification of the COM interface, not including methods inherited from _super-id. Each method type produced by ctype-expr that is not _fpointer must be a function type whose first argument is the “self” pointer, usually constructed with _mfun or _hmfun.

The define-com-interface form binds _id, id ?, _id -pointer, _id _ vt (for the virtual-method table), _id _ vt-pointer, and method-id for each method whose ctype-expr is not _fpointer. (In other words, use _fpointer as a placeholder for methods of the interface that you do not need to call.) An instance of the interface will have type _id -pointer. Each defined method-id is bound to a function-like macro that expects a _id -pointer as its first argument and the method arguments as the remaining arguments.

A maybe-alloc-spec describes allocation and finalization information for a method along the lines of ffi/unsafe/alloc. If the maybe-alloc-spec is #:release-with-function function-id, then function-id is used to deallocate the result produced by the method, unless the result is explictly deallocated before it becomes unreachable; for exmaple, #:release-with-function Release is suitable for a method that returns a COM interface reference that must be eventually released. The #:release-with-method method-id form is similar, except that the deallocator is a method on the same object as the allocating method (i.e., one of the other method-ids or an inherited method). A #:releases annotation indicates that a method is a deallocator (so that a value should not be automatically deallocated if it is explicitly deallocated using the method).

See COM Interface Example for an example using define-com-interface.

##### 5.12.2.2`` Obtaining COM Interface References

> > procedure
> > (QueryInterface |  | iunknown |  |  |  |
> > |  | iid |  |  |  |
> > |  | intf-pointer-type) |  | → |  | (or/c cpointer? #f)
> iunknown : com-iunknown?
> iid : iid?
> intf-pointer-type : ctype?

Attempts to extract a COM interface pointer for the given COM object. If the object does not support the requested interface, the result is #f, otherwise it is cast to the type intf-pointer-type.

Specific IIDs and intf-pointer-types go together. For example, IID_IUnknown goes with _IUnknown-pointer.

For a non-#f result, Release function is the automatic deallocator for the resulting pointer. The pointer is register with a deallocator after the cast to intf-pointer-type, which is why QueryInterface accepts the intf-pointer-type argument (since a cast generates a fresh reference).

> procedure (AddRef iunknown) → exact-positive-integer?
> iunknown : com-iunknown?
> procedure (Release iunknown) → exact-nonnegative-integer?
> iunknown : com-iunknown?

Increments or decrements the reference count on iunknown, returning the new reference count and releasing the interface reference if the count goes to zero.

> > procedure
> > (make-com-object |  | iunknown |  |  |  |
> > |  | clsid |  |  |  |
> > | [ | #:manage? manage?]) |  | → |  | com-object?
> iunknown : com-iunknown?
> clsid : (or/c clsid? #f)
> manage? : any/c = #t

Converts a COM object into an object that can be used with the COM automation functions, such as com-invoke.

If manage? is true, the resulting object is registered with the current custodian and a finalizer to call com-release when the custodian is shut down or when the object becomes inaccessible.

##### 5.12.2.3`` COM FFI Helpers

> > syntax
> > (_wfun fun-option ... maybe-args type-spec ... -> type-spec
> > maybe-wrapper)

Like _fun, but adds #:abi winapi.

> > syntax
> > (_mfun fun-option ... maybe-args type-spec ... -> type-spec
> > maybe-wrapper)

Like _wfun, but adds a _pointer type (for the “self” argument of a method) as the first argument type-spec.

> syntax (_hfun fun-option ... type-spec ... -> id maybe-allow output-expr)
> maybe-allow |  | = |  |
> |  | | |  | #:allow [result-id allow?-expr]

Like _wfun, but for a function that returns an _HRESULT. The result is bound to result-id if #:allow is specified, otherwise the result is not directly accessible.

The _hfun form handles the _HRESULT value of the foreign call as follows:

- If the result is zero or if #:allow is specified and allow?-expr produces #t, then output-expr (as in a maybe-wrapper for _fun) determines the result.

- If the result is RPC_E_CALL_REJECTED or RPC_E_SERVERCALL_RETRYLATER, the call is autmatically retried up to (current-hfun-retry-count) times with a delay of (current-hfun-retry-delay) seconds between each attempt.

- Otherwise, an error is raised using windows-error and using id as the name of the failed function.

Changed in version 6.2 of package base: Added #:allow and automatic retries.

> syntax (_hmfun fun-option ... type-spec ... -> id output-expr)

Like _hfun, but lke _mfun in that _pointer is added for the first argument.

> parameter (current-hfun-retry-count) → count
> (current-hfun-retry-count exact-nonnegative-integer?) → void?
> exact-nonnegative-integer? : count
> parameter (current-hfun-retry-delay) → (>=/c 0.0)
> (current-hfun-retry-delay secs) → void?
> secs : (>=/c 0.0)

Parameters that determine the behavior of automatic retries for _hfun.

Added in version 6.2 of package base.

> procedure (HRESULT-retry? r) → boolean?
> r : exact-nonnegative-integer?

Returns #t if r is RPC_E_CALL_REJECTED or RPC_E_SERVERCALL_RETRYLATER, #f otherwise.

Added in version 6.2 of package base.

> value _GUID : ctype?
> value _GUID-pointer : ctype?
> value _HRESULT : ctype?
> value _LCID : ctype?

Some C types that commonly appear in COM interface specifications.

> value LOCALE_SYSTEM_DEFAULT : exact-integer?

The usual value for a _LCID argument.

> procedure (SysFreeString str) → void?
> str : _pointer
> procedure (SysAllocStringLen content len) → cpointer?
> content : _pointer
> len : integer?

COM interfaces often require or return srings that must be allocated or freed as system strings.

When receiving a string value, cast it to _string/utf-16 to extract a copy of the string, and then free the original pointer with SysFreeString.

> value IID_NULL : iid?
> value IID_IUnknown : iid?

Commonly used IIDs.

> value _IUnknown : ctype?
> value _IUnknown-pointer : ctype?
> value _IUnknown_vt : ctype?

Types for the IUnknown COM interface.

> procedure (windows-error msg hresult) → any
> msg : string?
> hresult : exact-integer?

Raises an exception. The msg strign provides the base error message, but hresult and its human-readable interpretation (if available) are added to the message.

##### 5.12.2.4`` COM Interface Example

Here’s an example using the Standard Component Categories Manager to enumerate installed COM classes that are in the different system-defined categories. The example illustrates instantiating a COM class by CLSID, describing COM interfaces with define-com-interface, and using allocation specifications to ensure that resources are reclaimed even if an error is encountered or the program is interrupted.

> #lang racket/base
> (require ffi/unsafe
> ffi/unsafe/com)
> (provide show-all-classes)
> ; The function that uses COM interfaces defined further below:
> (define (show-all-classes)
> (define ccm
> (com-create-instance CLSID_StdComponentCategoriesMgr))
> (define icat (QueryInterface (com-object-get-iunknown ccm)
> IID_ICatInformation
> _ICatInformation-pointer))
> (define eci (EnumCategories icat LOCALE_SYSTEM_DEFAULT))
> (for ([catinfo (in-producer (lambda () (Next/ci eci)) #f)])
> (printf "~a:\n"
> (cast (array-ptr (CATEGORYINFO-szDescription catinfo))
> _pointer
> _string/utf-16))
> (define eg
> (EnumClassesOfCategories icat (CATEGORYINFO-catid catinfo)))
> (for ([guid (in-producer (lambda () (Next/g eg)) #f)])
> (printf " ~a\n" (or (clsid->progid guid)
> (guid->string guid))))
> (Release eg))
> (Release eci)
> (Release icat))
> ; The class to instantiate:
> (define CLSID_StdComponentCategoriesMgr
> (string->clsid "{0002E005-0000-0000-C000-000000000046}"))
> ; Some types and variants to match the specification:
> (define _ULONG _ulong)
> (define _CATID _GUID)
> (define _REFCATID _GUID-pointer)
> (define-cstruct _CATEGORYINFO ([catid _CATID]
> [lcid _LCID]
> [szDescription (_array _short 128)]))
> ; —— IEnumGUID ——-
> (define IID_IEnumGUID
> (string->iid "{0002E000-0000-0000-C000-000000000046}"))
> (define-com-interface (_IEnumGUID _IUnknown)
> ([Next/g (_mfun (_ULONG = 1) ; simplifed to just one
> (guid : (_ptr o _GUID))
> (got : (_ptr o _ULONG))
> -> (r : _HRESULT)
> -> (cond
> [(zero? r) guid]
> [(= r 1) #f]
> [else (windows-error "Next/g failed" r)]))]
> [Skip _fpointer]
> [Reset _fpointer]
> [Clone _fpointer]))
> ; —— IEnumCATEGORYINFO ——-
> (define IID_IEnumCATEGORYINFO
> (string->iid "{0002E011-0000-0000-C000-000000000046}"))
> (define-com-interface (_IEnumCATEGORYINFO _IUnknown)
> ([Next/ci (_mfun (_ULONG = 1) ; simplifed to just one
> (catinfo : (_ptr o _CATEGORYINFO))
> (got : (_ptr o _ULONG))
> -> (r : _HRESULT)
> -> (cond
> [(zero? r) catinfo]
> [(= r 1) #f]
> [else (windows-error "Next/ci failed" r)]))]
> [Skip _fpointer]
> [Reset _fpointer]
> [Clone _fpointer]))
> ; —— ICatInformation ——-
> (define IID_ICatInformation
> (string->iid "{0002E013-0000-0000-C000-000000000046}"))
> (define-com-interface (_ICatInformation _IUnknown)
> ([EnumCategories (_hmfun _LCID
> (p : (_ptr o _IEnumCATEGORYINFO-pointer))
> -> EnumCategories p)]
> [GetCategoryDesc (_hmfun _REFCATID _LCID
> (p : (_ptr o _pointer))
> -> GetCategoryDesc
> (begin0
> (cast p _pointer _string/utf-16)
> (SysFreeString p)))]
> [EnumClassesOfCategories (_hmfun (_ULONG = 1) ; simplifed
> _REFCATID
> (_ULONG = 0) ; simplifed
> (_pointer = #f)
> (p : (_ptr o
> _IEnumGUID-pointer))
> -> EnumClassesOfCategories p)
> #:release-with-function Release]
> [IsClassOfCategories _fpointer]
> [EnumImplCategoriesOfClass _fpointer]
> [EnumReqCategoriesOfClass _fpointer]))

top ← prev up next →