All Manuals > COM/Automation User Guide and Reference Manual > 1 Using COM

1.8 Calling COM interface methods

The macros call-com-interface and with-com-interface are used to call COM methods. To call a COM method, you need to specify the interface name, the method name, a COM interface pointer and suitable arguments. The interface and method names are given as symbols named as in 1.3 The mapping from COM names to Lisp symbols and the COM interface pointer is a foreign pointer of type com-interface. In both macros, the args and values are as specified in the 1.8.1 Data conversion when calling COM methods.

The with-com-interface macro is useful when several methods are being called with the same COM interface pointer, because it establishes a local macro that takes just the method name and arguments.

For example, the following are equivalent ways of calling the move and resize methods of a COM interface pointer window-ptr for the i-window interface:

(progn
  (call-com-interface (window-ptr i-window move) 10 10)
  (call-com-interface (window-ptr i-window resize) 100 100))
(with-com-interface (call-window-ptr i-window) window-ptr
  (call-window-ptr move 10 10)
  (call-window-ptr resize 100 100))

1.8.1 Data conversion when calling COM methods

All IDL definitions map onto FLI definitions, mirroring the mapping that midl.exe does for C/C++. However, IDL provides some additional type information that C/C++ lacks (for instance the string attribute), so there are some additional conversions that Lisp performs when it can.

The COM API uses the information from the IDL to convert data between FLI types and Lisp types where appropriate for arguments and return values of COM method calls. In particular:

In COM, all parameters have a direction which can be either in, out or both in and out (referred to as in-out here). Arguments and values for client-side COM method calls reflect the direction as described in the following sections. For a complete version of the example code, see the file:

(example-edit-file "com/manual/args/args-calling")
1.8.1.1 In parameters

In parameters are passed as positional arguments in the order they are specified and do not affect the return values.

For example, given the IDL:

import "unknwn.idl";
 
[ object,
  uuid(E37A70A0-EFC9-11D5-BF02-000347024BE1)
]
interface IArgumentExamples : IUnknown
{
  typedef [string] char *argString;
 
  HRESULT inMethod([in] int inInt,
                   [in] argString inString,
                   [in] int inArraySize,
                   [in, size_is(inArraySize)] int *inArray);
}

the method in-method can be called with Lisp objects like this:

