如何在SWIG生成的Java绑定中转换为SWIGTYPE_p_void类型?
我正在为C库开发一些SWIG生成的Java绑定。 该库包含void *
类型参数的函数。 在C端,这些通常作为指向float
类型的数组的指针传递或int
cast to void *
。 在生成的Java绑定中,这会产生采用SWIGTYPE_p_void
类型参数的方法。
在Java绑定中构造浮点数/整数数组的最佳方法是什么,以便它们可以作为SWIGTYPE_p_void
类型SWIGTYPE_p_void
给这些方法?
目前我正在我的example.i文件中定义一个辅助函数:
void *floata_to_voidp(float f[]) { return (void *)f; }
然后在Java端做这样的事情:
float foo[] = new float[2]; SWIGTYPE_p_void av = null; // do something with foo av = example.floata_to_voidp(foo); example.myfunction(av);
这看起来相当丑陋,特别是因为我还需要在我的SWIG接口文件中为每个我希望支持的类型转换使用inta_to_voidp()
等。
有没有办法在没有辅助函数的情况下执行此操作,并且在Java端涉及更少的额外代码来转换数据类型?
更新(17/6/12):给出问题的更多细节:我要做的是采用一组C函数,原型为int foo(const float *data, int N, const void *argv, float *result)
并将它们映射到Java端的方法,其中任意类型的数组可以作为argv
传入。 请注意, argv
是const void *
而不是void *
。
有一个替代这个答案 ,它是非常不同的,并提供了一个更自然的解决方案,更接近你原来寻找的问题。 其他建议主要集中在添加重载(繁琐,手动)或使array_class
es以这种或那种方式实现公共接口。
它忽略的是Object
大多数时候都是Java中void*
的良好匹配。 甚至Java中的数组都是Object
。 这意味着如果你有SWIG map void*
to Object
它将接受你可能要传入的任何数组作为输入。稍微小心点和一些JNI然后我们可以得到一个指向该数组的开头的指针以传入function。 显然,我们需要拒绝非数组Object
,但有一个例外。
我们最终仍然编写一些(私有)辅助函数来安排提取真正的底层指针并在完成后释放它,但是这个解决方案的好处是我们只需要执行一次然后我们最终得到一个类型映射可以用于任何将数组作为void*
函数。
我最终为此解决方案提供了以下SWIG接口:
%module test %{ #include void foo(void *in) { printf("%p, %d, %g\n", in, *(jint*)in, *(jdouble*)in); } %} %typemap(in,numinputs=0) JNIEnv *env "$1 = jenv;" %javamethodmodifiers arr2voidd "private"; %javamethodmodifiers arr2voidi "private"; %javamethodmodifiers freearrd "private"; %javamethodmodifiers freearri "private"; %inline %{ jlong arr2voidd(JNIEnv *env, jdoubleArray arr) { void *ptr = (*env)->GetDoubleArrayElements(env, arr, NULL); return (intptr_t)ptr; } void freearrd(JNIEnv *env, jdoubleArray arr, jlong map) { void *ptr = 0; ptr = *(void **)↦ (*env)->ReleaseDoubleArrayElements(env, arr, ptr, JNI_ABORT); } jlong arr2voidi(JNIEnv *env, jintArray arr) { void *ptr = (*env)->GetIntArrayElements(env, arr, NULL); return (intptr_t)ptr; } void freearri(JNIEnv *env, jintArray arr, jlong map) { void *ptr = 0; ptr = *(void **)↦ (*env)->ReleaseIntArrayElements(env, arr, ptr, JNI_ABORT); } %} %pragma(java) modulecode=%{ private static long arrPtr(Object o) { if (o instanceof double[]) { return arr2voidd((double[])o); } else if (o instanceof int[]) { return arr2voidi((int[])o); } throw new IllegalArgumentException(); } private static void freeArrPtr(Object o, long addr) { if (o instanceof double[]) { freearrd((double[])o, addr); return; } else if (o instanceof int[]) { freearri((int[])o, addr); return; } throw new IllegalArgumentException(); } %} %typemap(jstype) void *arr "Object" %typemap(javain,pre=" long tmp$javainput = arrPtr($javainput);",post=" freeArrPtr($javainput, tmp$javainput);") void *arr "tmp$javainput" void foo(void *arr);
这实现了两个数组类型,有一个小的有限数,你也可以使用片段或宏来帮助解决这个问题。 SWIG内部使用jlong
来表示指针。 因此,对于每个数组类型,我们需要一个函数返回给定数组的指针,另一个函数释放它。 这些是私有的并且是模块类的一部分 – 除模块之外的任何人都不需要知道它是如何工作的。
然后有两个函数接受Object
并使用instanceof
(丑陋,但Java中的数组没有任何其他公共基础或接口,并且generics没有帮助)并调用正确的函数来获取/释放指针。
有了这些,那么只需要设置SWIG以将其用于所有void *arr
参数的两个类型图。 在这些情况下,jstype typemap指示SWIG使用Object
for void*
。 javain类型映射安排一个临时局部变量来保存指针( long
),然后用于调用它并在调用成功或失败后进行清理。
最简单的解决方案是使用SWIG的
来创建一个包含 float
数组和int
数组以及您关心的任何其他类型的类型。 这些可以使用cast()
成员函数SWIGTYPE_p_float
转换为SWIGTYPE_p_float
等。 问题是,这不能自动从Java中转换为SWIGTYPE_p_void
。 理论上你可以打电话:
new SWIGTYPE_p_void(FloatArray.getCPtr(myfloatarr));
但由于各种原因(尤其是它很麻烦),这并不理想。 (它还存在内存所有权和垃圾回收问题)。
所以我定义了一个接口:
public interface VoidPtr { public long asVoidPtr(); }
我们可以将您的库的包装版本作为输入,并为我们实现数组类FloatArray
, IntArray
等。
这最终得到了模块文件:
%module test %include %typemap(javainterfaces) FloatArray "VoidPtr" %typemap(javainterfaces) IntArray "VoidPtr" %typemap(javacode) FloatArray %{ public long asVoidPtr() { return getCPtr(this); } %} %typemap(javacode) IntArray %{ public long asVoidPtr() { return getCPtr(this); } %} %array_class(float, FloatArray); %array_class(int, IntArray); %typemap(jstype) void *arr "VoidPtr" %typemap(javain) void *arr "$javainput.asVoidPtr()" void foo(void *arr);
修改void *arr
将被视为我们的VoidPtr
类型并自动调用asVoidPtr()
方法。 您可以使用类型映射复制或宏来减少重复性。 (注意,可能需要在此处解决过早垃圾收集的问题,具体取决于您计划如何使用此方法)
这允许我们编写如下代码:
public class run { public static void main(String[] argv) { FloatArray arr = new FloatArray(100); test.foo(arr); } }
我认为这是最简单,最干净的解决方案。 还有其他几种方法可以解决这个问题:
-
也可以编写一些代码,这些代码将采用实际的Java数组而不仅仅是SWIG
array_class
并通过调用JNI函数来获取底层指针来实现此接口。 你必须为每种原始类型编写一个版本,就像上面那样。接口文件可能看起来像:
%module test %{ void foo(void *arr); %} %typemap(in,numinputs=0) JNIEnv *env "$1 = jenv;" %rename(foo) fooFloat; %rename(foo) fooInt; %inline %{ void fooFloat(JNIEnv *env, jfloatArray arr) { jboolean isCopy; foo((*env)->GetFloatArrayElements(env, arr, &isCopy)); // Release after call with desired semantics } void fooInt(JNIEnv *env, jintArray arr) { jboolean isCopy; foo((*env)->GetIntArrayElements(env, arr, &isCopy)); // Release after call } %} void foo(void *arr);
然后,它会给你
foo
重载,它SWIGTYPE_p_void
float[]
和int[]
以及SWIGTYPE_p_void
。 -
你可以使用联合技巧:
%inline %{ union Bodge { void *v; float *f; int *i; }; %}
虽然这被认为是不好的forms,但它确实会生成一个Java接口,可用于将
SWIGTYPE_p_int
转换为SWIGTYPE_p_void
。 -
我认为可以使
FloatArray
inheritance自SWIGTYPE_p_void
,类似于以下编译但未经测试的代码:%module test %include
%typemap(javabase) FloatArray "SWIGTYPE_p_void" %typemap(javabody) FloatArray %{ private long swigCPtr; // Minor bodge to work around private variable in parent private boolean swigCMemOwn; public $javaclassname(long cPtr, boolean cMemoryOwn) { super(cPtr, cMemoryOwn); this.swigCPtr = SWIGTYPE_p_void.getCPtr(this); swigCMemOwn = cMemoryOwn; } %} %array_class(float, FloatArray); void foo(void *arr); 这复制了Java端的指针,但是在void指针或数组类中没有任何改变(当前),因此它不像它最初看起来那么大。 (您也可以使用我认为的替代类型映射使其在基类中受到保护,或者使用通过
getCPtr
函数获取swigCPtr
的getCPtr
的修改版本)