将数组作为指针+大小或范围传递给包装函数

Fle*_*exo 8 c c++ java swig

给出如下标题:

#include <iostream>
#include <algorithm>
#include <iterator>

inline void foo(const signed char *arr, size_t sz) {
  std::copy_n(arr, sz, std::ostream_iterator<int>(std::cout, "\n"));
}

inline void bar(const signed char *begin, const signed char *end) {
  std::copy(begin, end, std::ostream_iterator<int>(std::cout, "\n"));
}
Run Code Online (Sandbox Code Playgroud)

(为方便起见,我在这里使用了C++ 11,如果改变了实现,可以使用C或C++)

如何将这些函数包装在Java端只接受一个数组,并使用数组的(已知)大小为这些函数提供第二个参数?

Fle*_*exo 12

这个问题的关键在于,要包装这些函数中的任何一个,您将需要使用多参数类型映射.

序言是SWIG的标准.我使用我个人喜欢的prgama自动加载共享库,而界面用户不需要知道:

%module test

%{
#include "test.hh"
%}

%pragma(java) jniclasscode=%{
  static {
    try {
        System.loadLibrary("test");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load. \n" + e);
      System.exit(1);
    }
  }
%}
Run Code Online (Sandbox Code Playgroud)

首先,您需要使用一些Java类型映射来指示SWIG将其byte[]用作Java接口的两个部分的类型--JNI和调用它的包装器.在生成模块文件中,我们将使用JNI类型jbyteArray.我们将输入直接从SWIG接口传递给它生成的JNI.

%typemap(jtype) (const signed char *arr, size_t sz) "byte[]"
%typemap(jstype) (const signed char *arr, size_t sz) "byte[]"
%typemap(jni) (const signed char *arr, size_t sz) "jbyteArray"
%typemap(javain) (const signed char *arr, size_t sz) "$javainput"
Run Code Online (Sandbox Code Playgroud)

完成后,我们可以编写一个多参数类型映射:

%typemap(in,numinputs=1) (const signed char *arr, size_t sz) {
  $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL);
  $2 = JCALL1(GetArrayLength, jenv, $input);
}
Run Code Online (Sandbox Code Playgroud)

in typemap的工作是将我们通过JNI调用给出的内容转换为实际函数真正期望作为输入的内容.我曾经numinputs=1指出两个真正的函数参数只在Java端接受一个输入,但无论如何这是默认值,因此不需要明确说明.

在这个typemap中$1是typemap 的第一个参数,即在这种情况下我们函数的第一个参数.我们通过寻求指向Java数组的底层存储的指针来设置它(实际上可能是也可能不是副本).我们设置$2第二个typemap参数为数组的大小.

JCALLn这里的宏确保typemap可以使用C和C++ JNI进行编译.它扩展到适当的语言调用.

一旦真正的函数调用返回,我们需要另一个类型映射来清理:

%typemap(freearg) (const signed char *arr, size_t sz) {
  // Or use  0 instead of ABORT to keep changes if it was a copy
  JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT); 
}
Run Code Online (Sandbox Code Playgroud)

这调用ReleaseByteArrayElements告诉JVM我们已经完成了数组.它需要指针我们从中获取的Java数组对象.此外,它还需要一个参数来指示是否应该将内容复制回来,如果它们被修改并且我们获得的指针首先是副本.(我们传递NULL的参数是一个指向a的可选指针jboolean,指示我们是否已经获得了副本).

对于第二个变体,类型图基本相似:

%typemap(in,numinputs=1) (const signed char *begin, const signed char *end) {
  $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL);
  const size_t sz = JCALL1(GetArrayLength, jenv, $input);
  $2 = $1 + sz;
}

%typemap(freearg) (const signed char *begin, const signed char *end) {
  // Or use  0 instead of ABORT to keep changes if it was a copy
  JCALL3(ReleaseByteArrayElements, jenv, $input, $1, JNI_ABORT);
}

%typemap(jtype) (const signed char *begin, const signed char *end) "byte[]"
%typemap(jstype) (const signed char *begin, const signed char *end) "byte[]"
%typemap(jni) (const signed char *begin, const signed char *end) "jbyteArray"
%typemap(javain) (const signed char *begin, const signed char *end) "$javainput"
Run Code Online (Sandbox Code Playgroud)

