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))
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")
In parameters are passed as positional arguments in the order they are specified and do not affect the return values.
string
attribute can be passed either as a foreign pointer or as a Lisp string (converted to a foreign string with dynamic extent for the duration of the call).size_is
attribute can be passed either as a foreign pointer or, if the element type is not a foreign aggregate type, as a Lisp array of the appropriate rank (converted to a foreign array with dynamic extent for the duration of the call).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.
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.
nil
then a null pointer is passed to the method.string
attribute is converted to a Lisp string if the keyword is not passed. If the keyword is passed, the memory for the string might need to be freed by co-task-mem-free if nothing else does this.size_is
attribute will be converted to a Lisp array if the keyword is not passed and the element type is not a foreign aggregate type. If the keyword argument is not passed then a new Lisp array is made. If the value of the keyword argument is a Lisp array then that is filled.struct
, the keyword argument must be passed and its value must be as a foreign pointer. This pointer is passed directly to the method.iid_is
attribute, a com-interface pointer is returned using the indicated iid parameter to control the interface name.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)))))
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.
nil
then a null pointer is passed to the COM call. The positional argument should be nil
is these cases. If the keyword argument not passed, a foreign object with dynamic extent is created to contain the value, initialized with data from the positional argument before calling the method and possibly converted back to a Lisp value on return.string
attribute, the positional argument is handled as for the in argument string case and the keyword argument is handled as for the out argument string case. The functions co-task-mem-alloc and co-task-mem-free should be used to manage the memory for the string itself.size_is
attribute, the positional argument is handled as for the in argument array case and the keyword argument is handled as for the out argument array case. To update an existing array, pass it as both the positional and keyword argument values.nil
.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 ))
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