2011-11-16 7 views
11

Ho un codice C++ nativo che sto convertendo in Java usando SWIG in modo che la mia applicazione Java possa usarlo. In particolare ci sono alcune funzioni che restituiscono std :: vector. Ecco un frammento del mio file di interfaccia:SWIG (v1.3.29) generato da C++ a Java Classe vettoriale che non funziona correttamente

%include "std_vector.i" 
namespace std { 
    %template(Vector) vector<double>; 
    %template(Matrix) vector<vector<double> >; 
} 

%include "std_string.i" 

std_string.i e std_vector.i sono stati inclusi nella mia build di SWIG sto usando. La mia prima sorpresa è stata che l'output Java includeva la "propria" versione di SWIG della classe Vector (invece di usare java.util.Vector). Il mio vero problema è che i Vettori che vengono restituiti da queste funzioni non sembrano funzionare. Ad esempio, non riesco a recuperare il loro contenuto utilizzando get() (a volte il programma si blocca) o la funzione restituendo valori negativi. So che i dati Vector contengono dati perché ho codificato versioni "String" delle stesse funzioni che semplicemente eseguono l'iterazione attraverso lo Vector s (di nuovo nel codice C++ nativo) e restituiscono il contenuto in un valore separato da virgola String. Anche se questa è una soluzione valida, in definitiva mi piacerebbe che funzionasse correttamente con me potendo ricevere e manipolare lo Vectors. Qualsiasi aiuto/suggerimento sarebbe molto apprezzato.

+0

Non sei un utente SWIG, ma guardando 'std_vector.i' (le versioni di esso ho trovato on-line, in ogni caso),' size() 'si suppone essere un int'' non firmato, e SWIG si suppone di tradurre che a Java 'lungo'. Se ottieni dimensioni negative, sono pura assurdità, o sembrano come se stessero maltrattando "non firmati" come firmati? –

risposta

14

Il tipo di base appropriato per il wrapping di std::vector in Java è java.util.AbstractList. L'utilizzo di java.util.Vector come base sarebbe dispari perché si finirebbe con due set di archiviazione, uno su std::vector e uno su java.util.Vector.

Il motivo SWIG non lo fa per voi se è perché you can't have AbstractList<double> in Java, deve essere AbstractList<Double> (Double eredita da Object mentre double è un tipo primitivo).

Detto tutto ciò che ho messo insieme un piccolo esempio che avvolge std::vector<double> e std::vector<std::vector<double> > in Java. Non è completo, ma supporta lo stile "per ogni" iterazione in Java e set()/get() sugli elementi. Dovrebbe essere sufficiente per mostrare come implementare altre cose come/quando le vuoi.

Parlerò del file di interfaccia in sezioni man mano che andremo, ma in fondo sarà tutto sequenziale e completo.

Partendo num.i che definisce il nostro modulo num:

%module num 

%{ 
#include <vector> 
#include <stdexcept> 

std::vector<double> testVec() { 
    return std::vector<double>(10,1.0); 
} 

std::vector<std::vector<double> > testMat() { 
    return std::vector<std::vector<double> >(10, testVec()); 
} 
%} 

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

Abbiamo #include s per le generati num_wrap.cxx e due implementazioni di funzioni per la verifica (che potrebbe essere in un file separato ho appena messo qui fuori pigrizia/minimarket).

C'è anche un trucco con lo %pragma(java) jniclasscode= che mi piace usare nelle interfacce Java SWIG per far sì che l'oggetto/DLL condiviso venga caricato in modo trasparente per l'utente dell'interfaccia.

Il prossimo nel file di interfaccia sono le parti di std::vector che vogliamo avvolgere. Non sto usando std_vector.i perché abbiamo bisogno di fare alcune modifiche:

namespace std { 

    template<class T> class vector { 
     public: 
     typedef size_t size_type; 
     typedef T value_type; 
     typedef const value_type& const_reference; 
     %rename(size_impl) size; 
     vector(); 
     vector(size_type n); 
     size_type size() const; 
     size_type capacity() const; 
     void reserve(size_type n); 
     %rename(isEmpty) empty; 
     bool empty() const; 
     void clear(); 
     void push_back(const value_type& x); 
     %extend { 
      const_reference get_impl(int i) throw (std::out_of_range) { 
       // at will throw if needed, swig will handle 
       return self->at(i); 
      } 
      void set_impl(int i, const value_type& val) throw (std::out_of_range) { 
       // at can throw 
       self->at(i) = val; 
      } 
     } 
    }; 
} 

Il cambiamento principale è %rename(size_impl) size;, che racconta SWIG per esporre size() da std::vector come size_impl invece.Abbiamo bisogno di farlo perché Java si aspetta che size restituisca un int dove la versione std::vector restituisce un size_type che molto probabilmente non sarà int.

Next up nel file di interfaccia diciamo che cosa classe base e le interfacce che vogliamo implementare così come scrivere del codice Java in più per costringere le cose tra le funzioni con i tipi incompatibili:

%typemap(javabase) std::vector<double> "java.util.AbstractList<Double>" 
%typemap(javainterface) std::vector<double> "java.util.RandomAccess" 
%typemap(javacode) std::vector<double> %{ 
    public Double get(int idx) { 
    return get_impl(idx); 
    } 
    public int size() { 
    return (int)size_impl(); 
    } 
    public Double set(int idx, Double d) { 
    Double old = get_impl(idx); 
    set_impl(idx, d.doubleValue()); 
    return old; 
    } 

%} 

