2011-11-16 8 views
11

Tengo un código nativo de C++ que estoy convirtiendo a Java usando SWIG para que mi aplicación Java pueda usarlo. En particular, hay algunas funciones que devuelven std :: vector. He aquí un fragmento de mi archivo de interfaz:SWIG (v1.3.29) generado C++ a Java La clase Vector no funciona correctamente

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

%include "std_string.i" 

std_string.i y std_vector.i se incluyeron en mi construcción de TRAGO que estoy usando. Mi primera sorpresa fue que la salida de Java incluía la versión "propia" de SWIG de la clase Vector (en oposición a usar java.util.Vector). Mi verdadero problema es que los vectores que devuelven estas funciones no parecen funcionar. Por ejemplo, no puedo recuperar sus contenidos usando get() (a veces bloqueando el programa) o la función size() devolviendo valores negativos. Sé que los Vector s contienen datos porque codifiqué las versiones 'String' de las mismas funciones que simplemente iteran a través del Vector s (de vuelta en el código C++ nativo) y devuelven el contenido en un valor de String separado por comas. Si bien esta es una solución válida, en última instancia, me gustaría que esto funcione correctamente para poder recibir y manipular el Vectors. Cualquier ayuda/sugerencia sería muy apreciada.

+0

No soy un usuario de SWIG, pero al mirar 'std_vector.i' (las versiones que encuentro en línea, de todos modos),' size() 'se supone que es' unsigned int', y SWIG se supone que traduce eso a un Java 'largo'. Si obtiene tamaños negativos, ¿son tonterías sin sentido, o se ven como si estuvieran maltratando 'unsigned' como firmado? –

Respuesta

14

El tipo de base apropiado para envolver std::vector en Java es java.util.AbstractList. Usar java.util.Vector como base sería extraño porque terminaría con dos juegos de almacenamiento, uno en el std::vector, y el otro en el java.util.Vector.

La razón TRAGO no hace esto para usted, aunque se debe a you can't have AbstractList<double> in Java, que tiene que ser AbstractList<Double> (Double hereda de Object mientras que double es un tipo primitivo).

Habiendo dicho todo eso, he reunido un pequeño ejemplo que envuelve std::vector<double> y std::vector<std::vector<double> > muy bien en Java. No está completo, pero admite el estilo de iteración "para cada uno" en Java y set()/get() en los elementos. Debería ser suficiente para mostrar cómo implementar otras cosas cuando/cuando las desee.

Hablaré a través del archivo de interfaz en las secciones a medida que avanzamos, pero básicamente todo será secuencial y completo.

A partir de num.i que define nuestro módulo 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); 
    } 
    } 
%} 

Tenemos #include s para los generados num_wrap.cxx y dos implementaciones de funciones para la prueba (que podría ser en un archivo separado acabo de poner aquí de pereza/conveniencia).

También hay un truco allí con el %pragma(java) jniclasscode= que me gusta usar en las interfaces Java SWIG para hacer que el objeto compartido/DLL se cargue de forma transparente para el usuario de la interfaz.

El siguiente en el archivo de interfaz son las partes de std::vector que queremos envolver. No estoy usando std_vector.i porque tenemos que hacer algunos cambios:

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; 
      } 
     } 
    }; 
} 

El cambio principal es %rename(size_impl) size;, que dice TRAGO para exponer size() de std::vector como size_impl lugar.Necesitamos hacer esto porque Java espera que size devuelva un int donde la versión std::vector devuelve un size_type que muy probablemente no será int.

siguiente en el archivo de interfaz que indicarle lo que la clase base y las interfaces que queremos implementar, así como escribir algo de código Java adicional para obligar a las cosas entre funciones con tipos incompatibles:

%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; 
    } 

%} 

Esto establece una base clase de java.util.AbstractList<Double> para std::vector<double> y java.util.AbstractList<Vector> para std::vector<std::vector<double> > (Vector es lo que llamaremos std::vector<double> en el lado de Java de la interfaz).

