SWIG (v1.3.29) сгенерировал класс С++ для Java Vector, не действующий должным образом

У меня есть собственный код на С++, который я конвертирую в Java с помощью SWIG, чтобы мое приложение Java могло его использовать. В частности, есть некоторые функции, которые возвращают std::vector. Вот фрагмент моего файла интерфейса:

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

%include "std_string.i"

std_string.i и std_vector.i были включены в мою сборку SWIG, которую я использую. Моей первой неожиданностью было то, что выход Java включал "собственную" версию SWIG класса Vector (в отличие от использования java.util.Vector). Моя реальная проблема заключается в том, что векторы, которые возвращаются из этих функций, похоже, не работают. Например, я не могу получить их содержимое, используя get() (иногда сбой программы) или функцию size(), возвращающую отрицательные значения. Я знаю, что Vector содержит данные, потому что я закодировал версии "String" тех же функций, которые просто перебирают через Vector (обратно в собственный код на С++) и возвращают содержимое в значении String, разделенном запятыми. Хотя это допустимое решение проблемы, в конечном счете, мне бы хотелось, чтобы это правильно работало, и я мог получать и манипулировать Vectors. Любые советы/подсказки будут очень оценены.

Ответ 1

Соответствующим базовым типом для упаковки std::vector в Java является java.util.AbstractList. Использование java.util.Vector в качестве базы было бы нечетным, потому что у вас было бы два набора хранилищ, один в std::vector и один в java.util.Vector.

Причина, по которой SWIG это не делает, потому что вы не можете иметь AbstractList<double> в Java, она должна быть AbstractList<double> (Double наследует от Object, тогда как Double является примитивным типом).

Сказав все, что я собрал, небольшой пример, который хорошо переносит std::vector<double> и std::vector<std::vector<double> > на Java. Он не завершен, но поддерживает стили итерации "для каждого" в Java и set()/get() для элементов. Этого должно быть достаточно, чтобы показать, как реализовать другие вещи, когда вы хотите их.

Я расскажу через интерфейс файл в разделах, как мы идем, но в основном все будет последовательным и полным.

Начиная с num.i, который определяет наш модуль 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);
    }
  }
%}

У нас есть #include для сгенерированных num_wrap.cxx и двух реализаций функций для тестирования (они могут быть в отдельном файле, который я просто выложил из них из лени/удобства).

Там также есть трюк с %pragma(java) jniclasscode=, который я хотел бы использовать в интерфейсах Java SWIG, чтобы обеспечить прозрачный доступ к общему объекту /DLL для пользователя интерфейса.

Далее в файле интерфейса находятся части std::vector, которые мы хотим обернуть. Я не использую std_vector.i, потому что нам нужно сделать несколько изменений:

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

Основное изменение здесь %rename(size_impl) size;, которое сообщает SWIG об открытии size() из std::vector как size_impl. Мы должны сделать это, потому что Java ожидает, что size вернет int, где, когда версия std::vector вернет size_type, которая скорее всего не будет int.

Далее в файле интерфейса мы расскажем, какой базовый класс и интерфейсы мы хотим реализовать, а также написать некоторый дополнительный код Java для упрощения функций между функциями с несовместимыми типами:

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

%}

Это устанавливает базовый класс java.util.AbstractList<Double> для std::vector<double> и java.util.AbstractList<Vector> для std::vector<std::vector<double> > (Vector - это то, что мы будем называть std::vector<double> на стороне Java интерфейса).

Мы также предоставляем реализацию get и set на стороне Java, которая может обрабатывать преобразование Double to Double и обратно.

Наконец, в интерфейсе добавим:

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

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

Это говорит, что SWIG ссылается на std::vector<double> (с конкретным типом) как Vector и аналогично для std::vector<vector<double> > как Matrix. Мы также сообщаем SWIG разоблачить две наши тестовые функции.

Далее, test.java, простой main в Java, чтобы немного реализовать наш код:

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

Чтобы создать и запустить это, мы делаем:

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

Я тестировал это с помощью g++ версии 4.4 и SWIG 1.3.40 на Linux/x86.

Полная версия num.i может быть найдена здесь, но всегда может быть восстановлена ​​из этого ответа, вставив каждую часть вместе в один файл.

Вещи, которые я не реализовал из AbstractList:

  • add() - может быть реализован через push_back(), std_vector.i даже пытается реализовать что-то совместимое по умолчанию, но оно не работает с проблемой Double vs Double или соответствует типу возврата, указанному в AbstractList (Не забудьте прирастить modCount)
  • remove() - не очень подходит для std::vector с точки зрения сложности времени, но не невозможно реализовать (аналогично modCount)
  • Рекомендуется использовать конструктор, который принимает другой Collection, но не реализован. Может быть реализовано в том же месте set() и get(), но ему потребуется $javaclassname, чтобы правильноменовать сгенерированный конструктор.
  • Возможно, вы захотите использовать что-то вроде этого, чтобы проверить, что преобразование size_typeint в size() является нормальным.

Ответ 2

Я тот человек, который предложил щедрость по этому вопросу, потому что у меня была такая же проблема. Мне немного стыдно сообщать, что я наконец нашел реальное решение - и это в руководстве SWIG! Исправление заключается в использовании флага -fno-strict-aliasing для g++ при компиляции сгенерированного кода - просто. Мне не терпится признать, что для этого, наконец, выяснилось, что для этого понадобилось много Googling.

Проблема заключается в том, что последние версии g++ совершают некоторые агрессивные оптимизации, которые делают предположения о сглаживании указателей, которые не сохраняются для кода SWIG, генерируемого для std_vector (и в других случаях.) g++ 4.1 doesn ' t сделать это, но 4.4.5 определенно делает. Эти предположения совершенно верны и разрешены действующим стандартом ИСО, хотя я не уверен, насколько хорошо они известны. В принципе, это два указателя разных типов (за некоторыми исключениями) никогда не могут указывать на один и тот же адрес. Код, который SWIG генерирует для преобразования между указателем на объект и jlong, отклоняется от этого правила.