唯一的区别是使用局部变量szend使用begin指针计算arugment .

剩下要做的就是告诉SWIG使用我们刚刚编写的类型图包装头文件本身:

%include "test.hh"
Run Code Online (Sandbox Code Playgroud)

我测试了这两个函数:

public class run {
  public static void main(String[] argv) {
    byte[] arr = {0,1,2,3,4,5,6,7};
    System.out.println("Foo:");
    test.foo(arr);
    System.out.println("Bar:");
    test.bar(arr);
  }
}
Run Code Online (Sandbox Code Playgroud)

哪个按预期工作.

为方便起见,我在我的网站上分享了我用来写这个文件.通过按顺序执行此回答,可以重建该存档中每个文件的每一行.


作为参考,我们可以在没有任何JNI调用的情况下完成整个事情,使用%pragma(java) modulecode生成一个重载,我们使用将输入(纯Java)转换为实际函数所期望的形式.为此,模块文件将是:

%module test

%{
#include "test.hh"
%}

%include <carrays.i>
%array_class(signed char, ByteArray);

%pragma(java) modulecode = %{
  // Overload foo to take an array and do a copy for us:
  public static void foo(byte[] array) {
    ByteArray temp = new ByteArray(array.length);
    for (int i = 0; i < array.length; ++i) {
      temp.setitem(i, array[i]);
    }
    foo(temp.cast(), array.length);
    // if foo can modify the input array we'll need to copy back to:
    for (int i = 0; i < array.length; ++i) {
      array[i] = temp.getitem(i);
    }
  }

  // How do we even get a SWIGTYPE_p_signed_char for end for bar?
  public static void bar(byte[] array) {
    ByteArray temp = new ByteArray(array.length);
    for (int i = 0; i < array.length; ++i) {
      temp.setitem(i, array[i]);
    }
    bar(temp.cast(), make_end_ptr(temp.cast(), array.length));
    // if bar can modify the input array we'll need to copy back to:
    for (int i = 0; i < array.length; ++i) {
      array[i] = temp.getitem(i);
    }
  }
%}

// Private helper to make the 'end' pointer that bar expects
%javamethodmodifiers make_end_ptr "private";
%inline {
  signed char *make_end_ptr(signed char *begin, int sz) {
    return begin+sz;
  }
}

%include "test.hh"

%pragma(java) jniclasscode=%{
  static {
    try {
        System.loadLibrary("test");
    } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load. \n" + e);
      System.exit(1);
    }
  }
%}
Run Code Online (Sandbox Code Playgroud)

除了需要明显的(二)复制到数据进入正确的类型(有自走非同小可的方式byte[]SWIGTYPE_p_signed_char),后这有一个缺点-它是具体的职能foobar,而我们前面写的typemaps不是专门针对一个给定的函数 - 如果碰巧有一个带两个范围或两个指针+长度组合的函数,它们将被应用于它们匹配的任何地方,甚至可以在同一函数上多次应用.这样做的一个好处是,如果你碰巧有其他包装的功能让你SWIGTYPE_p_signed_char回来,那么你仍然可以根据需要使用重载.即使在你有一个ByteArray来自%array_class你的情况下仍然无法在Java中end为你生成指针算法.

所显示的原始方式在Java中提供了更清晰的界面,具有不进行过多复制和更可重用的附加优点.


另一种可供选择的方法包装是写几个%inline重载foobar:

%inline {
  void foo(jbyteArray arr) {
    // take arr and call JNI to convert for foo
  }
  void bar(jbyteArray arr) {
    // ditto for bar
  }
}
Run Code Online (Sandbox Code Playgroud)

这些在Java接口中表示为重载,但它们仍然是特定于模块的,此外所需的JNI比它本来需要的更复杂 - 你需要安排以jenv某种方式获取,这是不可访问的默认.选项是缓慢调用它,或者是一个numinputs=0自动填充参数的类型映射.无论哪种方式,多参数类型映射似乎更好.