También suministramos una implementación de get y set en el lado de Java que puede manejar la conversión double-Double y viceversa.

Por último, en la interfaz que añadir:

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

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

Esto le dice TRAGO para referirse a std::vector<double> (con el tipo específico) como Vector y lo mismo para std::vector<vector<double> > como Matrix. También le decimos a SWIG que exponga nuestras dos funciones de prueba.

El siguiente, test.java, un simple main en Java para ejercer nuestro código un poco:

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(); 
    } 
    } 
} 

para generar y ejecutar esto lo hacemos:

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 

He probado esto con g ++ versión 4.4 y SWIG 1.3.40 en Linux/x86.

La versión completa de num.i se puede encontrar en here, pero siempre se puede reconstruir a partir de esta respuesta pegando cada parte en un solo archivo.

Cosas que no he implementado desde AbstractList:

  1. add() - se puede implementar a través de push_back(), incluso std_vector.i intenta poner en práctica algo compatible de forma predeterminada, pero no funciona con la Double vs double problema o que coincida con el tipo de retorno se especifica en AbstractList (no se olvide de incrementar modCount)
  2. remove() - no es genial para std::vector en cuanto a la complejidad del tiempo, pero no imposible de poner en práctica ni (lo mismo con 012.)
  3. Se recomienda un constructor que tome otro Collection, pero no implementado aquí. Puede implementarse en el mismo lugar set() y get(), pero necesitará $javaclassname para nombrar correctamente el constructor generado.
  4. Es posible que desee utilizar algo como this para comprobar que la conversión size_type ->int en size() es correcta.
+0

Esto es bastante agradable y realmente impresionante, aunque esperaba una idea del problema. Estoy viendo bloqueos en la versión Linux64 de una aplicación que funciona sólidamente en arquitecturas de 32 bits, y me pregunto sobre posibles errores al acecho en std_vector.i mismo. –

+0

@ ErnestFriedman-Hill - No puedo ver nada obvio en el 'std_vector.i' en mi versión de SWIG que pueda causar tal comportamiento. Mi suposición sería una asignación incorrecta entre los tipos nativos de Java y algún tipo en otro lugar en el C++ ajustado, o posiblemente un problema en otro lugar de manera más general. Realmente no puedo hacer una gran conjetura más allá de eso, aunque me temo. Sin embargo, probaré esto en una máquina Linux/x86_64 en breve. Espero que lo que he escrito sea útil para otros que buscan envolver contenedores C++ para Java usando SWIG. – Flexo

+0

@ ErnestFriedman-Hill - No puedo reproducir el problema que ha informado. ¿Puede armar un ejemplo mínimo de trabajo para eso? Un pequeño módulo que muestre el problema de bloqueo más claramente sería muy útil. – Flexo

4

Soy la persona que ofreció la recompensa en esta pregunta porque tuve el mismo problema. Estoy un poco avergonzado de informar que finalmente encontré la verdadera solución, ¡y está en el manual de SWIG! La solución es usar el indicador -fno-strict-aliasing para g++ al compilar el código generado, así de simple. Odio admitir que se requirió mucha búsqueda en Google para finalmente descubrir esto.

El problema es que las versiones recientes de g++ hacer algunas optimizaciones agresivas que hacen suposiciones acerca de aliasing puntero que no estén en posesión del código genera TRAGO para std_vector (y en otros casos.) g++ 4.1 no hace esto, pero 4.4.5 definitivamente lo hace. Las suposiciones son perfectamente válidas y están permitidas por el estándar ISO actual, aunque no estoy seguro de qué tan conocidas sean. Básicamente, es que dos punteros de diferentes tipos (con algunas excepciones) pueden nunca apuntan a la misma dirección. El código que genera SWIG para convertir puntero-a-objeto y jlong entra en conflicto con esta regla.

Cuestiones relacionadas