SWIG(v1.3.29)生成的C++到Java向量类表现不正常。

11

我有一些本地的C++代码,我正在使用SWIG将其转换为Java,以便我的Java应用程序可以使用它。特别是有一些返回std::vector的函数。这是我的接口文件的片段:

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

%include "std_string.i"

我使用的SWIG版本包含了std_string.istd_vector.i。我的第一个惊喜是,Java输出包括SWIG自己的Vector类(而不是使用java.util.Vector)。我的真正问题是,从这些函数返回的向量似乎不起作用。例如,我无法使用get()检索它们的内容(有时会导致程序崩溃),或者size()函数返回负值。我知道Vector包含数据,因为我编写了相同功能的'String'版本,简单地迭代遍历Vector (回到本机C++代码),并将内容作为逗号分隔的String值返回。虽然这是一个有效的解决方法,但我最终希望能正确地接收和操作Vector。任何帮助/提示都将不胜感激。


我不是SWIG的用户,但是看了一下std_vector.i(无论我在网上找到的哪个版本),size()应该是一个unsigned int,而SWIG应该将其转换为Java的long。如果你得到了负数大小,它们是纯粹的无意义,还是看起来像是将unsigned误解为有符号的? - David Moles
2个回答

16
适用于将std::vector包装在Java中的合适基础类型是java.util.AbstractList。使用java.util.Vector作为基础类型会很奇怪,因为你最终会得到两组存储器,一组在std::vector中,另一组在java.util.Vector中。

然而,SWIG不为你自动完成此操作的原因是在Java中无法使用AbstractList<double>,必须使用AbstractList<Double>DoubleObject继承,而double是原始类型)。

话虽如此,我已经准备了一个小例子,在Java中很好地包装了std::vector<double>std::vector<std::vector<double> >。它不是完整的,但支持Java中的“for each”样式迭代以及元素的set()/get()。它应该足以展示如何在需要时实现其他内容。

我会按顺序详细讲解接口文件的各个部分,但基本上它将是连续完整的。

从定义模块numnum.i开始:

%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以及两个函数的实现来进行测试(它们可以在单独的文件中,但出于懒惰/方便,我将它们放在这里)。
此外,在Java SWIG接口中使用的%pragma(java) jniclasscode=技巧可以使共享对象/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 将 std::vectorsize() 暴露为 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;
  }

%}

这将为std::vector<double>设置一个基类为java.util.AbstractList<Double>,并且为std::vector<std::vector<double>>设置一个基类为java.util.AbstractList<Vector>(在接口的Java端我们将称之为Vector,即std::vector<double>)。
此外,我们还提供了Java端的getset实现,可以处理doubleDouble的转换。
最后,在接口中我们添加如下内容:
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公开我们的两个测试函数。
接下来是一个简单的Java main,用于对我们的代码进行一些操作: test.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

我在Linux/x86上使用g++ 4.4版本和SWIG 1.3.40进行了测试。

num.i的完整版本可以在这里找到,但是可以通过将每个部分粘贴到一个文件中来重构此答案以获取完整版本。

我从AbstractList中未实现的内容:

  1. add() - 可以通过push_back()实现,默认情况下std_vector.i甚至尝试实现与之兼容的东西,但它不能解决Doubledouble问题或匹配AbstractList中指定的返回类型(不要忘记增加modCount
  2. remove() - 对于std::vector来说时间复杂度不太好,但也不是不可能实现(同样适用于modCount
  3. 建议使用另一个Collection作为构造函数,但这里没有实现。可以在与set()get()相同的位置实现,但需要$javaclassname来正确命名生成的构造函数。
  4. 您可能希望使用类似于这样的方法来检查size_type->int转换是否合理。

这很不错,真的令人印象深刻,尽管我希望能够了解崩溃问题的内部情况。我在 Linux64 版本的应用程序上看到了崩溃现象,而该应用程序在 32 位架构上运行稳定,因此我想知道 std_vector.i 中可能存在的潜在错误。 - Ernest Friedman-Hill
@ErnestFriedman-Hill - 我在我的 SWIG 版本的 std_vector.i 中没有看到任何明显会导致这种行为的问题。我猜测可能是本地 Java 类型与包装的 C++ 中的某些类型之间的映射不正确,或者可能是其他地方出现了问题。但是,除此之外,我无法做出更多有根据的猜测。不过,我很快就会在 Linux/x86_64 机器上测试这个问题。我希望我所写的对于那些使用 SWIG 包装 C++ 容器以供 Java 使用的人们有用。 - Flexo
@ErnestFriedman-Hill - 我无法重现您报告的问题 - 您能否为此准备一个最小工作示例?一个展示崩溃问题更清晰的小模块将非常有帮助。 - Flexo
@ErnestFriedman-Hill - 你看过http://www.swig.org/Doc1.3/Java.html#jvm64吗?看起来这可能与此有关。似乎在1.3.29之后不久就检查了一些相关代码,但我建议的包装器也应该很好用。 - Flexo
谢谢你继续思考这个问题!明天早上我会回去工作,看看我能做什么。我实际上从未详细解释过我的特定问题(记住这不是我的问题!)而且情况实际上更加复杂,但是关于sizeof(long)的提示可能有用。我看到的问题取决于编译器。 - Ernest Friedman-Hill
FYI,我找到了解决方案,已经作为新答案发布在这个问题下。 - Ernest Friedman-Hill

4

我是在这个问题上提供赏金的人,因为我遇到了同样的问题。我有点尴尬地报告,我终于找到了真正的解决方案——在 SWIG 手册中!解决方法是在编译生成的代码时,使用 -fno-strict-aliasing 标志来替代 g++ ——就是这么简单。我不得不承认,我用了很多时间在 Google 上才找到这个答案。

问题在于近期版本的 g++ 做了一些激进的优化,假设指针别名对于 SWIG 生成的 std_vector 代码(以及其他情况)不成立。 g++ 4.1 没有这样做,但 4.4.5 明显有。这些假设完全有效,并且符合当前的 ISO 标准,尽管我不确定它们有多出名。基本上,它是两个不同类型的指针(有少数例外)永远不能指向相同的地址。SWIG 生成的用于在指向对象和 jlong 之间进行转换的代码违反了这个规则。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接