关键是要包装这些功能中的任何一个,您都需要使用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指示痛饮使用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);
}
的类型映射中的任务是从什么我们通过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数组对象。此外,它还有一个参数,用于指示是否应该将内容复制回来,如果它们被修改,并且我们得到的指针是第一个副本。 (我们传递NULL的参数是一个指向jboolean
的可选指针,它指示我们是否已经获得副本)。
对于第二变型中,typemaps基本上相似:
%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
arugment。
唯一剩下要做的就是告诉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);
}
}
其中按预期工作。
为了方便起见,我已经在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
,那么如果你愿意,你仍然可以使用超载。即使在%array_class
中有ByteArray
的情况下,仍然无法为Java生成end
所需的Java指针算法。
显示的原始方式在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
typemap。无论哪种方式多参数类型映射看起来好得多。
如何在Java中手动提供包装器方法?这不是像在Java中使用数组的方法,也不是带有'int offset,int length'参数以及... – 2012-07-22 04:24:57
@SamuelAudet - 你可以做到这一点,但我认为这不是一个设计良好的界面(复制信息只是为了它的乐趣)。问题是,如果你有'byte []'',你需要编写一个typemap(大部分时间)来将它转换为'signed char *',或者使用'%array_class'和'for'无论如何循环做一个副本。这两个都很丑陋。 – Flexo 2012-07-22 09:21:40
@SamuelAudet - 我用手动包装方法更新了我的答案。我认为这非常难看。 – Flexo 2012-07-22 09:50:45