2

I'm trying to pass an input and an output buffer to a java class from C++. For efficiency reasons, I need to use a ByteBuffer.

Both of the buffers were allocated in the C++ part and I need to pass them to a java function that will use the input buffer for some computation and write the result into the output buffer.

Here is an simplified example of how the C++ looks like:

// SWIG mapped with %feature("director") JavaDelegate;
class JavaDelegate {
public:
    JavaDelegate() {}
    virtual ~JavaDelegate() {}
    virtual int compute(const uint8_t* input, size_t inSize,
                        uint8_t* output, size_t maxOutSize,
                        int someFlag) = 0;
};

// assume inputBuf is filled with valid data
std::vector<uint8_t> inputBuf;
std::vector<uint8_t> outputBuf(10000);

// delegate also points to a valid SWIG mapped class
JavaDelegate* delegate;

void computeOutput() {
   int someFlag = 1;
   int numValuesComputed = delegate->compute(inputBuf.data(), inputBuf.size(), 
                                             outputBuf.data(), outputBuf.size(), 
                                             someFlag);
   std::cout << "Computed " << numValuesComputed << " value from input.\n";
}

On the Java side, I want to achieve this (JavaDelegate class has been generated by SWIG):

public class Delegate extends JavaDelegate {

    public long compute(java.nio.ByteBuffer input, 
                        java.nio.ByteBuffer output, 
                        long someFlag) 
    {
        // read input
        // compute something n values from m input values
        // write result to output
        return numOutputValues;
    }
}

The problem is, I simply can't figure out how to do the typemaps to wrap a plain char* to a ByteBuffer, especially in conjunction with SWIG's director feature turned on for the abstract baseclass. I know how to pass a ByteBuffer from Java to C++ but I can't find out how to do the reverse.

Any help appreciated!

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
pokey909
  • 1,797
  • 1
  • 16
  • 22
  • I would pass a DirectByteBuffer from Java to C++ for it to populate, you can avoid copying the data by altering the address and capacity. – Peter Lawrey Dec 23 '16 at 12:37
  • The output buffer is actually a hardware buffer, so I can't really allocate it from java. Of course I could copy it back to the hardware buffer afterwards, but I'd prefer to simply map it directly to java if possible. – pokey909 Dec 23 '16 at 12:43
  • http://stackoverflow.com/questions/15414753/shared-memory-between-c-and-java-processes – Joop Eggen Dec 23 '16 at 13:56
  • appreciate your help, but this is about creating a SWIG typemap for it. E.g. The way from Java to C++ has been done in https://github.com/swig/swig/blob/master/Lib/java/various.i But the reverse is missing. – pokey909 Dec 23 '16 at 14:07

2 Answers2

1

Preface: this is not SWIG-ish solution, this is general purpose, I found this somewhat related to your question


First, there are no ref parameters in Java as far as I know so passing it directly from C++ is not an option unless you are ready to change your signature and have it as a return value. This would be possible to circumvent by passing typewrapper from Java and having C++ creating/setting buffer inside it, a la:

public class BufferWrapper {
    private ByteBuffer mBuffer;

    public void setBuffer(ByteBuffer buffer) {
        mBuffer = buffer;
    }
} 

Just pass this to C++ and have it allocate and set buffer to wrapper.

There's NewDirectByteBuffer JNI function that does exactly what you need:

Allocates and returns a direct java.nio.ByteBuffer referring to the block of memory starting at the memory address address and extending capacity bytes.

Native code that calls this function and returns the resulting byte-buffer object to Java-level code should ensure that the buffer refers to a valid region of memory that is accessible for reading and, if appropriate, writing. An attempt to access an invalid memory location from Java code will either return an arbitrary value, have no visible effect, or cause an unspecified exception to be thrown.


P.S we could have gone other way - ByteBuffer has wrap method that would allow you to wrap existing buffer of byte[] which is not exactly char* but we could marry them (look at this answer).

In depth of your Jni method implmentation:

 jsize n = sizeof(yourBuf);
 jbyteArray arr = (*env)->NewByteArray(env, n);
 (*env)->SetByteArrayRegion(env,arr,0,n, (jbyte*)yourBuf);

However note that according to doc this function incurs copy not just wrapping so I doom it not very helpful here

Community
  • 1
  • 1
Oleg Bogdanov
  • 1,712
  • 13
  • 19
  • I think your answer is spot on. I found this https://svn.apache.org/repos/asf/qpid/branches/address-refactor2/qpid/cpp/bindings/swig_java_typemaps.i which does something pretty similar to what you describe. I'm fine with changing my signatures as well. Right now, I'm trying to pass a wrapper that keeps track of the pointer to the data. This seems to be the way to go here. I'l post the complete code once i get it working. Thanks Oleg! – pokey909 Dec 23 '16 at 21:25
  • I need to do the same thing - do you still have that code? – Neil Benn Jun 17 '21 at 21:25
0

One way to go about is to extend the C++ class to return a ByteBuffer. Roughly speaking, one could go about it like this:

%extend some::namespace::Buffer {
    jobject byteBuffer() {
        if (cached_jvm != 0) {
            JNIEnv *env = JNU_GetEnv();
            return env->NewDirectByteBuffer($self->get(), $self->size());
        }
    }
}

Additionally, you will need this code somewhere in your swig.i file to cache the jenv on startup.

%{
#include <stdexcept>
#include "jni.h"

static JavaVM *cached_jvm = 0;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
  cached_jvm = jvm;
  return JNI_VERSION_1_2;
}

static JNIEnv * JNU_GetEnv() {
  JNIEnv *env;
  jint rc = cached_jvm->GetEnv((void **)&env, JNI_VERSION_1_2);
  if (rc == JNI_EDETACHED)
    throw std::runtime_error("current thread not attached");
  if (rc == JNI_EVERSION)
    throw std::runtime_error("jni version not supported");
  return env;
}
%}

You might be able to get away without enabling the director feature support.

Now you can call the extended function in Java somewhat like this:

ByteBuffer bb = (ByteBuffer)nativeBufferReference.byteBuffer();
Adnan Y
  • 2,982
  • 1
  • 26
  • 29