2014-07-27 39 views
5

Ho un'API C che sto cercando di utilizzare in Clojure, tramite l'API JNA. Il mio problema può essere meglio dimostrato con il seguente esempio. Dire che ho questo codice C in una libreria:Ottenere e passare le strutture in base al valore in Clojure con JNA

typedef struct { 
    int foo; 
    int bar; 
    double baz; 
} MyStruct; 

MyStruct createStruct() { 
    MyStruct myStruct; 
    myStruct.foo = 3; 
    myStruct.bar = 4; 
    myStruct.baz = 3.14; 

    return myStruct; 
} 

double addStruct(MyStruct myStruct) { 
    return myStruct.foo + myStruct.bar + myStruct.baz; 
} 

In questo esempio, mi piacerebbe chiamare createStruct, e quindi passare tale risultato addStruct. Il punto importante qui è che MyStruct è passato al valore sia come tipo di ritorno che come argomento. In nessun momento ho bisogno di leggere effettivamente i valori dei campi in MyStruct.

Inoltre, nel mio sistema, funzioni native sono avvolti in questo modo:

; `quux` is defined in `some-lib` and returns an `int` 
(let [fn- (com.sun.jna.Function/getFunction "some-lib" "quux")] 
    (fn [& args] 
    (.invoke fn- Integer (to-array args)))) 

L'obiettivo è quello di ottenere un tipo per sostituire Integer sopra che vi avvolgerà MyStruct come un valore.

L'unica risorsa che ho trovato che copre questo argomento è this article, ma si parla solo di come passare le strutture per riferimento.

Dato che, qui ci sono i diversi approcci che ho provato a prendere per risolvere questo problema:

  1. creare una classe che eredita da Structure, che è JNA di built-in mechanism for creating and using structs. Date le informazioni su quella pagina, ho cercato di creare la seguente classe utilizzando solo Clojure:

    class MyStruct extends Structure implements Structure.ByValue { 
        int foo; 
        int bar; 
        double baz; 
    } 
    

    deftype non funziona per questo scenario, dal momento che la classe deve ereditare dalla classe astratta Structure, e gen-class doesn' t lavorare perché la classe deve avere i campi pubblici non statici foo, bar e baz.

    Da quello che posso dire, nessuno dei servizi di interoperabilità Clojure Java standard può creare la classe precedente.

  2. Creare una classe che eredita da Structure e sovrascrivere i metodi getter/setter del campo struct. Poiché gen-class è (credo) l'unico costrutto Clojure che consente l'ereditarietà diretta e non supporta più campi pubblici non statici, l'alternativa successiva è semplicemente non utilizzare affatto i campi. Looking at the Structure abstract class documentation, sembra che ci sia una combinazione di sostituzioni possibile per utilizzare i campi della struttura "virtuale", in modo tale da ottenere e impostare i dati da un'origine diversa (such as the state field from gen-class). Guardando attraverso la documentazione, sembra sovrascrivere readField, writeField, e altri metodi possono avere l'effetto desiderato, ma non sono riuscito a capire come farlo leggendo la documentazione, e non sono riuscito a trovare esempi simili online.

  3. Utilizzare una classe di memoria diversa. JNA ha una miriade di classi per il wrapping di tipi nativi.Mi chiedo se, piuttosto che definire e utilizzare una classe Structure, potrei usare un'altra classe generica che può prendere un numero arbitrario di bit (come il modo in cui lo Integer può contenere qualsiasi cosa di 4 bit di larghezza, indipendentemente dal tipo di sorgente "effettivamente" è). È possibile, ad esempio, dire che una funzione restituisce una matrice di byte di lunghezza 16 (poiché sizeof(MyStruct) è 16)? Che dire del wrapping di una matrice a dimensione fissa in una classe contenitore che implementa NativeMapped? Non sono riuscito a trovare esempi di come fare neanche.

+0

JNA fornisce allocazione di memoria di blocco in forma di 'com.sun.jna.Memory'. Da lì, puoi estrarre tipi arbitrari da offset arbitrari in memoria usando i metodi 'Pointer.getXXX()'. – technomage

+0

@technomage L'uso di 'Memory' come tipo di ritorno per' createStruct' restituisce un 'Pointer', il cui indirizzo è' 0x0000000400000003'. In altre parole, considera il valore della memoria grezza della struttura come un puntatore. Quando si dereferenzia (tramite uno dei metodi 'read' o' getXXX'), si blocca la VM. –

+1

Il problema è che il parametro 'struct' e la semantica del valore di ritorno variano a seconda del compilatore e il layout' Structure' viene effettivamente utilizzato dal codice nativo per determinare come impostare correttamente lo stack. Stavo suggerendo che potresti essere in grado di usare 'Memoria' per assegnare un blocco arbitrario di memoria a un'istanza' Structure.ByValue' esistente. – technomage

risposta

5

Clojure realtà incorpora una versione biforcuta di ASM, che viene utilizzato per generare e caricamento JVM bytecode.

In cima a questa libreria, ho creato un piccolo DSL (che è incompleto e probabilmente danneggiato) che consente di creare classi Java arbitrarie, con una sintassi simile a Java. Attualmente è solo uno GitHub gist e supporta solo l'estensione delle classi, l'implementazione di interfacce, l'aggiunta di costruttori che chiamano costruttori di superclasse e campi.

Ecco la soluzione alla domanda di cui sopra, utilizzando questo DSL:

(def-class MyStruct :extends com.sun.jna.Structure 
        :implements [com.sun.jna.Structure$ByValue] 
    ; Declare all the constructors, which just forward their 
    ; arguments to the constructors of com.sun.jna.Structure 
    (com.sun.jna.Structure []) 
    (com.sun.jna.Structure [com.sun.jna.TypeMapper]) 
    (com.sun.jna.Structure [Integer]) 
    (com.sun.jna.Structure [Integer com.sun.jna.TypeMapper]) 
    (com.sun.jna.Structure [com.sun.jna.Pointer]) 
    (com.sun.jna.Structure [com.sun.jna.Pointer Integer]) 
    (com.sun.jna.Structure [com.sun.jna.Pointer Integer com.sun.jna.TypeMapper]) 

    ; Declare the fields of the struct 
    ^Integer foo 
    ^Integer bar 
    ^Double baz) 

Ora, posso effettuare le seguenti operazioni:

(defn createStruct [& args] 
    (let [fn- (com.sun.jna.Function/getFunction "some-lib" "createStruct")] 
    (.invoke fn- MyStruct (to-array args)))) 

(defn addStruct [& args] 
    (let [fn- (com.sun.jna.Function/getFunction "some-lib" "addStruct")] 
    (.invoke fn- Double (to-array args)))) 

(addStruct (createStruct)) 
; => 10.14