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

标签 java c++ c 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"));
}

(为了方便,我在这里使用C++ 11,但如果更改了实现,则可以是C或C++)
如何包装这些函数,使其仅在Java端使用一个数组,并使用数组的(已知)大小为这些函数提供第二个参数?

最佳答案

关键在于,要包装这些函数中的任何一个,您都需要使用multi-argument typemap
前言是瑞士政府的标准。我使用我个人喜爱的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);
    }
  }
%}

首先,尽管您需要使用一些Java typemaps来指示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"

完成后,我们可以编写多参数类型映射:
%typemap(in,numinputs=1) (const signed char *arr, size_t sz) {
  $1 = JCALL2(GetByteArrayElements, jenv, $input, NULL);
  $2 = JCALL1(GetArrayLength, jenv, $input);
}

in-typemap的工作是将JNI调用提供的内容转换为实际函数真正期望的输入内容。我使用numinputs=1来指示这两个实数函数参数在Java端只接受一个输入,但这仍然是默认值,因此不需要显式声明。
在这种情况下,typemap$1是typemap的第一个参数,即函数的第一个参数。我们通过请求一个指向Java数组底层存储的指针(它可能是也可能不是真正的副本)来设置这个值。我们将第二个typemap参数设置为数组的大小。
这里的$2宏确保类型映射可以用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); 
}

这将调用JCALLn来告诉JVM我们已经完成了对数组的处理。它需要指针和我们从中获取的Java数组对象。此外,它还接受一个参数,该参数指示在内容被修改并且我们得到的指针最初是一个副本时是否应该将其复制回。(我们传递的参数NULL是指向ReleaseByteArrayElements的可选指针,它指示我们是否已获得副本)。
对于第二种变体,类型映射基本相似:
%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"

唯一的区别是使用局部变量jboolean来使用sz指针计算end数组。
唯一要做的就是告诉SWIG使用我们刚刚编写的类型映射来包装头文件本身:
%include "test.hh"

我用以下方法测试了这两个功能:
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);
  }
}

果然奏效了。
为了方便起见,我在my site上共享了编写本文时使用的文件。该存档中每个文件的每一行都可以通过按此顺序进行重建。
作为参考,我们可以在没有任何JNI调用的情况下完成整个工作,使用begin生成一个重载,我们使用它将输入(在纯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);
    }
  }
%}

除了将数据转换为正确类型所需的明显(两个)副本(从%pragma(java) modulecodebyte[]没有任何简单的方法)之外,这还有另一个缺点——它特定于函数SWIGTYPE_p_signed_charfoo,而我们之前编写的类型映射并不特定于给定的函数——它们将应用于任何匹配的地方,如果碰巧有一个函数采用两个范围或两个指针+长度组合,则对同一个函数执行多次操作。这样做的一个好处是,如果碰巧有其他包装好的函数返回给您bar,那么如果您愿意,您仍然可以使用重载。即使在SWIGTYPE_p_signed_char中有一个ByteArray的情况下,您仍然无法用Java完成生成%array_class所需的指针运算。
所示的原始方法在Java中提供了一个更干净的接口,其附加的优点是不生成过多的副本,并且更易于重用。
另一种包装方法是为end%inline编写一些foo重载:
%inline {
  void foo(jbyteArray arr) {
    // take arr and call JNI to convert for foo
  }
  void bar(jbyteArray arr) {
    // ditto for bar
  }
}

这些在Java接口中显示为重载,但它们仍然是特定于模块的,另外这里需要的JNI比它本来需要的要复杂-您需要安排以某种方式获得bar,这在默认情况下是不可访问的。这些选项是一个缓慢的获取它的调用,或者是一个自动填充参数的jenv类型映射。不管怎样,多参数类型映射看起来都要好得多。

关于java - 将数组作为指针+大小或范围传递给包装的函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13915333/

相关文章:

java - Spring 集成注释 Java 8 可选的不正确处理

c++ - 未初始化的变量被使用

java - FlowLayout添加在之前的组件下

Java面向对象编程: referencing subclass object

C++ const 指针 - 无法解释的行为

c - C 模块(文件)大小的最佳实践

c - C中的段错误,似乎无法找到原因

c++ - 在 C 中有效地解析 char 数组

java - 如何检测客户端何时与服务器断开连接

c++ - 像素叠加透明