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

给出如下标题:

#include  #include  #include  inline void foo(const signed char *arr, size_t sz) { std::copy_n(arr, sz, std::ostream_iterator(std::cout, "\n")); } inline void bar(const signed char *begin, const signed char *end) { std::copy(begin, end, std::ostream_iterator(std::cout, "\n")); } 

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

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

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

序言是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类型映射来指示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数组的底层存储的指针来设置它(实际上可能是也可能不是副本)。 我们设置$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); } 

这会调用ReleaseByteArrayElements告诉JVM我们已经完成了数组。 它需要指针我们从中获取的Java数组对象。 另外,它接受一个参数,指示是否应该复制内容, 如果它们被修改,我们得到的指针首先是副本。 (我们传递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指针计算结束语。

剩下要做的唯一事情是告诉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); } } 

哪个按预期工作。

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


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

 %module test %{ #include "test.hh" %} %include  %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 typemap。 无论哪种方式,多参数类型映射似乎更好。