Defines a Lisp proxy.
lw-ji
define-lisp-proxy name &body interface-and-method-descs => name
name⇩ |
A non-nil symbol. |
interface-and-method-descs⇩ | |
A body of Lisp code. |
name |
A non-nil symbol. |
The macro define-lisp-proxy
defines a Lisp proxy, which means creating a Lisp proxy definition and attaching it to name, which can then be used to create Lisp proxies, which are Java proxies where methods invocation ends up calling Lisp functions.
define-lisp-proxy
parses interface-and-method-descs to a proxy definition, and attaches it to name. This operation is a "load-time" operation: it does not require running Java, and does not create any proxy. The name can then be used at run time as argument to make-lisp-proxy or make-lisp-proxy-with-overrides, or to the Java method com.lispworks.LispCalls.createLispProxy. The result of any these calls is a proxy that implements the interfaces listed in interface-and-method-descs, and can be used in Java whenever an object that implements any of these interfaces is required.
interface-and-method-descs describes the Java interfaces to implement and the Lisp functions to call. It is parsed as a body of Lisp forms.
Each element in the list must be either a string which is the Java interface name, or a list where the cl:car is the Java interface name. Each item specifies a Java interface to implement, except that one item (at most) may specify options relating to the whole proxy definition, by using a list starting with the keyword :options
instead of giving an interface name.
When the item is a list starting with an interface name, the rest of the list are method specifications. Note that you do not need to have a method specification for each method of the interface.
Each method specification must be a list, where the first element is a string with the name of the Java method, and the second element is the symbol specifying what Lisp function to call for this method. The symbol specifies the function to call except when it is overridden (see below about "Overriding"). In some cases, you will want to always override the function to call (typically when you want to use a closure as the function), in which case the symbol can be and should be a keyword (which is ignored by the verifying functions), but does not have to be. See below for how the calling of the Lisp function is done.
The rest of the method specification can contain keyword/value pairs. Currently, the only supported keyword is :with-user-data
, which takes a boolean value, overrides the default value of :with-user-data
of the proxy definition. The default of :with-user-data
of the definition defaults to nil
, and can be changed in the :options
. The value of :with-user-data
specifies whether to pass the user-data of a proxy to the Lisp function.
The :options
item is specified by an item in interface-and-method-descs where the cl:car is the keyword :options
. The rest of the item is keyword/value pairs. The keywords currently supported are:
:default-function |
Specifies the default function to call for methods which do not have a Lisp function. This function is applied to the arguments of the method preceded by the method-name, and if The default function can be overridden by make-lisp-proxy and make-lisp-proxy-with-overrides. |
:default-function-with-user-data | |
A boolean specifying whether the user-data of a proxy should be passed when the default function is called. When it is non-nil, the user-data is passed as the first argument to the default-function (or the function that overrides it). The default value of | |
:with-user-data |
A boolean specifying whether the default for calling functions in the proxy definition is with user-data or not. Each method description can override it as described above. The default value of |
:print-name | Must be a string or a symbol. Specifies the first part of the print-name of each proxy. |
:jobject-scope |
One of
If you use
The default value of |
The user-data is set up for each individual proxy object by make-lisp-proxy or make-lisp-proxy-with-overrides, and thus allows you to associate each individual proxy with an arbitrary Lisp object. The proxy definition determines whether to use it when calling the Lisp functions in the proxy definition. The default value of user-data is nil
, so if you want to use it you need to specify it by using :with-user-data
, either in the :options
which would give the default value for all calls in the definition, or in individual method specifications. When user-data is passed, it is always passed to the Lisp function as the first argument. Another way to individualize proxies is to use overriding, which also allows you to use closures.
When make-lisp-proxy or make-lisp-proxy-with-overrides make a proxy, they can specify overriding of some of the symbols in the proxy definition. Overriding here means mapping one symbol to to another symbol or a function object. When a symbol is supposed to be called and it is overridden, the target of the mapping is called rather than the symbol. Note that the overriding is specific to each individual proxy rather to the proxy definition, and therefore you can have different proxies using the same proxy definition (and hence implementing the same interfaces), but calling different Lisp functions. An advantage of overriding is that it allows you to use closures created at run time instead of symbols.
See the documentation for make-lisp-proxy for how the overriding is created.
After a proxy is created from a proxy definition, any invocation of a Java method on it (except the Object methods toString
, equals
and hashCode
) enters Lisp.
When a method is invoked on a proxy (normally from Java, but can be done from Lisp too), the steps for invoking your Lisp function are:
Note that that if the first step above found a method specification, and it contains the keyword :jobject-scope
, it affects the way non-primitive arguments are processed as described above.
(i) If a method specification was found in the first step above:
(a) Take the symbol from the method specification, then:
(b) Check whether the symbol is overridden, and if it is use the target as a function to call. Otherwise, check whether the symbol is fbound, and if it is use it as a function to call, then:
(c) If as a result of (b) there is a function to call, check whether it should be called with the user-data. If :with-user-data
was used in the method specification then use its value, otherwise if :with-user-data
was used in :options
item use this value, otherwise default to nil
, then:
(d) Apply the function: if using user-data, apply the function to the user-data followed by the Lisp arguments, otherwise apply the function to the Lisp arguments only.
(ii) If the method-specific call in (i) did not happen (no method specification found, or the symbol is not fbound and not overridden), try to apply the default function:
(a) If there is a default function, check whether it is overridden and if so use the target as the function to call. Otherwise use the default-function itself as the function to call, then:
(b) Check whether need to pass user-data, which is specified by the :default-function-with-user-data
in the :options
item, then:
(c) Apply the function: if user-data needs to be used, apply the function to the user-data, method-name and the Lisp arguments. Otherwise apply the function to the method-name and Lisp arguments.
(iii) If the calls in (i) and (ii) did not happen, an error is signaled. See handling of errors below.
The call to the Lisp function is wrapped dynamically such that any throw from it is blocked, and the default value as in the last step above is returned.
In addition, there is a debugger wrapper (using with-debugger-wrapper) which calls the java-to-lisp-debugger-hook (see init-java-interface) with the condition and then calls cl:abort. If this abort is not caught by your cl:abort restart, it is handled by the "throwing blocker" from the previous paragraph, that is the Java method returns 0 or null.
The verification functions verify-lisp-proxies and verify-lisp-proxy are provided to allow you to do some checking of the correctness of your proxy definition. Two things can be verified:
Note that neither of these issues is actually an error, because the default function can handle them. However it is useful to check them in case you did miss something.
There is a little overhead associated with using setup-lisp-proxy as opposed to define-lisp-proxy
, both in the size of the delivered application (very small) and in run time, but the difference is not large enough to prevent using setup-lisp-proxy when it is appropriate.
There is an overhead associated with initializing a proxy definition. It is therefore a bad idea to use setup-lisp-proxy many times.
Overrides and using multiple interfaces add a negligible overhead.
:jobject-scope
with nil
or :local
are useful optimizations. In proxies that are invoked infrequently, say less than 10 times each second, the difference is probably insignificant, but it is useful for proxies that are called repeatedly by Java code. For example, if you implement the interface "java.io.FilenameFilter"
to pass to "java.io.File.list"
on large directories, using :jobject-scope :local
or nil
will reduce the overhead significantly.
(example-edit-file "android/android-othello-user")
make-lisp-proxy
make-lisp-proxy-with-overrides
verify-lisp-proxy
verify-lisp-proxies
check-lisp-calls-initialized
15.3.2 Using proxies
15.3.1 Direct calls
LispWorks® User Guide and Reference Manual - 01 Dec 2021 19:30:46