通过 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 是完全可行的,但正如所写的那样,这绝对不是包装代码的正确方法。所写的问题有两个:

  1. 您已经 return 按值编辑了一个向量,但这意味着一旦它超出范围,它的内存就会过期,留下 ByteBuffer 指的是不再存在的内存有效
  2. 如果您确实延长了 vector 的生命周期来避免这个问题,那么目前没有任何东西可以在 ByteBuffer 收集垃圾时释放内存,所以您会泄漏内存
  3. arducam_capture 的调用本身需要以某种方式与对 arducam_release_buffer 的调用配对 - 您可以将其添加到 ArduGrabClass::grab() 函数中,但请参阅下文
  4. 即使你做了所有这些,你仍然会做很多数据副本,这在某种程度上违背了在 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
......