给出如下标题:
#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端,只使用数组(已知)大小为这些函数提供第二个参数?
答案 0 :(得分:12)
关键在于,要包装这些功能中的任何一个,您需要使用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 typemap的工作是将我们从JNI调用给出的内容转换为实际函数真正期望的输入。我用numinputs=1
表示两个实函数参数只在Java端接受一个输入,但这仍然是默认值,因此不需要明确说明。
在这个类型映射中,$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数组对象。另外,它需要一个参数来指示是否应该复制内容 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
来使用end
指针计算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);
}
}
按预期工作。
为方便起见,我在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
没有任何琐碎的方法)并且这有另一个缺点 - 它特定于函数foo
和bar
,而我们之前编写的类型图并不特定于给定的函数 - 它们将应用于匹配的任何位置,如果碰巧有函数,甚至可以在同一函数上多次应用需要两个范围或两个指针+长度组合。这样做的一个好处是,如果你碰巧有其他包装函数给你SWIGTYPE_p_signed_char
,那么你仍然可以根据需要使用重载。即使在ByteArray
中有%array_class
的情况下,您仍然无法使用Java生成end
所需的指针算法。
显示的原始方式在Java中提供了一个更清晰的界面,具有不会制作过多副本和更可重用的附加优势。
另一种包装方法是为%inline
和foo
写一些bar
重载:
%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
类型图自动填充参数。无论哪种方式,多参数类型映射似乎都更好。