重点是,要包装这两个函数之一,您需要使用多参数类型映射。
引言对于SWIG来说非常标准。我使用了我个人最喜欢的编译指示,自动加载共享库而无需接口使用者知道:
%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);
}
在typemap中的工作是将JNI调用所提供的内容转换为实际函数期望的输入。我使用numinputs=1
来表示这两个真实函数参数在Java端只需要一个输入,但这是默认值,因此不需要明确说明。
在这个typemap中,$1
是typemap的第一个参数,即在本例中我们函数的第一个参数。我们通过请求指向Java数组底层存储的指针(可能是复制品)来设置它。我们将第二个typemap参数$2
设置为数组的大小。
这里的JCALLn
宏确保typemap可以与C和C++ JNI一起编译。它会展开适合该语言的调用。
我们需要另一个typemap来在真实函数调用返回后进行清理:
%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) {
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使用我们刚刚编写的typemaps来包装头文件本身。
%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中)转换为真实函数所需的形式。对于这个功能,模块文件将是:
%module test
%{
#include "test.hh"
%}
%include <carrays.i>
%array_class(signed char, ByteArray);
%pragma(java) modulecode = %{
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);
for (int i = 0; i < array.length; ++i) {
array[i] = temp.getitem(i);
}
}
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));
for (int i = 0; i < array.length; ++i) {
array[i] = temp.getitem(i);
}
}
%}
%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,而我们之前编写的typemaps不特定于给定的函数-它们将应用于任何匹配的地方,甚至在相同的函数上多次匹配,如果您碰巧有一个接受两个范围或两个指针+长度组合的函数。以这种方式操作的一个优点是,即使您已经包装了返回SWIGTYPE_p_signed_char的其他封装函数,如果需要,则仍然可以使用重载。即使您有来自%array_class的ByteArray,您仍然无法在Java中执行所需的指针算术以为您生成结束。原始的方法提供了更清晰的Java界面,具有不制作过多副本和更高的可重用性的附加优势。
另一种包装的替代方法是为foo和bar编写一些%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
类型映射自动填充参数。无论哪种方式,多参数类型映射似乎更好。
byte[]
,你将需要编写一个类型映射(大多数情况下)将其转换为signed char *
,或者使用%array_class
和一个for
循环来进行复制。这两种方法都相当丑陋。 - Flexochar*
映射到byte[]
,并且需要在Java和C++之上再使用另一种语言来指定它...不过现在你提到它,它的%typemap
东西似乎与我在JavaCPP中放置的@Adapter
注释非常相似。 - Samuel Audet