On this page:
5.11.2.1 Describing COM Interfaces
define-com-interface
5.11.2.2 Obtaining COM Interface References
Query  Interface
Add  Ref
Release
make-com-object
5.11.2.3 COM FFI Helpers
_  wfun
_  mfun
_  hfun
_  hmfun
_  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.11.2.4 COM Interface Example
5.11.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.11.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.11.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.11.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 output-expr)

Like _wfun, but for a function that returns an _HRESULT. If the result is not zero, then an error is raised using windows-error and using id as the name of the failed function. Otherwise, output-expr (as in a maybe-racket for _fun) determines the result.

syntax

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

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

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 strign provides the base error message, but hresult and its human-readable interpretation (if available) are added to the message.

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