给定一个任意的Java类,说
public class Coord {
public double x;
public double y;
public double z;
public Coord(double x, double y, double z) {
this.x = x; this.y = y; this.z = z;
}
}
和一个任意的C++类(或结构),例如C++ 11 STL类
std::array<double, 3>
,以及一个使用Swig打包以生成Java代理类的C++类,例如
class Object {
move_center(std::array<double, 3> vec) {
// C++ code
}
}
我希望能够用Java编写
Object obj;
// initialize obj
Coord vec = new Coord(1.5,2.5,3.5);
obj.move_center(vec);
并让包装器使用
std::array<double, 3>
等值构造vec[0] = 1.5
。我知道我可以在C++中定义一个
Coord
类,让Swig包装它并在Java中使用代理Coord
,但是有两个原因不这样做:Coord
类的Java代码部分将因必须立即使用代理而减慢速度(或者Java代码必须处理两个coord类,一个是本地的一个代理,并始终在两个之间进行转换) 我可以想象要走的路是使用
i
-file并使用typemap
%typemap(jstype) std::array<double, 3> "com.foo.bar.Coord"
但是从那里我真的不知道要去哪里。
最佳答案
有很多考虑因素,有几种可能的解决方法,因此,我将按照逻辑顺序来研究其中的一些问题。
我首先创建了一个要在演示中使用的测试头文件,该文件对您显示的内容进行了一些细微调整:
#include <array>
struct Object {
void move_center(std::array<double, 3> vec) {
// C++ code
}
};
首先,如果您使用的是最新版本的SWIG(肯定比3.0.2更新,虽然不确定确切是哪个版本),您将对
std::array
有一些库支持,我们可以以此为起点。%module test
%{
#include "test.hh"
%}
%include <std_array.i>
%template(Vec3) std::array<double, 3>;
%include "test.hh"
以此为起点就足够了,您将获得一个使用类型为
Vec3
的可用接口(interface),该类型是std::array<double, 3>
的可用包装形式。显然,尽管使用
Coord
确实不能满足您的要求,所以我们想编写一些类型映射在函数调用时在Java / C++类型之间进行转换。您实际上可以在几个地方这样做。最简单的方法是将其编写为javain typemap:%module test
%{
#include "test.hh"
%}
%include <std_array.i>
%template(Vec3) std::array<double, 3>;
%typemap(jstype) std::array<double, 3> "Coord"
%typemap(javain,pre=" Vec3 temp$javainput = new Vec3();\n"
" temp$javainput.set(0, $javainput.x);\n"
" temp$javainput.set(1, $javainput.y);\n"
" temp$javainput.set(2, $javainput.z);",
pgcppname="temp$javainput") std::array<double, 3>, const std::array<double, 3>& "$javaclassname.getCPtr(temp$javainput)"
%include "test.hh"
基本上,在您显示给我们的类型图之上,所有这些操作都是将代码插入生成的Java函数调用中。此代码只读取x,y,z并将它们放入专门为调用持续时间创建的临时
Vec3
中。(如果您愿意,可以添加一个javaout类型映射,以从C++函数返回它们,以及另一个具有post属性的变体,该属性支持非const引用,可根据要求提供详细信息)
您会在这里注意到,尽管它满足您所要求的功能要求,但另一个目标是避免过多的跨语言函数调用,但是在这里,我们还有4个额外功能,其中包括一个内存分配。
因此,为了解决这个问题,我们可以开始使我们的界面更加智能。 SWIG(很久以来)在其库arrays_java.i中拥有一些数组的辅助代码。我们可以使用
%extend
来添加一个新的构造函数,以double[3]
作为输入,从而在一次调用中使用它来构造临时C++对象:%module test
%{
#include "test.hh"
#include <algorithm>
%}
%include <std_array.i>
%include <arrays_java.i>
%template(Vec3) std::array<double, 3>;
%extend std::array<double, 3> {
std::array<double, 3>(double in[3]) {
std::array<double, 3> temp;
std::copy_n(in, 3, std::begin(temp));
return new std::array<double, 3>(temp);
}
}
%typemap(jstype) std::array<double, 3> "Coord"
%typemap(javain,pre=" Vec3 temp$javainput = new Vec3(new double[]{$javainput.x, $javainput.y, $javainput.z});",
pgcppname="temp$javainput") std::array<double, 3>, const std::array<double, 3>& "$javaclassname.getCPtr(temp$javainput)"
%include "test.hh"
但是,我们可以做得更好,为什么不将临时结构从Java移到C++。下一步,我希望SWIG将
double[3]
传递给C++,然后在intypemap中对其进行按摩。我尝试了以下方法:%module test
%{
#include "test.hh"
#include <algorithm>
%}
%include <arrays_java.i>
%apply double[3] { std::array<double, 3> };
%typemap(jstype) std::array<double, 3> "Coord"
%typemap(javain) std::array<double, 3>, const std::array<double, 3>& "new double[]{$javainput.x, $javainput.y, $javainput.z}"
%typemap(in) std::array<double, 3> //....
%include "test.hh"
请注意,尽管现在我们已经在SWIG库中删除了对std_array.i的要求,而仅依赖于arrays_java.i。尽管这实际上没有用(
%apply
在这里无效)。这确实不是什么大问题,我也没有花太多时间,因为我们可以通过自己编写arrays_java提供的JNI调用来解决此问题:
%module test
%{
#include "test.hh"
#include <algorithm>
%}
%typemap(jstype) std::array<double, 3> "Coord"
%typemap(javain) std::array<double, 3>, const std::array<double, 3>& "new double[]{$javainput.x, $javainput.y, $javainput.z}"
%typemap(jtype) std::array<double, 3> "double[]"
%typemap(jni) std::array<double, 3> "jdoubleArray"
%typemap(in) std::array<double, 3> {
if (!$input || JCALL1(GetArrayLength, jenv, $input) != 3) {
SWIG_JavaThrowException(jenv, SWIG_JavaIndexOutOfBoundsException, "incorrect array size");
return $null;
}
double *arr = JCALL2(GetDoubleArrayElements, jenv, $input, NULL);
std::copy_n(arr, 3, $1.begin());
JCALL3(ReleaseDoubleArrayElements, jenv, $input, arr, JNI_ABORT);
}
%include "test.hh"
这已开始尽可能接近我们目标的最小开销包装。我们只编写了足够的JNI,以允许我们一次性将整个
double[]
数组复制到std::array
中。 (您的C++编译器应该在优化复制操作方面做得很好)。我们仍然在Java内部分配3个double的临时数组,这种方法在很大程度上是不可避免的,因为我们没有增加传递给C++的参数数量的方法,只能减少该数量。如果要使用argout,typemap可以支持通过非常量引用传递给修改输入的函数。您想使用
GetDoubleArrayElements
调用的最后一个参数来查看它是否是副本,并保存取消映射,直到argout类型映射并在那时创建自己的副本。作为一种完全替代的方法,我们可以选择将
Coord
对象作为Jobject一直传递到intypemap中,并在那里进行3次JNI调用以获取x,y和z成员变量值。我个人不喜欢上面的数组想法,我将使用上面的示例作为参数,将Coord
类的组件内部存储为数组,并在Java中使用访问器函数公开它们。给他们起名字。