将数组作为指针+大小或范围传递给换行函数
Pass an array to a wrapped function as pointer+size or range
给定一个标题,如:
#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端的一个数组,并使用数组的(已知)大小为这些函数提供第二个参数?
关键在于,要包装这两个函数中的任何一个,都需要使用多参数类型映射。
序言是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端只接受一个输入,但这无论如何都是默认值,因此不需要显式声明。
在这个类型映射中,$1
是类型映射的第一个参数,即在这种情况下我们函数的第一个自变量。我们通过请求一个指向Java数组底层存储的指针来设置它(它可能真的是副本,也可能不是副本)。我们将第二个typemap参数$2
设置为数组的大小。
这里的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
来使用begin
指针来计算end
分段。
剩下要做的唯一一件事就是告诉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)转换为实际函数所期望的形式。为此,模块文件应该是:
%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中提供了一个更干净的界面,并增加了不进行过多复制和更可重用的优点。
包装的另一种替代方法是为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
类型映射。无论哪种方式,多参数类型映射看起来都要好得多。
- 通过函数指针定义类范围之外的方法
- 是否可以依赖函数范围的静态变量来执行程序关闭期间调用的方法?
- 重载运算符的范围是什么?它是否会影响作为类成员的集合的插入函数?
- 在函数范围内在堆栈上分配的数组在离开函数时是否总是被释放?
- 在函数内创建的对象的范围 - 如果在函数外部存储和访问引用,它们是否有效?
- 给定一个C++嵌套的私有结构类型,是否有从文件范围静态函数访问它的策略
- 程序中的布尔函数返回输入的范围无论如何都是无效的
- 有没有办法在另一个函数中加入线程?(即超出其自身范围)
- 从函数返回范围视图时,带有std::span:中间对象所有权的C++Ranges-v3
- 在可变参数函数中转发特定范围的参数
- std::线程不是全局变量,但在到达创建它的函数的末尾时不会超出范围?
- 使函数参数默认为周围范围
- 在 gcc/clang (C++) 中获取函数范围之外的标签地址
- C++ 在编译过程中 strtok 函数 Eclipse 说没有在范围内声明?
- 包含来自另一个文件的函数会导致范围错误(openFoam)
- 定义一个 void f(void) 函数,但使用来自同一范围的变量?
- 私有在函数定义/实现的返回值范围内是什么意思 (c++)?
- 函数范围的静态变量如何导致与共享库中函数代码的未来使用不兼容
- 成员变量在超出BeginPlay函数虚幻引擎的范围时丢失值c++
- 将自适应阈值应用于范围函数 opencv c++