jfli - a Java Foreign Language Interface for Common Lisp

Copyright 2004 (c) Rich Hickey. All rights reserved.
Updates copyright 2008 (c) Nick Levine. All rights reserved.

The use and distribution terms for this software are covered by the Common Public License 1.0, which can be found in the file CPL.TXT at the root of this distribution. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must not remove this notice, or any other, from this software.

Contents

Introduction

My objective was to provide comprehensive, safe, dynamic and Lisp-y access to Java and Java libraries as if they were Lisp libraries, for use in Lisp programs, i.e. with an emphasis on working in Lisp rather than in Java.

The approach I took was to embed a JVM instance in the Lisp process using JNI. I was able to do this using LispWorks' own FLI and no C (or Java! *) code, which is a tribute to the LW FLI. On top of the JNI layer (essentially a wrapper around the entire JNI API), I built this user-level API using Java Reflection. This first version was built with, and contains code specific to, LispWorks.

jfli ("jay fly") provides:

jfli was built using LWM and LWW (using Apple's and Sun's JVMs respectively), and it works fine on both. More recently (2007) it's been tested extensively on LWL. It should be a trivial port to other LispWorks, and a possible port to any Common Lisp with a robust FLI. It should also work with any JVM with a conformant JNI implementation.

Download

jfli is hosted on SourceForge.

Setup and Configuration

jfli comprises: a small system of three Lisp files (and an accompanying defsys file), a system building utility (build-java-classes.lisp), and an optional Java .jar file. The first Lisp file, jni.lisp, defines a low-level API to the Java Native Interface, and is not documented here. The second, jfli.lisp, depends upon jni.lisp, and provides the user API documented here. Finally process.lisp provides recyclable processes.

To get started:

  1. (load "defsys.lisp")
  2. (compile-system 'jfli :load t)
  3. (use-package :jfli)
  4. prior to creating the JVM you must tell the library how to find the Java JNI library by setting *jni-lib-path*.

If you wish to allow for callbacks from Java to Lisp, you must place jfli.jar in your classpath when creating the JVM.

Quick Start

This sample session presumes you have already compiled the jfli system.

CL-USER 4 > (load "/lisp/defsys")
; Loading lisp file C:\lisp\defsys.lisp
#P"C:/lisp/defsys.lisp"

CL-USER 5 > (load-system 'jfli)
; Loading fasl file c:\lisp\jni.ofasl
; Loading fasl file c:\lisp\jfli.ofasl
; Loading fasl file c:\lisp\process.ofasl
(JFLI)

;The user API is entirely in the jfli package
CL-USER 6 > (use-package :jfli)
T

;tell the library where Java is located
CL-USER 7 > (setf *jni-lib-path* "/j2sdk1.4.2_01/jre/bin/client/jvm.dll")
"/j2sdk1.4.2_01/jre/bin/client/jvm.dll"

;this starts the VM and sets the class path (to the default plus
;jfli.jar, in this case)
CL-USER 8 > (connect-jvm ())
0
#<Pointer: JNI:PVM = #x081022A0>
#<Pointer: JNI:PENV = #x0086A858>

;define wrappers for the members of Object
CL-USER 9 > (def-java-class "java.lang.Object")
NIL

;and of Properties, a Hashtable-like class
CL-USER 10 > (def-java-class "java.util.Properties")
#<STANDARD-CLASS |java.util|:PROPERTIES. 2066B964>

;the above will create these packages if they do not already exist
;use the packages for easy name access

CL-USER 11 > (use-package "java.lang")
T

CL-USER 12 > (use-package "java.util")
T

;create a Properties instance, note keyword-style member inits, string conversion etc
;also note typed return value
CL-USER 13 > (setf p (new properties. :getproperty "fred" "ethel"))
#<PROPERTIES. 20664A94>

;virtual functions work as normal
CL-USER 14 > (object.tostring p)
"{fred=ethel}"

;setter was generated for member function because it follows the JavaBeans property protocol
CL-USER 15 > (setf (properties.getproperty p "ricky") "lucy")
"lucy"

CL-USER 16 > (object.tostring p)
"{ricky=lucy, fred=ethel}"

CL-USER 17 > (properties.size p)
2

;totally dynamic access, create wrappers as you need
CL-USER 18 > (def-java-class "java.lang.Class")
#<STANDARD-CLASS CLASS. 20680EC4>

CL-USER 19 > (class.getname (object.getclass p))
"java.util.Properties"

CL-USER 20 > (def-java-class "java.util.Enumeration")
#<STANDARD-CLASS ENUMERATION. 20669274>

;no need to wait for the vendor to enhance the language - you use Lisp!
CL-USER 21 > (defmacro doenum ((e enum) &body body)
               (let ((genum (gensym)))
                 `(let ((,genum ,enum))
                    (do ()
                        ((not (enumeration.hasmoreelements ,genum)))
                      (let ((,e (enumeration.nextelement ,genum)))
                        ,@body)))))
DOENUM

;can't do this in Java yet
CL-USER 22 > (doenum (prop (properties.elements p)) (print (object.tostring prop)))

"lucy"
"ethel"
NIL

;doc strings are created giving original Java signatures and indicating
overloads
CL-USER 23 > (documentation 'properties.getproperty 'function)
"java.lang.String getProperty(java.lang.String,java.lang.String)
java.lang.String getProperty(java.lang.String)
"

CL-USER 24 > (documentation 'properties.new 'function)
"java.util.Properties()
java.util.Properties(java.util.Properties)
"

API Reference

JVM Creation and Initialization

Wrapper Generation

Object Creation

Array Support

Proxies - Java calling back to Lisp

Utilities

Threading

This is one area of jfli which needs more attention. The JNI call DetachCurrentThread has proved most problematic (SEGVs in foriegn code, random corruption in Java internals and heaven only knows what else) and so we need another solution to prevent used threads from "leaking". The solution proposed, to recycle lisp threads rather than letting them go, is implemented in the file process.lisp which can be used as a standalone facility indepenently of the jfli.

Error Handling

Summary

I hope you find jfli useful. It is my sincere intent that it enhance the utility and interoperability of Common Lisp, a language with which I am still becoming familiar, and grow to appreciate more every day. I welcome comments and code contributions.

Rich Hickey, July 2004
Updated by Nick Levine, April 2008