2012-07-20 14 views
8

Dato un'intestazione come:Passare una matrice a una funzione avvolto come dimensione del puntatore + o intervallo

#include <iostream> 
#include <algorithm> 
#include <iterator> 

inline void foo(const signed char *arr, size_t sz) { 
    std::copy_n(arr, sz, std::ostream_iterator<int>(std::cout, "\n")); 
} 

inline void bar(const signed char *begin, const signed char *end) { 
    std::copy(begin, end, std::ostream_iterator<int>(std::cout, "\n")); 
} 

(ho usato C++ 11 qui per comodità, questo potrebbe essere o C o C++ se è stato modificato il implementazioni però)

Come è possibile eseguire il wrapping di queste funzioni per richiedere solo un array sul lato Java e utilizzare la dimensione (nota) dell'array per fornire il secondo parametro per queste funzioni?

+0

Che ne dici di fornire manualmente un metodo wrapper in Java? Non è come i metodi che prendono un array in Java non hanno anche i parametri 'int offset, int length' ... –

+0

@ SamuelAudet: potresti farlo, ma direi che non è un'interfaccia ben progettata (duplicazione delle informazioni solo per il gusto di farlo). Il problema è che se avete 'byte []' avrete bisogno di scrivere una typemap (la maggior parte delle volte) per convertirla in 'signed char *', o usare '% array_class' e un' for' loop per fare una copia comunque. Entrambi sono piuttosto brutti. – Flexo

+0

@ SamuelAudet - Ho aggiornato la mia risposta con un metodo wrapper manuale. È piuttosto brutto secondo me. – Flexo

risposta

12

Il punto cruciale di questo è che per avvolgere una di queste funzioni si vorrà utilizzare un multi-argument typemap.

Il preambolo è piuttosto standard per SWIG. Ho usato il mio prgama preferito per caricare automaticamente la libreria condivisa senza che l'utente dell'interfaccia bisogno di sapere:

%module test 

%{ 
#include "test.hh" 
%} 

%pragma(java) jniclasscode=%{ 
    static { 
    try { 
     System.loadLibrary("test"); 
    } catch (UnsatisfiedLinkError e) { 
     System.err.println("Native code library failed to load. \n" + e); 
     System.exit(1); 
    } 
    } 
%} 

Prima, però userete bisogno di un paio Java typemaps per istruire SWIG per usare byte[] come il tipo di entrambi parti dell'interfaccia Java: il JNI e il wrapper che lo chiama. Nel file del modulo di generazione useremo il tipo JNI jbyteArray. Stiamo passando l'input direttamente dall'interfaccia SWIG al JNI che genera.

%typemap(jtype) (const signed char *arr, size_t sz) "byte[]" 
%typemap(jstype) (const signed char *arr, size_t sz) "byte[]" 
%typemap(jni) (const signed char *arr, size_t sz) "jbyteArray" 
%typemap(javain) (const signed char *arr, size_t sz) "$javainput" 

Quando questo è fatto siamo in grado di scrivere un typemap multi-argomento:

%typemap(in,numinputs=1) (const signed char *arr, size_t sz) { 
    $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL); 
    $2 = JCALL1(GetArrayLength, jenv, $input); 
} 

Il lavoro della a typemap è convertire da quello che stiamo dato dalla chiamata JNI a quello che il vero e proprio la funzione si aspetta davvero come input. Ho usato numinputs=1 per indicare che i due argomenti di funzione reale prendono solo un input sul lato Java, ma questo è comunque il valore predefinito, quindi non è necessario dichiararlo esplicitamente.

In questa typemap è il primo argomento della typemap, ovvero il primo argomento della nostra funzione in questo caso. Lo impostiamo chiedendo un puntatore alla memoria sottostante dell'array Java (che può essere o non essere una copia in realtà). Abbiamo impostato $2, il secondo argomento typemap per essere la dimensione della matrice.

I macro JCALLn assicurano che la typemap possa essere compilata con JNI C e C++. Si espande alla chiamata appropriata per la lingua.

abbiamo bisogno di un'altra typemap per ripulire una volta che la chiamata di funzione reale è tornato:

%typemap(freearg) (const signed char *arr, size_t sz) { 
    // Or use 0 instead of ABORT to keep changes if it was a copy 
    JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT); 
} 

Ciò richiede ReleaseByteArrayElements a dire la JVM abbiamo finito con l'array. Ha bisogno del puntatore e dell'oggetto Java dell'array da cui è stato ottenuto. Inoltre prende un parametro che indica se il contenuto deve essere copiato iff sono stati modificati e il puntatore che abbiamo ottenuto era una copia in primo luogo. (L'argomento passato a NULL è un puntatore opzionale a jboolean che indica se è stata data una copia).

Per la seconda variante, le typemaps sono sostanzialmente simili:

%typemap(in,numinputs=1) (const signed char *begin, const signed char *end) { 
    $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL); 
    const size_t sz = JCALL1(GetArrayLength, jenv, $input); 
    $2 = $1 + sz; 
} 

%typemap(freearg) (const signed char *begin, const signed char *end) { 
    // Or use 0 instead of ABORT to keep changes if it was a copy 
    JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT); 
} 