%typemap(javabase) std::vector<std::vector<double> > "java.util.AbstractList<Vector>" 
%typemap(javainterface) std::vector<std::vector<double> > "java.util.RandomAccess" 
%typemap(javacode) std::vector<std::vector<double> > %{ 
    public Vector get(int idx) { 
    return get_impl(idx); 
    } 
    public int size() { 
    return (int)size_impl(); 
    } 
    public Vector set(int idx, Vector v) { 
    Vector old = get_impl(idx); 
    set_impl(idx, v); 
    return old; 
    } 

%} 

Questo imposta una base classe di java.util.AbstractList<Double> per std::vector<double> e java.util.AbstractList<Vector> per std::vector<std::vector<double> > (Vector è ciò che chiameremo std::vector<double> sul lato Java dell'interfaccia).

Forniamo anche un'implementazione di get e set sul lato Java che può gestire il double a Double conversione e viceversa.

Infine nell'interfaccia aggiungiamo:

namespace std { 
    %template(Vector) std::vector<double>; 
    %template(Matrix) std::vector<vector<double> >; 
} 

std::vector<double> testVec(); 
std::vector<std::vector<double> > testMat(); 

Questo dice SWIG per riferirsi a std::vector<double> (con il tipo specifico) come Vector e analogamente per std::vector<vector<double> > come Matrix. Diciamo anche a SWIG di esporre le nostre due funzioni di test.

Next up, test.java, un semplice main in Java per esercitare il nostro codice un po ':

import java.util.AbstractList; 

public class test { 
    public static void main(String[] argv) { 
    Vector v = num.testVec(); 
    AbstractList<Double> l = v; 
    for (Double d: l) { 
     System.out.println(d); 
    } 
    Matrix m = num.testMat(); 
    m.get(5).set(5, new Double(5.0)); 
    for (Vector col: m) { 
     for (Double d: col) { 
     System.out.print(d + " "); 
     } 
     System.out.println(); 
    } 
    } 
} 

per compilare ed eseguire questo facciamo:

swig -java -c++ num.i 
g++ -Wall -Wextra num_wrap.cxx -shared -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux/ -o libnum.so 

javac test.java && LD_LIBRARY_PATH=. java test 

Ho provato questo con g ++ versione 4.4 e SWIG 1.3.40 su Linux/x86.

La versione completa di num.i può essere trovata here, ma può sempre essere ricostruita da questa risposta incollando ciascuna parte in un unico file.

Le cose che non hai implementato da AbstractList:

  1. add() - può essere implementato tramite push_back(), std_vector.i anche cerca di implementare qualcosa di compatibile per impostazione predefinita, ma non funziona con il Double vs double problema o corrispondere il tipo di ritorno specificato nel AbstractList (non dimenticare di incrementare modCount)
  2. remove() - non eccezionale per std::vector in termini di complessità temporale, ma non impossibile da implementare sia (lo stesso con 012.)
  3. Un costruttore che prende un altro Collection è consigliato, ma non implementato qui. Possono essere implementati nello stesso posto set() e get(), ma sarà necessario $javaclassname per denominare correttamente il costruttore generato.
  4. Si potrebbe voler utilizzare qualcosa come this per verificare che la conversione size_type ->int in size() sia sensata.
+0

Questo è abbastanza bello e davvero impressionante, anche se speravo di approfondire il problema del crash. Sto vedendo arresti anomali sulla versione Linux64 di un'app che funziona in modo solido su architetture a 32 bit e mi sto chiedendo dei possibili bug in agguato in std_vector.i stesso. –

+0

@ ErnestFriedman-Hill - Non riesco a vedere nulla di ovvio in 'std_vector.i' sulla mia versione di SWIG che potrebbe causare un simile comportamento. La mia ipotesi sarebbe una mappatura errata tra i tipi nativi di Java e qualche altro tipo nel C++ incartato, o forse un problema altrove in generale. Non posso davvero fare molte ipotesi informate oltre a ciò, anche se ho paura. Presto lo metterò alla prova su una macchina Linux/x86_64. Spero che ciò che ho scritto possa essere utile ad altri che guardano al wrapping di contenitori C++ per Java usando SWIG. – Flexo

+0

@ ErnestFriedman-Hill - Non riesco a riprodurre il problema che hai segnalato - puoi forse creare un esempio di lavoro minimo per questo? Un piccolo modulo che mostri più chiaramente il problema del crash sarebbe molto utile. – Flexo

4

Sono la persona che ha offerto la taglia su questa domanda perché ho avuto lo stesso problema. Sono un po 'imbarazzato nel riferire che finalmente ho trovato la soluzione reale - ed è nel manuale SWIG! La correzione consiste nell'utilizzare il flag -fno-strict-aliasing per g++ durante la compilazione del codice generato, semplice come quello. Detesto ammettere che ci sia voluto un bel po 'di Google per scoprirlo definitivamente.

Il problema è che le versioni recenti di g++ fanno alcune ottimizzazioni aggressive che fanno ipotesi sul puntatore aliasing che non detengono il codice SWIG genera per std_vector (e in altri casi.) g++ 4.1 non fa questo, ma 4.4.5 lo fa sicuramente. Le ipotesi sono perfettamente valide e consentite dall'attuale standard ISO, anche se non sono sicuro di quanto siano noti. Fondamentalmente, è che due puntatori di tipi diversi (con poche eccezioni) possono mai puntare allo stesso indirizzo. Il codice che SWIG genera per la conversione tra puntatore su oggetto e jlong cade in conflitto con questa regola.

Problemi correlati