作为包装类的参数传递时,在Java和C ++类实例之间进行转换

时间:2016-06-02 10:23:00

标签: java c++ swig

给定一个任意的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包装的C ++类生成Java代理类,例如

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,但有两个理由不这样做:

  • C ++ Library界面必须更改
  • 使用Java Coord类的Java代码的一部分将因现在必须使用代理而变慢(或者Java代码必须处理两个coord类,一个本机代理,并在之间进行转换)一直两个)

我可以想象如何使用i - 文件并使用typemap

%typemap(jstype) std::array<double, 3> "com.foo.bar.Coord"

但从那里我真的不知道去哪里。

1 个答案:

答案 0 :(得分:2)

对此有很多考虑因素以及解决此问题的几种可能方法,因此我将在我认为的逻辑顺序中处理其中的一些。

我首先创建了一个测试头文件,以便在我的演示中使用,它从您展示的内容中进行了一些小调整:

#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类型std::array<double, 3>的可用包装形式。

显然,尽管使用Coord,这实际上并不符合您的要求,因此我们希望在函数调用时编写一些类型映射以在Java / C ++类型之间进行转换。你可以在几个地方实际做到这一点。最简单的是将其写为javain类型映射:

%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 ++函数返回这些函数,另一个变量具有支持非const引用的post属性,请求详细信息)

你会注意到,虽然它符合你要求的功能要求,但另一个目标是避免过多的跨语言函数调用,但是在这里我们有4个额外的包括一个内存分配。

所以为了解决这个问题,我们可以开始让我们的界面变得更聪明。 SWIG(多年来)在其库中有一些数组的辅助代码,arrays_java.i。我们可以使用它在一次调用中构造我们的临时C ++对象,方法是使用%extend添加一个以double[3]作为输入的新构造函数:

%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 ++,然后在in typemap中按摩它。我尝试了以下方法:

%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在这里无效)。

这真的不是一个大问题,我没有花太多时间在上面,因为我们可以通过编写array_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个双精度的临时数组,这在这种方法中基本上是不可避免的,因为我们没有办法增加传递给C ++的参数数量,只能减少这个数量。

如果你想argout类型映射可以支持通过非const引用传递给修改输入的函数。您希望使用GetDoubleArrayElements调用的最后一个参数来查看它是否是副本,并将取消映射保存到argout类型映射并在此时制作您自己的副本。

作为一种完全替代的方法,我们可以选择将Coord对象一直传递给intype作为jobject并在那里进行3次JNI调用以获取x,y和z成员变量值点。就我个人而言,我不喜欢上面的数组这个想法,我会使用上面的例子来创建一个参数,用于将Coord类的组件作为数组存储在内部并使用内部的访问器函数公开它们Java如果你想给他们起名字。