On this page:
5.18.2.1 Describing COM Interfaces
define-com-interface
5.18.2.2 Obtaining COM Interface References
Query  Interface
Add  Ref
Release
make-com-object
5.18.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.18.2.4 COM Interface Example
5.18.2 COM Classes and Interfaces 🔗

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

5.18.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 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.18.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.18.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:

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.

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.

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.

Types for the IUnknown COM interface.

procedure

(windows-error msg hresult)  any

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

5.18.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]))