Reputation: 1837
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!
Upvotes: 2
Views: 1452
Reputation: 3240
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();
Upvotes: 1
Reputation: 1732
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
Upvotes: 1