From: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - 2007-05-24 21:53:14
|
Ok, this post is going to sound more like a blog entry than a developers mailing list post, but I think it's good to add some history. The Goal ======= The goal was to write usocket (a portable sockets library), preferrably in Lisp, in order to prevent requiring additional dependencies. Step 1 ===== Step 0 was to use the functions provided by ArmedBear through its 'ext' package, but really soon that wasn't enough: the sockets interface provided by ArmedBear is very basic and just enough for trivial-sockets and SLIME. (I've been told that's basically why it was there in the first place.) So, I proceeded to use the FFI provided to me with the methods provided to me. These methods require a lot of redundancy in the information. For example when you want to call a method of an object of unknown type, you have to (1) Input the types of the parameters we know the method is taking (2) Input the name of the method to call (3) Input the name of the class the method belongs to (4) Input the object (5) Input the parameters themselves Finding it too much work to type all of these all the time, I soon discovered that the class of the object was always the class associated with the method I was trying to use. I already knew how to extract the class name from an instance variable, so, I ended up implementing a step which 'only' needed items 1,2, 4 and 5. Although this doesn't seem much, it made a lot of difference in terms of 'characters typed' because it also eliminated a number of function calls. The result (amongst others) was a macro to "quickly" call a static class method member. To make the story a bit less theoretic, here it is: (defmacro jstatic-call (class-name (method-name &rest arg-spec) &rest args) (let ((class-sym (gensym))) `(let ((,class-sym ,class-name)) (java:jstatic (java:jmethod ,class-sym ,method-name ,@arg-spec) (java:jclass ,class-sym) ,@args)))) I defined a macro to do non-static method calls along the same lines. Step 2 ==== So, this is only going to be a story if there is more than one step and here it is: the system of step 1 worked for a while - although not beautifully. Then, the next step was to extend usocket again and my Java implementation turned out to be returning class names of which I wasn't allowed to access the methods. Why did this work for Java but not for me? Well, I was asking the runtime object for its class name and accessing the methods from that, but Java might have been asking for the *compile time* class name and accessing the methods from it. Then I started inserting names of the classes that I would have used as compile time classes, if lisp would have had such a thing. As it turned out: I indeed could ask those for the methods; I was calling the open() static method in the java.nio.channels.SocketChannel class, but my Sun Java 1.5 implementation returned a class of type sun.nio.ch.(something). It's the sun.nio.* class I wasn't allowed to access, but java.nio.channels.SocketChannel indeed was available for access. I modified the code of the non-static-call-wrapper macro, which then essentially became: (defmacro jmethod-call (instance-and-class (method &rest arg-spec) &rest args) (let ((isym (gensym))) (multiple-value-bind (instance class-name) (if (listp instance-and-class) (values (first instance-and-class) (second instance-and-class)) (values instance-and-class)) (when (null class-name) (setf class-name `(java:jclass-name (java:jclass-of ,isym)))) `(let* ((,isym ,instance)) (java:jcall (java:jmethod ,class-name ,method ,@arg-spec) ,isym ,@args))))) Looking at the resulting code: (defun jset-iterator (jset) (jmethod-call (jset "java.util.Set") ("iterator"))) which basically says that jset is a "java.util.Set" class instance. It felt like I was typecasting every occurrance where I was calling the Java FFI. Step 3 ==== In Java everything is a class, even the method I am retrieving and as it turned out, I could ask that method class for the method return type! This opens great opportunities, because normally, the compile time type of the variable the result would be assigned to would equal the type returned by the method called. There are 2 situations where the type of the return value would differ from the one of the variable, both of which are addressed with type casts in Java: 1) The method returns the least specialized type (for instance the next() method in a set iterator), where most of the time more specialized objects will be actually returned 2) The cast is used to do an actual type conversion. Wouldn't it be nice if I could write Java FFI code which just knows to which compile time type I would have declared my variables? Wouldn't it be nice if I could change the assumed type simply by doing a type-cast into a new class? Enter proxy objects. I'm proposing to add a layer to the FFI (or incorporate it in the base code, which ever you prefer) which handles the java type system. I created an abstraction layer which handles all dynamic typing *and* allows me to typecast variable values into different classes (virtually, not really). The system I created also removes the need to explicitly pass the types of the parameters for the method we'll be trying to call (since that information is now dynamically inferred from the dynamic typing system). Could I remove only item (3) of the required items list in step 1, in this step I'm able to remove both item (3) and (1)! (which saves a lot of typing and dynamically changes with the program changes as you'd expect!) How did I do it? I created a (small) number of building blocks which do almost all of the work: - a structure to proxy for the real java-object - a routine to do typecasts (on these proxies) - routines to call dynamic and static methods - a return type wrapping routine which wraps return types in proxy objects - a routine to returning the real java-object (based on a proxy object) Basically, that's it. Now, the call-dynamic-java-method routine looks like this: (defun do-jmethod-call (object method-name &rest arguments) (multiple-value-bind (instance class-name) (java-value-and-class object) (let* ((argument-types (mapcar #'jtype-of arguments)) (jm (apply #'java:jmethod class-name method-name argument-types)) (rv (apply #'java:jcall jm instance (mapcar #'jop-deref arguments)))) (make-return-type-proxy jm rv)))) Where there are a number of helpers abstracted out for reuse. Did I have to call the keys() method for a "java.nio.channels.SocketChannel"-typed object before with this code (assuming the variable has been initialized elsewhere): (jmethod-call (selector "java.nio.channels.Selector") ("keys")) The same code with the new dynamic typing system looks like this: (do-jmethod-call selector "keys") Isn't the latter much nicer? This is the full code to achieve that and more: (defstruct (java-object-proxy (:conc-name :jop-)) value class) (defvar *jm-get-return-type* (java:jmethod "java.lang.reflect.Method" "getReturnType")) (declaim (inline make-return-type-proxy)) (defun make-return-type-proxy (jmethod jreturned-value) (if (java:java-object-p jreturned-value) (let ((rt (java:jcall *jm-get-return-type* jmethod))) (make-java-object-proxy :value jreturned-value :class rt)) jreturned-value)) (defun jcoerce (instance output-type-spec) (cond ((java-object-proxy-p instance) (setf (jop-class instance) (java:jclass output-type-spec)) instance) ((java:java-object-p instance) (make-java-object-proxy :class (java:jclass output-type-spec) :value instance)) ((keywordp output-type-spec) ;; all that remains is creating an immediate type... (let ((jval (java:make-immediate-object instance output-type-spec))) (make-java-object-proxy :class output-type-spec :value jval))) )) (defun jtype-of (instance) ;;instance must be a jop (if (keywordp (jop-class instance)) (string-downcase (symbol-name (jop-class instance))) (java:jclass-name (jop-class instance)))) (defun jop-deref (instance) (if (java-object-proxy-p instance) (jop-value instance) instance)) (defun java-value-and-class (object) (values (jop-deref object) (jtype-of object))) (defun do-jmethod-call (object method-name &rest arguments) (multiple-value-bind (instance class-name) (java-value-and-class object) (let* ((argument-types (mapcar #'jtype-of arguments)) (jm (apply #'java:jmethod class-name method-name argument-types)) (rv (apply #'java:jcall jm instance (mapcar #'jop-deref arguments)))) (make-return-type-proxy jm rv)))) (defun do-jstatic-call (class-name method-name &rest arguments) (let* ((argument-types (mapcar #'jtype-of arguments)) (jm (apply #'java:jmethod class-name method-name argument-types)) (rv (apply #'java:jstatic jm (java:jclass class-name) (mapcar #'jop-deref arguments)))) (make-return-type-proxy jm rv))) I hope you find the proposal usefull, thanks for reading my little tail about how I came up with this system and I'm hoping for your feedback! bye, XXXXXXXXXXXXXX (ehu) |
From: Alex M. <kil...@ne...> - 2007-05-25 18:21:05
|
(message (Hello 'Erik) (you :wrote :on '(Thu, 24 May 2007 23:53:14 +0200)) ( EH> Ok, this post is going to sound more like a blog entry than a EH> developers mailing list post, but I think it's good to add some EH> history. honestly speaking i didn't read all the stuff below (too much for me), but have you tried jfli-abcl? FFI interface it provides is enough to call (almost) any Java functions (static, virtual, constructors..) without much hassle. ) (With-best-regards '(Alex Mizrahi) :aka 'killer_storm) "I am everything you want and I am everything you need") |
From: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - 2007-05-25 19:00:35
|
On 5/25/07, Alex Mizrahi <kil...@ne...> wrote: > (message (Hello 'Erik) Hi Alex, > (you :wrote :on '(Thu, 24 May 2007 23:53:14 +0200)) > ( > > EH> Ok, this post is going to sound more like a blog entry than a > EH> developers mailing list post, but I think it's good to add some > EH> history. > > honestly speaking i didn't read all the stuff below (too much for me), but > have you tried jfli-abcl? > FFI interface it provides is enough to call (almost) any Java functions > (static, virtual, constructors..) without much hassle. Nope, I didn't know about it and apparently it's very obscure since not even google knows about it... But I wasn't actually proposing a new (optional) library, but an enhancement to the FFI which is there. I'm trying to prevent using anything which isn't available by default, since I'm writing a portability package which I hope to become integrated in a lisp library bootstrap system. You see, in that case any dependency is one too many. > (With-best-regards '(Alex Mizrahi) :aka 'killer_storm) > "I am everything you want and I am everything you need") bye, Erik. |