All Manuals > LispWorks® User Guide and Reference Manual > 15 Java interface

15.3 Calling from Java to Lisp

Calling from Java to Lisp requires the Java class com.lispworks.LispCalls, and Java code that uses methods from this class. Currently all methods are static. com.lispworks.LispCalls is supplied by LispWorks in the file 8-0-0-0/etc/lispcalls.jar, except on Android where it is part of the 8-0-0-0/etc/lispworks.aar file. After Java is initialized, either by an explicit call to init-java-interface or implicitly by the system (for example on Android), you can check whether the Java to Lisp calls are possible (the class LispCalls is available) by using check-lisp-calls-initialized.

There are two mechanisms for calling from Java to Lisp: direct calls and using proxies. Direct calls means calling directly a Lisp function from Java, passing the name of the symbol to funcall and the arguments. Using proxies meaning creating proxies from Lisp, and then passing such Lisp proxies to places where the interface(s) that it implements are required. Invoking a method on such proxy ultimately calls a Lisp function.

Direct calls are simple to use, and if you have a simple Java/Lisp interface can be all that you need. The proxies are needed when you use somebody else's interface, for example implement callbacks to user interaction in Android. They are also useful even if you write the Java side too to make a cleaner interface on the Java side, which is easier to switch between different implementations.

15.3.1 Direct calls

You can make direct calls from Java to Lisp using one of the call<type>[VA] static methods from LispCalls, which have these signatures:

public static int callIntV(String name, Object... args)
public static int callIntA(String name, Object[] args) 
public static double callDoubleV(String name, Object... args)
public static double  callDoubleA(String name, Object[] args)
public static Object callObjectV(String name, Object... args)
public static Object callObjectA(String name, Object[] args)
public static void callVoidV(String name, Object... args)
public static void callVoidA(String name, Object[] args)

The <type> in call<type>[VA] specifies the return type, and V or A specify whether the arguments are supplied as Variable arguments or Array. Otherwise the pairs of V and A methods behave the same.

All these methods apply the Lisp symbol which is named by the name argument to the arguments supplied by the Array or the Variable arguments, and return the result.

Note that on the Lisp side you will need to keep the Lisp symbol when delivering, most conveniently by hcl:deliver-keep-symbols (see the Delivery User Guide), and the name of the symbol is not interpreted using cl:read.

See com.lispworks.LispCalls for full details.

15.3.2 Using proxies

Using proxies allows you to create from inside Lisp a Java proxy which implements one or more Java interfaces. The proxy can then be used whenever an object that implements any of the interfaces is required. When a method is applied to a proxy, it ultimately calls a Lisp function.

Creating a proxy in Lisp is done in two steps:

  1. Defining a proxy, specifying:

    (i) A name (a symbol).

    (ii) The interfaces that it implements.

    (iii) The Lisp functions that get called for each method.

    (iv) A default function.

    (v) Several other options.

    Above, (i) and (ii) are obligatory, the other steps are optional.

    Defining a proxy is done normally at load time by define-lisp-proxy. It is possible to define a proxy at run time using setup-lisp-proxy. For example, defining a proxy that implements the onTouchListener interface, specifying that when the method "onTouch" is invoked it causes the function text-view-on-touch-callback to be called:

    (define-lisp-proxy my-text-view-on-touch-proxy
      ("android.view.View.OnTouchListener"
       ("onTouch" text-view-on-touch-callback)))
    
  2. Making a proxy object using the name of a proxy definition by make-lisp-proxy or make-lisp-proxy-with-overrides, or by calling inside Java the method com.lispworks.LispCalls.createLispProxy. The result of making a proxy is a Java proxy object, which can be used in Java. For example, assuming the definition above and that you have a View in mTextView:
    Object listener = LispCalls.createLispProxy("MY-TEXT-VIEW-ON-TOUCH-PROXY");
    // Check the type of listener to allow for errors in Lisp
    if (listener instanceof View.OnTouchListener)
        mTextView.setOnTouchListener((View.OnTouchListener)listener);
    

This will cause the Lisp function text-view-on-touch-callback to be called whenever the View in mtextView is touched.

Note: the result of make-lisp-proxy or make-lisp-proxy-with-overrides is "local", which means that it cannot be used outside the dynamic scope of the call to Lisp from Java in which it was created. If it is created outside the scope of a call from Java to Lisp, it must be used only in the thread that it was created.

When defining a proxy, you do not need to specify all the methods. You can specify a default function, which is called for any method for which you did not specify a function. See for example the proxy lisp-othello-server-lazy in this example, which does not specify any method, and instead specifies a default function that handles all of them:

(example-edit-file "android/android-othello-user")

When defining a proxy, it is also possible to specify that the Lisp functions should be called with an extra argument user-data, which is associated with each specific proxy by passing :user-data to make-lisp-proxy or make-lisp-proxy-with-overrides. This allows you to link each proxy with some of your data. If you do not specify this option, the functions in the proxy need to use the arguments and global data to decide what to do.

It is also possible to "override" the Lisp function at run time, which means specifying that when a Lisp function for a method should be invoked, another function is invoked instead. Overriding is specified by passing either of :overrides or :overrides-plist to make-lisp-proxy, or by using make-lisp-proxy-with-overrides. The main advantage of overriding is that it allows you to use run time closures, while the proxy definition itself allows only symbols. Overrides are efficient and are simple to use. For example, with the definition above, you can override the callback by:

(let ((closed-something (creating-something)))
  (make-lisp-proxy 'my-text-view-on-touch-proxy 
                   :overrides-plist 
                   (list 'text-view-on-touch-callback 
                         #'(lambda(&rest args)
                             (apply 'callback-with-something
                                    closed-something args)))))

which will cause a touch to invoke callback-with-something on closed-something and args.

Note that this example could easily be done using :user-data instead, but that will have to be specified "statically" in the proxy definition, while overriding can all done dynamically when creating individual proxies.

The Java method com.lispworks.LispCalls.createLispProxy cannot do overriding, it must be done inside Lisp by make-lisp-proxy or make-lisp-proxy-with-overrides.

To make it easier to detect typing errors in specifying the interface names and method names or specifying a Lisp function, the functions verify-lisp-proxies and verify-lisp-proxy are provided to verify all proxies or only one, respectively. Verification checks that all the specified functions are actually defined, and optionally also that all the methods that are declared in the interfaces are defined. The latter check must be done with Java running. You will typically use it when starting the application to check that all the proxies are OK, at least during the development phase.

The Lisp functions of the proxy are ordinary Lisp, but they need to return the correct value, unless the method has Void as its return type. Returning the wrong value will call the java-to-lisp-debugger-hook (see init-java-interface) with an appropriate condition, and then return zero of the correct type (that is 0, 0d0, 0f0, Java false, or Java null) from the method call.

The call to the Lisp function is wrapped such that trying to throw out of it does not actually finish the throw, and instead returns zero of the correct type from the method call.

In some cases the method needs to throw some exception. The function throw-an-exception can be used to throw an exception from inside a call to a proxy function.


LispWorks® User Guide and Reference Manual - 01 Dec 2021 19:30:20