%typemap(jtype) (const signed char *begin, const signed char *end) "byte[]" 
%typemap(jstype) (const signed char *begin, const signed char *end) "byte[]" 
%typemap(jni) (const signed char *begin, const signed char *end) "jbyteArray" 
%typemap(javain) (const signed char *begin, const signed char *end) "$javainput" 

L'unica differenza è l'uso della variabile locale sz per calcolare la end arugment utilizzando il puntatore begin.

L'unica cosa che resta da fare è dire SWIG per avvolgere il file di intestazione stessa, utilizzando le typemaps che abbiamo appena scritto:

%include "test.hh" 

ho provato entrambe queste funzioni con:

public class run { 
    public static void main(String[] argv) { 
    byte[] arr = {0,1,2,3,4,5,6,7}; 
    System.out.println("Foo:"); 
    test.foo(arr); 
    System.out.println("Bar:"); 
    test.bar(arr); 
    } 
} 

Che ha funzionato come previsto.

Per comodità ho condiviso i file che ho usato per scrivere questo su my site. Ogni riga di ogni file in quell'archivio può essere ricostruita seguendo questa risposta in sequenza.


Per riferimento che avremmo potuto fare il tutto senza alcuna JNI chiamata, utilizzare le %pragma(java) modulecode per generare un sovraccarico che usiamo convertire l'ingresso (in puro Java) nella forma prevista dalle funzioni reali. Per che il file del modulo sarebbe stato:

%module test 

%{ 
#include "test.hh" 
%} 

%include <carrays.i> 
%array_class(signed char, ByteArray); 

%pragma(java) modulecode = %{ 
    // Overload foo to take an array and do a copy for us: 
    public static void foo(byte[] array) { 
    ByteArray temp = new ByteArray(array.length); 
    for (int i = 0; i < array.length; ++i) { 
     temp.setitem(i, array[i]); 
    } 
    foo(temp.cast(), array.length); 
    // if foo can modify the input array we'll need to copy back to: 
    for (int i = 0; i < array.length; ++i) { 
     array[i] = temp.getitem(i); 
    } 
    } 

    // How do we even get a SWIGTYPE_p_signed_char for end for bar? 
    public static void bar(byte[] array) { 
    ByteArray temp = new ByteArray(array.length); 
    for (int i = 0; i < array.length; ++i) { 
     temp.setitem(i, array[i]); 
    } 
    bar(temp.cast(), make_end_ptr(temp.cast(), array.length)); 
    // if bar can modify the input array we'll need to copy back to: 
    for (int i = 0; i < array.length; ++i) { 
     array[i] = temp.getitem(i); 
    } 
    } 
%} 

// Private helper to make the 'end' pointer that bar expects 
%javamethodmodifiers make_end_ptr "private"; 
%inline { 
    signed char *make_end_ptr(signed char *begin, int sz) { 
    return begin+sz; 
    } 
} 

%include "test.hh" 

%pragma(java) jniclasscode=%{ 
    static { 
    try { 
     System.loadLibrary("test"); 
    } catch (UnsatisfiedLinkError e) { 
     System.err.println("Native code library failed to load. \n" + e); 
     System.exit(1); 
    } 
    } 
%} 

Oltre agli ovvi (due) copie richiesto per ottenere i dati nel giusto tipo (non c'è modo banale per andare da byte[] a SWIGTYPE_p_signed_char) e ritorno questo è un altro svantaggio - è specifico per le funzioni foo e bar, mentre le typemaps che abbiamo scritto in precedenza non sono specifiche di una determinata funzione - verranno applicate ovunque corrispondano, anche più volte sulla stessa funzione se si dispone di una funzione che richiede due gamme o due combinazioni di puntatore + lunghezza. L'unico vantaggio di farlo in questo modo è che se ti capita di avere altre funzioni avvolte che ti stanno dando SWIGTYPE_p_signed_char allora avrai ancora i sovraccarichi da usare se lo desideri. Anche nel caso in cui si disponga di uno ByteArray da %array_class, non è ancora possibile eseguire il calcolo aritmetico in Java necessario per generare end.

Il modo originale mostrato offre un'interfaccia più pulita in Java, con i vantaggi aggiunti di non realizzare copie eccessive e di essere più riutilizzabili.


Ancora un altro approccio alternativo al confezionamento sarebbe di scrivere un paio di %inline sovraccarichi per foo e bar:

%inline { 
    void foo(jbyteArray arr) { 
    // take arr and call JNI to convert for foo 
    } 
    void bar(jbyteArray arr) { 
    // ditto for bar 
    } 
} 

Questi sono presentati come sovraccarichi nell'interfaccia Java, ma sono ancora modulo specifico inoltre, il JNI richiesto qui è più complesso di quanto sarebbe altrimenti necessario - è necessario organizzare in modo da ottenere in qualche modo jenv, che non è accessibile per impostazione predefinita. Le opzioni sono una chiamata lenta per ottenerla o una typemap numinputs=0 che riempie automaticamente il parametro. In entrambi i casi la typemap multi-argomento sembra molto più bella.

Problemi correlati