(let ((array #(7 6)))
  (call-com-interface (arg-example i-argument-examples
                                   in-method)
                      42
                      "the answer"
                      (length array)
                      array))

or with foreign pointers like this:

(fli:with-dynamic-foreign-objects ()
  (let* ((farray-size 2)
         (farray (fli:allocate-dynamic-foreign-object
                  :type :int
                  :nelems farray-size
                  :initial-contents '(7 6))))
    (fli:with-foreign-string (fstring elt-count byte-count)
        "the answer"
      (declare (ignore elt-count byte-count))
      (call-com-interface (arg-example i-argument-examples
                                       in-method)
                          42
                          fstring
                          farray-size
                          farray))))

Note that the int arguments are always passed as Lisp integer because int is a primitive type.

1.8.1.2 Out parameters

Out parameters are always of type pointer in COM and never appear as positional arguments in the Lisp call. Instead, there is a keyword argument named after the parameter, which can be used to pass an object to be modified by the method. In addition, each out parameter generates a return value, which will be eq to the value of keyword argument if it was passed and otherwise depends on the type of the parameter as described below.

For example, given the IDL:

import "unknwn.idl";
 
[ object,
  uuid(E37A70A0-EFC9-11D5-BF02-000347024BE1)
]
interface IArgumentExamples : IUnknown
{
  typedef [string] char *argString;
 
  HRESULT outMethod([out] int *outInt,
                    [out] argString *outString,
                    [in] int outArraySize,
                    [out, size_is(outArraySize)] int *outArray);
}

the method out-method can return Lisp objects like this:

(multiple-value-bind (hres int string array)
    (call-com-interface (arg-example i-argument-examples
                                     out-method)
                        8)
  ;; int is of type integer
  ;; string is of type string
  ;; array is of type array
  )

or fill an existing array like this:

(let ((out-array (make-array 5)))
  (multiple-value-bind (hres int string array)
      (call-com-interface (arg-example i-argument-examples
                                       out-method)
                          (length out-array)
                          :out-array out-array)
    ;; int is of type integer
    ;; string is of type string
    ;; array is eq to out-array and was filled
    ))

or set the contents of foreign memory like this:

(fli:with-dynamic-foreign-objects ((out-int :int)
                                   (out-string WIN32:LPSTR))
  (let* ((out-farray-size 5)
         (out-farray (fli:allocate-dynamic-foreign-object
                      :type :int
                      :nelems out-farray-size)))
    (multiple-value-bind (hres int string array)
        (call-com-interface (arg-example i-argument-examples
                                         out-method)
                            out-farray-size
                            :out-int out-int
                            :out-string out-string
                            :out-array out-farray)
      ;; Each foreign pointer contains the method's results
      ;; int is the foreign pointer out-int
      ;; string is the foreign pointer out-string
      ;; array is the foreign pointer out-array
      ;; Note that the string must be freed as follows:
      (co-task-mem-free (fli:dereference out-string)))))
1.8.1.3 In-out parameters

In-out parameters are always of type pointer in COM and are handled as a mixture of in and out. In particular, they have both a positional parameter and a keyword parameter, which can be used to control the value passed and conversion of the value returned respectively. Each in-out parameter generates a return value, which will be eq to the value of the keyword argument if it was passed and otherwise depends on the type of the parameter as below.

For example, given the IDL:

import "unknwn.idl";
 
[ object,
  uuid(E37A70A0-EFC9-11D5-BF02-000347024BE1)
]
interface IArgumentExamples : IUnknown
{
  typedef [string] char *argString;
 
  HRESULT inoutMethod([in, out] int *inoutInt,
                      [in, out] argString *inoutString,
                      [in] int inoutArraySize,
                      [in, out, size_is(inoutArraySize)]
                      int *inoutArray);
}

the method inout-method can receive and return Lisp objects like this:

(let ((in-array #(7 6)))
  (multiple-value-bind (hres int string array)
      (call-com-interface (arg-example i-argument-examples
                          inout-method)
                          42
                          "the answer"
                          (length in-array)
                          in-array)
    ;; int is of type integer
    ;; string is of type string
    ;; array is of type array
    ))

or fill an existing array like this:

(let* ((in-array #(7 6))
       (out-array (make-array (length in-array))))
  (multiple-value-bind (hres int string array)
      (call-com-interface (arg-example i-argument-examples
                                       inout-method)
                          42
                          "the answer"
                          (length in-array)
                          in-array
                          :inout-array out-array)
    ;; int is of type integer
    ;; string is of type string
    ;; array is eq to out-array, which was filled
    ))

or update an existing array like this:

(let* ((inout-array #(7 6)))
  (multiple-value-bind (hres int string array)
      (call-com-interface (arg-example i-argument-examples
                                       inout-method)
                          42
                          "the answer"
                          (length inout-array)
                          inout-array
                          :inout-array inout-array)
    ;; int is of type integer
    ;; string is of type string
    ;; array is eq to inout-array, which was updated
    ))

1.8.2 Error handling

Most COM methods return an integer hresult to indicate success or failure, which can be checked using succeeded, s_ok, hresult-equal or check-hresult.

In addition, after calling a COM method that provides extended error information, you can call the function get-error-info to obtain more details of any error that occurred. This is supplied with a list of fields, which should be keywords specifying the parts of the error information to obtain.

For example, in the session below, tt is a COM interface pointer for the i-test-suite-1 interface:

CL-USER 186 > (call-com-interface (tt i-test-suite-1 fx))
 
"in fx"           ;; implementation running
-2147352567       ;; the error code DISP_E_EXCEPTION
 
CL-USER 187 > (get-error-info :fields '(:description
                                        :source))
("foo" "fx")
 
CL-USER 188 > 

COM/Automation User Guide and Reference Manual - 01 Dec 2021 19:38:38