gpt4 book ai didi

java - 将数组作为指针+大小或范围传递给包装函数

转载 作者:可可西里 更新时间:2023-11-01 17:37:13 26 4
gpt4 key购买 nike

给定一个标题,如:

#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 端仅获取一个数组并使用数组的(已知)大小为这些函数提供第二个参数?

最佳答案

关键是要包装这些函数中的任何一个,您都需要使用 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 接口(interface)的两个部分的类型 - JNI 和调用它的包装器。在生成模块文件中,我们将使用 JNI 类型 jbyteArray .我们将输入直接从 SWIG 接口(interface)传递给它生成的 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 ,第二个类型映射参数是数组的大小。
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 的可选指针,它表明我们是否得到了一个拷贝)。

对于第二个变体,类型图基本相似:
%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 上分享了我写这篇文章时使用的文件。 .该存档中每个文件的每一行都可以通过按顺序遵循这个答案来重建。

作为引用,我们可以使用 %pragma(java) modulecode 在没有任何 JNI 调用的情况下完成整个事情。生成我们使用的重载将输入(在纯 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那时,如果您愿意,您仍然可以使用重载。即使在您有 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 接口(interface)中显示为重载,但它们仍然是特定于模块的,此外,此处所需的 JNI 比其他情况下需要的更复杂 - 您需要安排获取 jenv不知何故,默认情况下无法访问。选项是获取它的缓慢调用,或者 numinputs=0自动填充参数的类型映射。无论哪种方式,多参数类型映射看起来都好得多。

关于java - 将数组作为指针+大小或范围传递给包装函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11584599/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com