# HG changeset patch
# Parent 26f119244959f9e43c921408e9b1c8136e193880
CFFI patches against abcl-1.1.0-dev including questionable implementation of callbacks.
diff -r 26f119244959 src/cffi-abcl.lisp
--- a/src/cffi-abcl.lisp Fri Feb 24 17:59:52 2012 +0100
+++ b/src/cffi-abcl.lisp Fri Feb 24 18:12:30 2012 +0100
@@ -27,7 +27,13 @@
;;; This implementation requires the Java Native Access (JNA) library.
;;;
-
+;;;
+;;; JNA may be automatically loaded into the current JVM process from
+;;; abcl-1.1.0-dev via
+;;;
+;;; CL-USER> (require 'abcl-contrib)(require 'jna)
+(require 'abcl-contrib)
+(require 'jna)
;;; This is a preliminary version that will have to be cleaned up,
;;; optimized, etc. Nevertheless, it passes all of the relevant CFFI
;;; tests except MAKE-POINTER.HIGH. Callbacks and Shareable Vectors
@@ -68,6 +74,7 @@
(in-package #:cffi-sys)
+
(defun private-jfield (class-name field-name instance)
(let ((field (find field-name
(jcall (jmethod "java.lang.Class" "getDeclaredFields")
@@ -297,14 +304,17 @@
when fn do (return fn))
(find-it name (gethash library *loaded-libraries*)))))
+(defun convert-calling-convention (convention)
+ (ecase convention
+ (:stdcall "ALT_CONVENTION")
+ (:cdecl "C_CONVENTION")))
+
(defun make-function-pointer (pointer cconv)
(jnew (private-jconstructor "com.sun.jna.Function"
"com.sun.jna.Pointer" "int")
pointer
(jfield "com.sun.jna.Function"
- (ecase cconv
- (:cdecl "C_CONVENTION")
- (:stdcall "ALT_CONVENTION")))))
+ (convert-calling-convention convention))))
(defun lisp-value-to-java (value foreign-type)
(if (eq foreign-type :pointer)
@@ -347,32 +357,135 @@
else do (setf return-type type)
finally (return (values types fargs return-type)))))
-(defmacro %foreign-funcall (name args &key library calling-convention)
+(defmacro %foreign-funcall (name args &key library convention)
+ (declare (ignore convention))
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
`(%%foreign-funcall (find-foreign-function ',name ',library)
(list ,@fargs) ',types ',rettype)))
-(defmacro %foreign-funcall-pointer (ptr args &key calling-convention)
+(defmacro %foreign-funcall-pointer (ptr args &key convention)
(multiple-value-bind (types fargs rettype)
(foreign-funcall-type-and-args args)
- `(%%foreign-funcall (make-function-pointer ,ptr ',calling-convention)
+ `(%%foreign-funcall (make-function-pointer ,ptr ',convention)
(list ,@fargs) ',types ',rettype)))
;;;# Callbacks
+;;;
+;;; Implementation by Mark .
-;;; TODO. IIUC, implementing this functionality would require being
-;;; able to create new interface definitions at runtime, which is
-;;; apparently no supported by ABCL as of June 2009.
+(defun foreign-to-callback-type (type)
+ (ecase type
+ ((:int :unsigned-int)
+ :int)
+ ((:long :unsigned-long)
+ (jvm::make-jvm-class-name "com.sun.jna.NativeLong"))
+ ((:long-long :unsigned-long-long)
+ (jvm::make-jvm-class-name "java.lang.Long"))
+ (:pointer
+ (jvm::make-jvm-class-name "com.sun.jna.Pointer"))
+ (:float
+ :float)
+ (:double
+ :double)
+ ((:char :unsigned-char)
+ :byte)
+ ((:short :unsigned-short)
+ :short)
+ (:void
+ :void)))
+
+(defvar *callbacks* (make-hash-table))
-(defmacro %defcallback (name rettype arg-names arg-types body
- &key calling-convention)
- (warn "callback support unimplemented"))
+(defmacro %defcallback (name return-type arg-names arg-types body
+ &key convention)
+ (declare (ignore convention))
+ `(let ((interface-name ,(define-jna-callback-interface return-type arg-types)))
+ (setf (gethash ',name *callbacks*)
+ (jinterface-implementation interface-name "callback"
+ (lambda (,@arg-names)
+ ,body)))))
+(jvm::define-class-name +callback-object+ "com.sun.jna.Callback")
+(defconstant +dynamic-callback-package+
+ "org/armedbear/jna/dynamic/callbacks")
+
+(defun define-jna-callback-interface (returns args)
+ (multiple-value-bind (interface interface-name)
+ (%define-jna-callback-interface
+ (foreign-to-callback-type returns)
+ (mapcar (lambda (type) (foreign-to-callback-type type)) args))
+ (load-class interface)
+ interface-name))
+
+(defun %define-jna-callback-interface (returns args)
+ "Returns the Java byte[] array of a class representing a Java interface.
+
+The fully qualified dotted name of the generated class is returned as
+the second value."
+ (let ((name (symbol-name (gensym))))
+ (values
+ (define-java-interface name +dynamic-callback-package+
+ `(("callback" ,returns ,args))
+ `(,+callback-object+))
+ (format nil "~A.~A"
+ (substitute #\. #\/ +dynamic-callback-package+) name))))
+
+(defun define-java-interface (name package methods
+ &optional (superinterfaces nil))
+"Define a class for a Java interface called NAME in PACKAGE with METHODS.
+
+METHODS is a list of (NAME RETURN-TYPE (ARG-TYPES)) entries. NAME is
+a string. The values of RETURN-TYPE and the list of ARG-TYPES for the
+defined method follow the are either references to Java objects as
+created by JVM::MAKE-JVM-CLASS-NAME, or keywords representing Java
+primtive types as contained in JVM::MAP-PRIMITIVE-TYPE.
+
+SUPERINTERFACES optionally contains a list of interfaces that this
+interface extends specified as fully qualifed dotted Java names."
+ (let* ((class-name-string (format nil "~A/~A" package name))
+ (class-name (jvm::make-jvm-class-name class-name-string))
+ (class (jvm::make-class-interface-file class-name)))
+ (dolist (superinterface superinterfaces)
+ (jvm::class-add-superinterface
+ class
+ (if (typep superinterface 'jvm::jvm-class-name)
+ superinterface
+ (jvm::make-jvm-class-name superinterface))))
+ (dolist (method methods)
+ (let ((name (first method))
+ (returns (second method))
+ (args (third method)))
+ (jvm::class-add-method
+ class
+ (jvm::make-jvm-method name returns args
+ :flags '(:public :abstract)))))
+ (jvm::finalize-class-file class)
+ (let ((s (sys::%make-byte-array-output-stream)))
+ (jvm::write-class-file class s)
+ (sys::%get-output-stream-bytes s))))
+
+(defun load-class (class-bytes)
+ "Load the Java byte[] array CLASS-BYTES as a Java class."
+ (let ((load-class-method
+ (jmethod "org.armedbear.lisp.JavaClassLoader"
+ "loadClassFromByteArray" "[B")))
+ (jcall load-class-method java::*classloader* class-bytes)))
+
+;;; Test function: unused in CFFI
+(defun write-class (class-bytes pathname)
+ "Write the Java byte[] array CLASS-BYTES to PATHNAME."
+ (with-open-file (stream pathname
+ :direction :output
+ :element-type '(signed-byte 8))
+ (dotimes (i (jarray-length class-bytes))
+ (write-byte (jarray-ref class-bytes i) stream))))
+
(defun %callback (name)
- (error "callback support unimplemented"))
+ (or (gethash name *callbacks*)
+ (error "Undefined callback: ~S" name)))
-;;;# Loading and Closign Foreign Libraries
+;;;# Loading and closing Foreign Libraries
(defparameter *loaded-libraries* (make-hash-table))