给定一个任意的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,但是有两个原因不这样做:
  • C++库接口(interface)必须更改
  • 使用Java 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中使用访问器函数公开它们。给他们起名字。

    10-06 14:39
    查看更多