【问题标题】:Pass an array to a wrapped function as pointer+size or range将数组作为指针+大小或范围传递给包装函数
【发布时间】:2012-12-04 14:50:06
【问题描述】:

给定一个像这样的标题:

#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 端获取一个数组并使用数组的(已知)大小为这些函数提供第二个参数?

【问题讨论】:

  • 在 Java 中手动提供一个包装器方法怎么样?这不像在 Java 中采用数组的方法也不带有 int offset, int length 参数......
  • @SamuelAudet - 你可以这样做,但我认为这不是一个设计良好的界面(复制信息只是为了好玩)。问题是,如果你有byte[],你将需要编写一个类型映射(大部分时间)将其转换为signed char *,或者使用%array_classfor 循环进行复制反正。这两个都很丑。
  • @SamuelAudet - 我用手动包装方法更新了我的答案。在我看来这很丑陋。
  • 嗯,我总是发现 SWIG 是个倒退,就像试图从汇编语言中使用 C++ 一样。是的,我忘记了,默认情况下它甚至没有将char* 映射到byte[],并且需要Java 和C++ 之上的另一种语言来指定它......现在你提到它,它似乎是它的@987654330 @thingy 与我在 JavaCPP 中添加的 @Adapter 注释非常相似。

标签: java c++ c swig


【解决方案1】:

关键是要包装这些函数中的任何一个,您都需要使用multi-argument typemap

序言对于 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);
    }
  }
%}

首先,尽管您需要使用一些 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 类型映射的工作是将 JNI 调用给定的内容转换为真正的函数真正期望作为输入的内容。我用numinputs=1 表示两个真正的函数参数在Java 端只接受一个输入,但无论如何这是默认值,因此不需要明确说明。

在这个类型映射中,$1 是类型映射的第一个参数,即在这种情况下我们函数的第一个参数。我们通过请求指向 Java 数组的底层存储的指针来设置它(这可能是也可能不是真正的副本)。我们将第二个 typemap 参数 $2 设置为数组的大小。

此处的JCALLn 宏确保类型映射可以使用 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); 
}

这会调用ReleaseByteArrayElements 来告诉JVM 我们已经完成了数组。它需要指针我们从中获取它的Java数组对象。此外,它还有一个参数,指示是否应该将内容复制回来iff它们被修改并且我们得到的指针首先是一个副本。 (我们传递 NULL 的参数是一个指向 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"

唯一的区别是使用局部变量sz 来使用begin 指针计算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 调用的情况下完成整个工作,使用 %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);
    }
  }
%}

除了将数据转换为正确类型所需的明显(两个)副本(从byte[]SWIGTYPE_p_signed_char 没有简单的方法)之外,这还有另一个缺点 - 它特定于函数foobar,而我们之前编写的类型映射并不特定于给定的函数——它们将应用于匹配的任何地方,如果你碰巧有一个函数需要两个范围或两个指针+,甚至在同一个函数上多次应用长度组合。这样做的一个好处是,如果你碰巧有其他包装函数给你SWIGTYPE_p_signed_char,那么如果你愿意,你仍然可以使用重载。即使在您有来自%array_classByteArray 的情况下,您仍然无法在Java 中执行为您生成end 所需的指针运算。

所示的原始方式在 Java 中提供了更简洁的界面,并具有不会制作过多副本和更可重用的附加优势。


另一种包装方法是为foobar 编写一些%inline 重载:

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

这些在 Java 接口中显示为重载,但它们仍然是特定于模块的,此外,这里所需的 JNI 比其他情况下需要的更复杂 - 你需要以某种方式获取jenv,默认情况下无法访问。选项是获取它的缓慢调用,或自动填充参数的numinputs=0 类型映射。无论哪种方式,多参数类型图看起来都好得多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-23
    • 2012-01-24
    • 2017-04-18
    • 1970-01-01
    • 2013-02-15
    • 1970-01-01
    相关资源
    最近更新 更多