通过 SWIG 访问 Java 中的 uint8_t 矢量
Access uint8_t vector in Java via SWIG
我正在使用 SWIG 开发 Java <-> C++ 桥;我的 C++ 方法如下:
std::vector<uint8_t> ArduGrabClass::grab() {
IMAGE_FORMAT fmt = { IMAGE_ENCODING_I420, 100 };
BUFFER *buffer = arducam_capture(camera_instance, &fmt, 352000);
return vector<uint8_t>(buffer->data, buffer->data + buffer->length);
}
也就是cut掉,真正的方法在c++class中持有buffer对象,这样java端就可以释放了。但是我需要在 Java 端访问此数据,最好是在 ByteBuffer 中,否则在数组中。但是我不知道该怎么做; JNI 方法 NewDirectByteBufer 似乎合适,但 SWIG 正在处理所有 JNI 的东西,所以我不知道如何告诉 SWIG 使用 NewDirectByteBufer 方法。
编写一个类型映射来调用 NewDirectByteBuffer
是完全可行的,但正如所写的那样,这绝对不是包装代码的正确方法。所写的问题有两个:
- 您已经 return 按值编辑了一个向量,但这意味着一旦它超出范围,它的内存就会过期,留下
ByteBuffer
指的是不再存在的内存有效
- 如果您确实延长了 vector 的生命周期来避免这个问题,那么目前没有任何东西可以在
ByteBuffer
收集垃圾时释放内存,所以您会泄漏内存
- 对
arducam_capture
的调用本身需要以某种方式与对 arducam_release_buffer
的调用配对 - 您可以将其添加到 ArduGrabClass::grab()
函数中,但请参阅下文
- 即使你做了所有这些,你仍然会做很多数据副本,这在某种程度上违背了在 Java 中使用 ByteBuffer 的意义。 (向量的构造函数中至少会有一个副本,可能更多取决于你如何解决它。
因此,如果您想使用 std::vector
副本,我建议您只使用现有的 SWIG 支持 std::vector
,或者将其复制到一些 JNI 代码中的新数组中。如果您有兴趣,我可以演示如何做到这一点,但现在我主要关注如何使用 arducam_capture
.
正确使用 ByteBuffer
的问题
要有一个 Java 方法 return 是 ByteBuffer
并在之后进行清理,我们想安排使用 Cleaner 来发现 [=14] =]超出范围。
如果我们将包装函数更改为 return BUFFER *
而不是 std::vector
中的副本,我们可以在生成的转换的内部使用一些 SWIG 支持调用我们添加到我们的包装器中的额外函数,该函数为我们调用 NewDirectByteBuffer
。然后,在该代码中,我们还可以使用 Runnable
注册 ByteBuffer
,这将为我们调用 arducam_release_buffer
。
为我们执行此操作的 SWIG 接口示例是:
%module test
%pragma(java) modulecode=%{
// Create a single cleaner thread for all our buffers to register with
// note package level access is deliberate
static final java.lang.ref.Cleaner cc = java.lang.ref.Cleaner.create();
static {
// actually load our shared object!
System.loadLibrary("test");
}
%}
// later on when we implement toBuffer() we need the environment pointer.
// This adds it into our method call automatically
%typemap(in,numinputs=0) JNIEnv * %{
= jenv;
%}
// Our native implementation of toBuffer on BUFFER is going to return
// a ByteBuffer straight up for us, with no need for conversions
%typemap(jtype) jobject toBuffer "java.nio.ByteBuffer"
%typemap(jstype) jobject toBuffer "java.nio.ByteBuffer"
// When we hand the ByteBuffer off to a caller we need to register
// something to do the clean up for us. This is where we register it.
%typemap(javaout) jobject toBuffer {
java.nio.ByteBuffer buf = $jnicall; // actually call the native code
System.out.println("In toBuffer"); // To prove it worked!
// Our cleaner instance lives in the module itself
$module.cc.register(buf, new Runnable(){
public void run() {
System.out.println("in buffer cleanup java side");
// We add a (private) cleanup function we can just call here
BUFFER.this.cleanup();
}
});
// Now it's registered actually let them have it
return buf;
}
// Every time we return a BUFFER * call toBuffer() on it instead
%typemap(javaout) BUFFER * {
return new $javaclassname($jnicall, $owner).toBuffer();
}
// Since we're going to call toBuffer() the return type is different
%typemap(jstype) BUFFER * "java.nio.ByteBuffer"
// Hide lots of thing about the BUFFER class outside our package
%typemap(javaclassmodifiers) BUFFER "class"
%javamethodmodifiers BUFFER::cleanup() "private";
%javamethodmodifiers BUFFER::toBuffer "";
// BEGIN Faked definitions, just for testing...
%{
typedef struct {
void *data;
int len;
} BUFFER;
typedef struct {
enum { IMAGE_ENCODING_I420 } a;
int n;
} IMAGE_FORMAT;
BUFFER * arducam_capture(void * instance, IMAGE_FORMAT *fmt, int timeout) {
(void)instance; (void)fmt; (void)timeout;
BUFFER *buf = malloc(sizeof *buf);
*buf = (BUFFER){
.data = malloc(100),
.len = 100,
};
return buf;
}
void arducam_release_buffer(BUFFER *instance) {
free(instance->data);
free(instance);
}
%}
// END TESTING
// All the details of BUFFER are not public for Java users
%nodefaultctor BUFFER;
%nodefaultdtor BUFFER;
typedef struct {} BUFFER;
// In addition to what's really in the buffer object we want to add another
// two methods.
%extend BUFFER {
// toBuffer() is used by our internals when returning a BUFFER
jobject toBuffer(JNIEnv *jenv) const {
// Swig provides JCALLx macros for us, but they are not usable inside %extend :(
%#ifdef __cplusplus__
return jenv->NewDirectByteBuffer($self->data, $self->len);
%#else
return (*jenv)->NewDirectByteBuffer(jenv, $self->data, $self->len);
%#endif
}
// Cleanup is used when the buffer is dead
void cleanup() {
arducam_release_buffer($self);
}
}
%inline %{
// Now, wrap a modified version of your code
BUFFER *do_capture() {
void *camera_instance = NULL;
IMAGE_FORMAT fmt = { IMAGE_ENCODING_I420, 100 };
BUFFER *buffer = arducam_capture(camera_instance, &fmt, 352000);
return buffer;
}
%}
然后尝试一下:
public class run {
public static void main(String[] argv) {
for (int i = 0; i < 100; ++i) {
java.nio.ByteBuffer buf = test.do_capture();
System.gc();
}
}
}
然后我们可以构建 运行:
swig3.0 -java -Wall test.i
gcc -Wall -Wextra -o libtest.so -I/usr/lib/jvm/default-java/include/ -I/usr/lib/jvm/default-java/include/linux test_wrap.c -shared
javac *.java -Xlint:deprecation
LD_LIBRARY_PATH=. java run
In toBuffer
in buffer cleanup java side
......
我正在使用 SWIG 开发 Java <-> C++ 桥;我的 C++ 方法如下:
std::vector<uint8_t> ArduGrabClass::grab() {
IMAGE_FORMAT fmt = { IMAGE_ENCODING_I420, 100 };
BUFFER *buffer = arducam_capture(camera_instance, &fmt, 352000);
return vector<uint8_t>(buffer->data, buffer->data + buffer->length);
}
也就是cut掉,真正的方法在c++class中持有buffer对象,这样java端就可以释放了。但是我需要在 Java 端访问此数据,最好是在 ByteBuffer 中,否则在数组中。但是我不知道该怎么做; JNI 方法 NewDirectByteBufer 似乎合适,但 SWIG 正在处理所有 JNI 的东西,所以我不知道如何告诉 SWIG 使用 NewDirectByteBufer 方法。
编写一个类型映射来调用 NewDirectByteBuffer
是完全可行的,但正如所写的那样,这绝对不是包装代码的正确方法。所写的问题有两个:
- 您已经 return 按值编辑了一个向量,但这意味着一旦它超出范围,它的内存就会过期,留下
ByteBuffer
指的是不再存在的内存有效 - 如果您确实延长了 vector 的生命周期来避免这个问题,那么目前没有任何东西可以在
ByteBuffer
收集垃圾时释放内存,所以您会泄漏内存 - 对
arducam_capture
的调用本身需要以某种方式与对arducam_release_buffer
的调用配对 - 您可以将其添加到ArduGrabClass::grab()
函数中,但请参阅下文 - 即使你做了所有这些,你仍然会做很多数据副本,这在某种程度上违背了在 Java 中使用 ByteBuffer 的意义。 (向量的构造函数中至少会有一个副本,可能更多取决于你如何解决它。
因此,如果您想使用 std::vector
副本,我建议您只使用现有的 SWIG 支持 std::vector
,或者将其复制到一些 JNI 代码中的新数组中。如果您有兴趣,我可以演示如何做到这一点,但现在我主要关注如何使用 arducam_capture
.
ByteBuffer
的问题
要有一个 Java 方法 return 是 ByteBuffer
并在之后进行清理,我们想安排使用 Cleaner 来发现 [=14] =]超出范围。
如果我们将包装函数更改为 return BUFFER *
而不是 std::vector
中的副本,我们可以在生成的转换的内部使用一些 SWIG 支持调用我们添加到我们的包装器中的额外函数,该函数为我们调用 NewDirectByteBuffer
。然后,在该代码中,我们还可以使用 Runnable
注册 ByteBuffer
,这将为我们调用 arducam_release_buffer
。
为我们执行此操作的 SWIG 接口示例是:
%module test
%pragma(java) modulecode=%{
// Create a single cleaner thread for all our buffers to register with
// note package level access is deliberate
static final java.lang.ref.Cleaner cc = java.lang.ref.Cleaner.create();
static {
// actually load our shared object!
System.loadLibrary("test");
}
%}
// later on when we implement toBuffer() we need the environment pointer.
// This adds it into our method call automatically
%typemap(in,numinputs=0) JNIEnv * %{
= jenv;
%}
// Our native implementation of toBuffer on BUFFER is going to return
// a ByteBuffer straight up for us, with no need for conversions
%typemap(jtype) jobject toBuffer "java.nio.ByteBuffer"
%typemap(jstype) jobject toBuffer "java.nio.ByteBuffer"
// When we hand the ByteBuffer off to a caller we need to register
// something to do the clean up for us. This is where we register it.
%typemap(javaout) jobject toBuffer {
java.nio.ByteBuffer buf = $jnicall; // actually call the native code
System.out.println("In toBuffer"); // To prove it worked!
// Our cleaner instance lives in the module itself
$module.cc.register(buf, new Runnable(){
public void run() {
System.out.println("in buffer cleanup java side");
// We add a (private) cleanup function we can just call here
BUFFER.this.cleanup();
}
});
// Now it's registered actually let them have it
return buf;
}
// Every time we return a BUFFER * call toBuffer() on it instead
%typemap(javaout) BUFFER * {
return new $javaclassname($jnicall, $owner).toBuffer();
}
// Since we're going to call toBuffer() the return type is different
%typemap(jstype) BUFFER * "java.nio.ByteBuffer"
// Hide lots of thing about the BUFFER class outside our package
%typemap(javaclassmodifiers) BUFFER "class"
%javamethodmodifiers BUFFER::cleanup() "private";
%javamethodmodifiers BUFFER::toBuffer "";
// BEGIN Faked definitions, just for testing...
%{
typedef struct {
void *data;
int len;
} BUFFER;
typedef struct {
enum { IMAGE_ENCODING_I420 } a;
int n;
} IMAGE_FORMAT;
BUFFER * arducam_capture(void * instance, IMAGE_FORMAT *fmt, int timeout) {
(void)instance; (void)fmt; (void)timeout;
BUFFER *buf = malloc(sizeof *buf);
*buf = (BUFFER){
.data = malloc(100),
.len = 100,
};
return buf;
}
void arducam_release_buffer(BUFFER *instance) {
free(instance->data);
free(instance);
}
%}
// END TESTING
// All the details of BUFFER are not public for Java users
%nodefaultctor BUFFER;
%nodefaultdtor BUFFER;
typedef struct {} BUFFER;
// In addition to what's really in the buffer object we want to add another
// two methods.
%extend BUFFER {
// toBuffer() is used by our internals when returning a BUFFER
jobject toBuffer(JNIEnv *jenv) const {
// Swig provides JCALLx macros for us, but they are not usable inside %extend :(
%#ifdef __cplusplus__
return jenv->NewDirectByteBuffer($self->data, $self->len);
%#else
return (*jenv)->NewDirectByteBuffer(jenv, $self->data, $self->len);
%#endif
}
// Cleanup is used when the buffer is dead
void cleanup() {
arducam_release_buffer($self);
}
}
%inline %{
// Now, wrap a modified version of your code
BUFFER *do_capture() {
void *camera_instance = NULL;
IMAGE_FORMAT fmt = { IMAGE_ENCODING_I420, 100 };
BUFFER *buffer = arducam_capture(camera_instance, &fmt, 352000);
return buffer;
}
%}
然后尝试一下:
public class run {
public static void main(String[] argv) {
for (int i = 0; i < 100; ++i) {
java.nio.ByteBuffer buf = test.do_capture();
System.gc();
}
}
}
然后我们可以构建 运行:
swig3.0 -java -Wall test.i
gcc -Wall -Wextra -o libtest.so -I/usr/lib/jvm/default-java/include/ -I/usr/lib/jvm/default-java/include/linux test_wrap.c -shared
javac *.java -Xlint:deprecation
LD_LIBRARY_PATH=. java run
In toBuffer
in buffer cleanup java side
......