2012-07-20 30 views
8

给定一首标,如:传递数组到包装的函数作为指针+大小或范围

#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")); 
} 

(I用c这里为方便起见++ 11,这可能是C或者C++,如果你改变了实现虽然)

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

+0

如何在Java中手动提供包装器方法?这不是像在Java中使用数组的方法,也不是带有'int offset,int length'参数以及... – 2012-07-22 04:24:57

+0

@SamuelAudet - 你可以做到这一点,但我认为这不是一个设计良好的界面(复制信息只是为了它的乐趣)。问题是,如果你有'byte []'',你需要编写一个typemap(大部分时间)来将它转换为'signed char *',或者使用'%array_class'和'for'无论如何循环做一个副本。这两个都很丑陋。 – Flexo 2012-07-22 09:21:40

+0

@SamuelAudet - 我用手动包装方法更新了我的答案。我认为这非常难看。 – Flexo 2012-07-22 09:50:45

回答

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指示痛饮使用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去)所需的显着性(二)副本和背面这有一个缺点 - 函数foobar的功能是特定的,而我们之前编写的类型映射不是特定于给定函数的 - 它们将被应用到任何匹配的位置,甚至在同一个函数上多次执行,如果碰巧有一个函数需要两个范围或两个指针+长度组合。这样做的一个优点是,如果你碰巧有其他包装功能给你SWIGTYPE_p_signed_char,那么如果你愿意,你仍然可以使用超载。即使在%array_class中有ByteArray的情况下,仍然无法为Java生成end所需的Java指针算法。

显示的原始方式在Java中提供了一个更干净的界面,并且具有不增加副本和更加可重用的优点。


另一种可供选择的方法包装是写几%inline重载foobar

%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。无论哪种方式多参数类型映射看起来好得多。