2012-09-19 13 views
5

Sono un po 'perso con typemaps in swig e come utilizzare gli array. Ho preparato un esempio funzionante che usa array tra java e c usando swig, ma non so se è il modo corretto per farlo.Modo corretto per interagire con gli array utilizzando SWIG

Fondamentalmente voglio passare un array di byte byte[] da java a c come un carattere di segno * `+ la sua dimensione, modificarlo in c e vedere le modifiche in java e creare un array in c e usarlo in Java.

devo dare un'occhiata a domande theese: How to pass array(array of long in java) from Java to C++ using Swig, Pass an array to a wrapped function as pointer+size or range, How can I make Swig correctly wrap a char* buffer that is modified in C as a Java Something-or-other?

E infatti utilizzato le soluzioni come una guida per fare l'esempio.

Questo è il mio codice nel file arrays.h:

#include <iostream> 

bool createArray(signed char ** arrCA, int * lCA){ 
    *lCA = 10; 
    *arrCA = (signed char*) calloc(*lCA, sizeof(signed char)); 

    for(int i = 0; i < *lCA; i++){ 
     (*arrCA)[i] = i; 
    } 

    return *arrCA != NULL; 
} 

bool readArray(const signed char arrRA[], const int lRA){ 
    for(int i = 0; i < lRA; i++){ 
     std::cout << ((unsigned int) arrRA[i]) << " "; 
    } 
    std::cout << std::endl; 
    return true; 
} 

bool modifyArrayValues(signed char arrMA[], const int lMA){ 
    for(int i = 0; i < lMA; i++){ 
     arrMA[i] = arrMA[i] * 2; 
    } 
    return true; 
} 


bool modifyArrayLength(signed char arrMALIn[], int lMALIn, signed char ** arrMALOut, int * lMALOut){ 

    *lMALOut = 5; 
    *arrMALOut = (signed char*) calloc(*lMALOut, sizeof(signed char)); 

    for(int i = 0; i < *lMALOut; i++){ 
     (*arrMALOut)[i] = arrMALIn[i]; 
    } 
    return true; 
} 

Questo è il file .i per sorso (arrays.i):

%module arrays 

%{ 
    #include "arrays.h" 
%} 

%typemap(jtype) bool createArray "byte[]" 
%typemap(jstype) bool createArray "byte[]" 
%typemap(jni) bool createArray "jbyteArray" 
%typemap(javaout) bool createArray { return $jnicall; } 
%typemap(in, numinputs=0) signed char ** arrCA (signed char * temp) "$1=&temp;" 
%typemap(in, numinputs=0) int * lCA (int l) "$1=&l;" 
%typemap(argout) (signed char ** arrCA, int * lCA) { 
    $result = JCALL1(NewByteArray, jenv, *$2); 
    JCALL4(SetByteArrayRegion, jenv, $result, 0, *$2, (const jbyte*) *$1); 
} 
%typemap(out) bool createArray { 
    if (!$1) { 
     return NULL; 
    } 
} 


%typemap(jtype) (const signed char arrRA[], const int lRA) "byte[]" 
%typemap(jstype) (const signed char arrRA[], const int lRA) "byte[]" 
%typemap(jni) (const signed char arrRA[], const int lRA) "jbyteArray" 
%typemap(javain) (const signed char arrRA[], const int lRA) "$javainput" 

%typemap(in,numinputs=1) (const signed char arrRA[], const int lRA) { 
    $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL); 
    $2 = JCALL1(GetArrayLength, jenv, $input); 
} 

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


%typemap(jtype) (signed char arrMA[], const int lMA) "byte[]" 
%typemap(jstype) (signed char arrMA[], const int lMA) "byte[]" 
%typemap(jni) (signed char arrMA[], const int lMA) "jbyteArray" 
%typemap(javain) (signed char arrMA[], const int lMA) "$javainput" 

%typemap(in, numinputs=1) (signed char arrMA[], const int lMA) { 
    $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL); 
    $2 = JCALL1(GetArrayLength, jenv, $input); 
} 

%typemap(freearg) (signed char arrMA[], const int lMA) { 
    JCALL3(ReleaseByteArrayElements, jenv, $input, $1, 0); 
} 

%typemap(jtype) (signed char arrMALIn[], int lMALIn) "byte[]" 
%typemap(jstype) (signed char arrMALIn[], int lMALIn) "byte[]" 
%typemap(jni) (signed char arrMALIn[], int lMALIn) "jbyteArray" 
%typemap(javain) (signed char arrMALIn[], int lMALIn) "$javainput" 

%typemap(in, numinputs=1) (signed char arrMALIn[], int lMALIn) { 
    $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL); 
    $2 = JCALL1(GetArrayLength, jenv, $input); 
} 

%typemap(freearg) (signed char arrMALIn[], int lMALIn) { 
    JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT); 
} 

%typemap(jtype) bool modifyArrayLength "byte[]" 
%typemap(jstype) bool modifyArrayLength "byte[]" 
%typemap(jni) bool modifyArrayLength "jbyteArray" 
%typemap(javaout) bool modifyArrayLength { return $jnicall; } 
%typemap(in, numinputs=0) signed char ** arrMALOut (signed char * temp) "$1=&temp;" 
%typemap(in, numinputs=0) int * lMALOut (int l) "$1=&l;" 
%typemap(argout) (signed char ** arrMALOut, int * lMALOut) { 
    $result = JCALL1(NewByteArray, jenv, *$2); 
    JCALL4(SetByteArrayRegion, jenv, $result, 0, *$2, (const jbyte*) *$1); 
} 
%typemap(out) bool modifyArrayLength { 
    if (!$1) { 
     return NULL; 
    } 
} 


%include "arrays.h" 

E infine il codice Java per testare iT:

public class Run{ 

    static { 
     System.loadLibrary("Arrays"); 
    } 

    public static void main(String[] args){ 

     byte[] test = arrays.createArray(); 

     printArray(test);  

     arrays.readArray(test); 

     arrays.modifyArrayValues(test); 

     printArray(test); 

     byte[] test2 = arrays.modifyArrayLength(test); 

     printArray(test2); 

    } 

    private static void printArray(byte[] arr){ 

     System.out.println("Array ref: " + arr); 

     if(arr != null){ 
      System.out.println("Array length: " + arr.length); 

      System.out.print("Arrays items: "); 

      for(int i =0; i < arr.length; i++){ 
       System.out.print(arr[i] + " "); 
      } 
     } 
     System.out.println(); 
    } 
} 

l'esempio funziona, ma non sono sicuro che questo è il modo corretto, voglio dire:

c'è un modo più semplice per ottenere lo stesso risultato?

questo codice ha perdite di memoria (da una parte penso che ci sia perché io faccio un calloc ma non lo libero, ma d'altra parte lo passo alla SetByteArrayRegion, quindi magari liberarlo causerebbe un errore)?

la SetByteArrayRegion copia i valori o solo il riferimento ?, ad esempio se invece di eseguire effettivamente un calloc, cosa succede se si ottiene un array da un oggetto C++ per riferimento che verrà distrutto quando esce dall'ambito?

L'array restituito a Java viene liberato correttamente quando lo si annulla?

c'è un modo per specificare da dove si applica una typemap ?, voglio dire, nel codice .i ho fornito una typemap per ogni funzione, dove penso che potrei riutilizzarne alcuni, ma se ci fossero altri funzionano con gli stessi parametri che non voglio tipizzarli, come posso farlo, potrei non essere in grado di modificare il nome dei parametri delle funzioni.

Ho visto la possibilità dei carriys.i descritta in questa domanda How do I pass arrays from Java to C++ using Swig?, ma questo implica che se la dimensione dell'array è di 1000 elementi e voglio inviarlo tramite un socket Java o creare una stringa da esso, ho effettuare 1 chiamata JNI per ogni elemento dell'array. E io in realtà voglio un byte[] nel lato Java, non un insieme di funzioni per accedere all'array di underlaying, quindi il codice già esistente funziona senza modifiche.


Contesto: Il motivo che voglio per raggiungere questo obiettivo è che v'è una libreria che hanno alcune funzionalità, ma la parte importante è che permette di importare ed esportare dati dalla libreria facendo uso di Google Buffer di protocolli.Così il codice relativo a questa domanda si presenta così:

class SomeLibrary { 

    bool export(const std::string & sName, std::string & toExport); 

    bool import(const std::string & sName, const std::string & toImport); 

} 

Il fatto è che Protobuf in C++ utilizza std :: string per memorizzare i dati, ma questi dati sono binari in modo che non possa essere restituito come Java normale Stringa perché viene troncata, più di questo in Swig: convert return type std::string(binary) to java byte[].

Quindi la mia idea è quella di tornare a Java un byte[] per il serializzato Protobuf (come fa la versione Java di buffer Protocol) e accettare byte[] per analizzare protobufs. Per evitare di ottenere SWIGTYPE_p_std_string nel secondo argomento delle esportazioni, e avendo String per il secondo argomento di importazione y hanno avvolto le due funzioni utilizzando% estendere, in questo modo:

%extend SomeLibrary{ 

    bool export(const std::string & sName, char ** toExportData, int * toExportLength); 

    bool import(const std::string & sName, char * toImportData, int toImportLength); 

} 

E ora dovrei essere in grado di effettuare i typemaps .

Ma per essere più generale, ho chiesto il generale di manipolare gli array da Java a sorso avendo il nativo di Java byte[].

+0

Credo che stai facendo è più complicato di quanto dovrebbe essere :). Ho ragione nel pensare di voler costruire un grande array in Java e poi passarlo in una funzione C++? Posso mostrare una soluzione semplice a questo e indirizzare alcune delle altre domande lungo la strada troppo speranzosamente. Ad esempio stai provando a passare un 'byte []' in una funzione come 'void foo (const char * arr, size_t len);'? – Flexo

+0

@Flexo - Penso anche che lo sto facendo troppo complicato. Ho anche modificato la domanda fornendo un po 'più di contesto. –

+0

Per rispondere a un paio di altri punti: 'SetByteArrayRegion' copia i dati. L'array Java sarà gestito dai soliti meccanismi Java. Viene creato localmente alla chiamata JNI e verrà rilasciato un punto dopo la rimozione dell'ultimo riferimento ad esso. – Flexo

risposta

5

Non sconto sui carriys.i automaticamente. Detto SWIG ha alcune typemaps convenienti già:

%module test 

%apply(char *STRING, size_t LENGTH) { (char *str, size_t len) }; 

%inline %{ 
void some_func(char *str, size_t len) { 
} 
%} 

che produce una funzione nell'interfaccia Java:

public static void some_func(byte[] str) 

cioè ci vuole una matrice è possibile costruire in Java come normale e riempie il puntatore e lunghezza per te. Quasi gratis.

Il tuo codice così com'è quasi sicuramente perde - vorresti chiamare lo free() all'interno della typemap di argout per rilasciare la memoria che hai assegnato una volta che è stata copiata nel nuovo array Java.

È possibile applicare in modo selettivo i tipi di caratteri sia con il tipo sia con il nome dei parametri. See this document for more on typemap matching rules. Puoi anche richiedere di usare esplicitamente una typemap dove non verrebbe altrimenti utilizzata con %apply come nell'esempio che ho mostrato sopra. (In realtà copia i typemaps, in modo che se ne modifica uno solo non lo sostituisca nel caso generale)

In generale i typemaps per passare array da Java a C++ o che lavorano con matrici di dimensioni note sono più semplice di quelli per il ritorno da C++ a Java perché le informazioni sulle dimensioni sono più evidenti.

Il mio suggerimento sarebbe quello di pianificare molte allocazioni all'interno di Java per allocare e progettare le funzioni che potrebbero far crescere un array in modo da operare in due modalità: una che indica la dimensione necessaria e una che fa effettivamente il lavoro. Si potrebbe farlo con:

ssize_t some_function(char *in, size_t in_sz) { 
    if (in_sz < the_size_I_need) { 
    return the_size_I_need; // query the size is pretty fast 
    } 

    // do some work on in if it's big enough 

    // use negative sizes or exceptions to indicate errors 

    return the_size_I_really_used; // send the real size back to Java 
} 

che permetterebbe di fare qualcosa di simile a quanto segue in Java:

int sz = module.some_function(new byte[0]); 
byte result[] = new byte[sz]; 
sz = module.some_function(result); 

Si noti che con il default typemaps il new byte[0] è necessario in quanto non consentono null da utilizzare come array: è possibile aggiungere i typemap che consentono questo se lo si desidera oppure utilizzare %extend per fornire un sovraccarico che non ha richiesto un array vuoto.

+0

Ho provato questa soluzione e funziona perfettamente per i valori di input. Per l'output non posso conoscere la dimensione necessaria prima della mano, voglio dire che sta serializzando un protobuf quindi so solo quanto spazio ci vuole quando eseguo la serializzazione, e non Non voglio scartare i dati serializzati perché non si adattano all'array che ho passato, e non sono nemmeno entusiasta di tornare a Java un array più grande dei dati contenuti perché il codice esistente non funzionerebbe senza modifiche (questo potrebbe essere evitato copiando gli array sul lato Java). Ho cambiato i typodap per l'input alla soluzione e l'output con i typepaps originali –

+0

@JavierMr che ha senso anche - Io userei qualcosa come questa risposta per input (o input/output combinati poiché i typemaps ricalcano il valore) e http://stackoverflow.com/a/12196522/168175 per la creazione di nuovi array. – Flexo

+0

Grazie mille per il tuo aiuto in tutte le domande su Swig che ho fatto. –

0

E 'ben documentato qui: http://swig.org/Doc3.0/SWIGDocumentation.html#Java_binary_char

lima L'interfaccia di seguito genererà byte [] metodo in Java:

%apply (char *STRING, size_t LENGTH) { (const char data[], size_t len) } 
%inline %{ 
void binaryChar1(const char data[], size_t len) { 
    printf("len: %d data: ", len); 
    for (size_t i=0; i<len; ++i) 
    printf("%x ", data[i]); 
    printf("\n"); 
} 
%} 
Problemi correlati