5.17.2 COM Classes and Interfaces
(require ffi/unsafe/com) | package: base |
5.17.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
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 explicitly deallocated before it becomes unreachable; for example, #: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.17.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?
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?
procedure
(make-com-object iunknown clsid [ #:manage? manage?]) → com-object? iunknown : com-iunknown? clsid : (or/c clsid? #f) manage? : any/c = #t
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.17.2.3 COM FFI Helpers
syntax
(_wfun fun-option ... maybe-args type-spec ... -> type-spec maybe-wrapper)
syntax
(_mfun fun-option ... maybe-args type-spec ... -> type-spec maybe-wrapper)
syntax
(_hfun fun-option ... type-spec ... -> id maybe-allow output-expr)
maybe-allow =
| #:allow [result-id allow?-expr]
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)
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)
Added in version 6.2 of package base.
procedure
(HRESULT-retry? r) → boolean?
r : exact-nonnegative-integer?
Added in version 6.2 of package base.
procedure
(SysFreeString str) → void?
str : _pointer
procedure
(SysAllocStringLen content len) → cpointer?
content : _pointer len : integer?
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
value
value
value
value
procedure
(windows-error msg hresult) → any
msg : string? hresult : exact-integer?
5.17.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]))