我有一些原生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.i
和std_vector.i
。我的第一个惊喜是,Java输出包括SWIG“自己的”版本的Vector类(而不是使用java.util.Vector
)。我真正的问题是从这些函数返回的Vectors似乎不起作用。例如,我不能使用get()
(有时会使程序崩溃)或返回负值的size()
函数来检索它们的内容。我知道Vector
包含数据,因为我编写了相同函数的“字符串”版本,它们只是简单地迭代Vector
s (回到原生C++代码中),并返回逗号分隔的String
值中的内容。虽然这是一个有效的解决方法,但最终我希望它能正常工作,使我能够接收和操作Vectors
。任何帮助/建议都将不胜感激。
发布于 2011-11-18 22:49:35
在Java语言中包装std::vector
的合适的基类型是java.util.AbstractList
。使用java.util.Vector
作为基础会很奇怪,因为最终会有两组存储,一组在std::vector
中,另一组在java.util.Vector
中。
SWIG没有为你做这件事的原因是因为you can't have AbstractList
in Java,它必须是AbstractList<Double>
(Double
继承自Object
,而double
是一个原始类型)。
说到这里,我用Java语言编写了一个很好地包装了std::vector<double>
和std::vector<std::vector<double> >
的小示例。它并不完整,但它支持Java语言中的"for each“迭代风格和元素上的set()
/get()
。它应该足以展示如何在你需要的时候实现其他东西。
我将在接下来的部分中讨论接口文件,但基本上都是连续和完整的。
从定义我们的模块num
的num.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);
}
}
%}
我们有用于生成的num_wrap.cxx
的#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::vector
中的size()
暴露为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>
基类(Vector
是我们在接口的Java端称为std::vector<double>
的基类)。
我们还在Java端提供了一个get
和set
的实现,它可以处理double
到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
,这是一个用Java语言编写的简单main
,用来测试一下我们的代码:
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++ V4.4和SWIG 1.3.40进行了测试。
可以在here中找到num.i
的完整版本,但始终可以通过将每个部分粘贴到一个文件中来根据此答案进行重建。
我还没有从AbstractList
实现的东西
add()
-可以通过push_back()
实现,std_vector.i甚至尝试在默认情况下实现一些兼容的东西,但它不能解决Double
与double
的问题,也不能匹配AbstractList
中指定的返回类型(不要忘记递增modCount
)remove()
-在时间复杂性方面对std::vector
来说不是很好,但也不是不可能实现)(建议使用另一个Collection
的modCount
)set()
和get()
的同一位置实现,但需要$javaclassname
来命名生成的构造函数correctly.size()
中的size_type
->int
转换是否正常。发布于 2011-12-07 13:22:53
我是悬赏这个问题的人,因为我也有同样的问题。我有点尴尬地报告,我终于找到了真正的解决方案--它在SWIG手册中!修复方法是在编译生成的代码时使用g++
的-fno-strict-aliasing
标志--就这么简单。我不得不承认,我花了很多时间在谷歌上搜索才最终发现了这一点。
问题是,最近版本的g++
做了一些积极的优化,对指针别名做了一些假设,这些假设不适用于SWIG为std_vector
(以及其他情况)生成的代码。g++
4.1不能做到这一点,但4.4.5肯定做到了这一点。这些假设是完全有效的,并被当前的ISO标准所允许,尽管我不确定它们有多广为人知。基本上,两个不同类型的指针(有几个例外)永远不能指向同一个地址。SWIG生成的用于在指向对象的指针和jlong
之间进行转换的代码与此规则冲突。
https://stackoverflow.com/questions/8145605
复制