I am trying to make a data conversion with JNI in c++. I have come into trouble working with java's ArrayList of strings, since I have not been able to convert such a data into a c++ vector or std::string*.
I would like to know how this conversion can be made without sacrificing too much performance, if possible. Any ideas would be appreciated.
I don't know if this fits your performance requirements, but it may be a good start.
For both options assume that jobject jList; is your ArrayList.
Option 1
Convert the List into an array and iterate on the array (maybe more applicable if you have a LinkedList instead of an ArrayList)
// retrieve the java.util.List interface class
jclass cList = env->FindClass("java/util/List");
// retrieve the toArray method and invoke it
jmethodID mToArray = env->GetMethodID(cList, "toArray", "()[Ljava/lang/Object;");
if(mToArray == NULL)
return -1;
jobjectArray array = (jobjectArray)env->CallObjectMethod(jList, mToArray);
// now create the string array
std::string* sArray = new std::string[env->GetArrayLength(array)];
for(int i=0;i<env->GetArrayLength(array);i++) {
// retrieve the chars of the entry strings and assign them to the array!
jstring strObj = (jstring)env->GetObjectArrayElement(array, i);
const char * chr = env->GetStringUTFChars(strObj, NULL);
sArray[i].append(chr);
env->ReleaseStringUTFChars(strObj, chr);
}
// just print the array to std::cout
for(int i=0;i<env->GetArrayLength(array);i++) {
std::cout << sArray[i] << std::endl;
}
Option 2
An alternative would be to use the List.size() and List.get(int) methods to retrieve the data from the list. As you use an ArrayList this solution would also be okay as the ArrayList is an RandomAccessList.
// retrieve the java.util.List interface class
jclass cList = env->FindClass("java/util/List");
// retrieve the size and the get method
jmethodID mSize = env->GetMethodID(cList, "size", "()I");
jmethodID mGet = env->GetMethodID(cList, "get", "(I)Ljava/lang/Object;");
if(mSize == NULL || mGet == NULL)
return -1;
// get the size of the list
jint size = env->CallIntMethod(jList, mSize);
std::vector<std::string> sVector;
// walk through and fill the vector
for(jint i=0;i<size;i++) {
jstring strObj = (jstring)env->CallObjectMethod(jList, mGet, i);
const char * chr = env->GetStringUTFChars(strObj, NULL);
sVector.push_back(chr);
env->ReleaseStringUTFChars(strObj, chr);
}
// print the vector
for(int i=0;i<sVector.size();i++) {
std::cout << sVector[i] << std::endl;
}
_edited: replaced JNI_FALSE with NULL_
_edited: replaced insert with push_back_
You could also use classic java iterators from within JNI, having the loop equally optimized both for ArrayList and LinkedList.
jobject jIterator = env->CallObjectMethod(jList, env->GetMethodID(env->GetObjectClass(jList), "iterator", "()Ljava/util/Iterator;"));
jmethodID nextMid = env->GetMethodID(env->GetObjectClass(jIterator), "next", "()Ljava/lang/Object;");
jmethodID hasNextMid = env->GetMethodID(env->GetObjectClass(jIterator), "hasNext", "()Z");
while (env->CallBooleanMethod(jIterator, hasNextMid)) {
jobject jItem = env->CallObjectMethod(jIterator, nextMid);
/* do something with jItem */
}
Related
I have a cache implemented in c++ using multimap and then I look up the cache and return the matching integer values as an array to the Java code.
The problem I am facing is, the Old generation progressively gets completely full and triggers continuous full GC from thereon. I found out, that, just returning a static array is causing the Old generation to fillup periodically and trigger a full GC. Eventually at a high load system will heap dump.
The code is like below
Java
for(JavaObject o: javaOjctes){
int[] values = cache.get(o.getkey1,o.getkey2,o.getkeyType[]);
//further processing
}
and on C++ side I have CacheImpl.cpp
JNIEXPORT jintArray JNICALL Java_com_test_cache_CacheService_get(JNIEnv *env, jobject thisObject, jstring key1,jstring key2,jobjectArray keyTypes){
const char *key1Value = (env)->GetStringUTFChars(key1,NULL);
env->ReleaseStringUTFChars(key1,key1Value);
//if(key1Value == NULL)
//return NULL;
const char *key2Value = (env)->GetStringUTFChars(key2,NULL);
//if(key2Value == NULL)
//return NULL;
string key2_str(key2value);
env->ReleaseStringUTFChars(key2,key2Value);
jsize length = env->GetArrayLength(keyTypes);
if(length == 0)
return NULL;
// I’m managing the C++ cache from Java side, so getting the
// reference of native cache from Java
jclass CacheService = env->GetObjectClass(thisObject);
Cache* cache= (Cache*) env->GetStaticLongField(CacheService,getCacheField(env,thisObject));
env->DeleteLocalRef(CacheService);
list<int> result;
for(int index=0;index<length;index++){
jstring keyType =(jstring) env->GetObjectArrayElement(keyTypes,index);
const char *key_type_str=env->GetStringUTFChars(keyType,NULL);
string key_type(key_type_str);
env->ReleaseStringUTFChars(keyType,key_type_str);
// Cache.cpp code is given below
list<int> intermediate=cache->get(key2_str+key_type,key1Value);
result.merge(intermediate);
intermediate=cache->get(key_type,key1Value);
result.merge(intermediate);
}
env->ReleaseStringUTFChars(key1,key1Value);
// I would return result list from below code
// For testing I commented all of the above code and
// tested with only below code. I could see Old gen
// Keep growing and GC spikes
jintArray Ids=env->NewIntArray(50);
if(Ids == NULL){
return NULL;
}
jint return_Array[50];
int j=0;
for(j=0; j<50 ;j++){
return_Array[j]=j;
}
env->SetIntArrayRegion(Ids,0,50,return_Array);
//env->DeleteLocalRef(Ids);
return Ids;
and The Cache.cpp code is like below
shared_ptr<multimap<string, SomeObject> > Cache::cacheMap;
list<int> Cache::get(string key,const char* key1Value){
SomeObject value;
list<int> IDs;
shared_ptr<multimap<string, SomeObject> > readMap=atomic_load(&cacheMap);
pair<mapIterator,mapIterator> result=readMap->equal_range(key);
for(auto iterator=result.first; iterator!=result.second; iterator++){
value=iterator->second;
if(!(value.checkID(key1Value))){
IDs.push_front(value.getID(key1Value));
}
}
return IDs;
}
In the code above I guess the Ids array
jintArray Ids=env->NewIntArray(50);
is not getting deleted so I tried to
//env->DeleteLocalRef(Ids);
This doesn't work as the empty array will be returned on Java side.
As described in the code comments, I commented out all the code except for returning a static array to Java. I could see that the Old Generation was getting filled up gradually triggering a Full GC, which I believe is a slow leak problem. I have a slow leak in this cache lookup flow. Am I missing something?
Thanks,
Raj
I'm developing an Android App and I'm receiving camera data from a lib in C++. I need to send this data from C++ to the Java code. For this, I'm using JNI. I'm able to set different fields in Java from the JNI and the C++ data (like the name or the type of the camera), but I'm unable to set the ID Field because it's an uint8_t array.
How can I do this?
I already tried several ways to do this but each time I've got an SIGSEGV error with an invalid address. For others fields I'm using
env->Set<Primitives>Field(jobject, jfieldID, value)
method but there are no methods like that for int array, are there?
So, I've tried to set this field by calling a method from my class and provide the int array as parameter but this function failed and returned the SIGSEGV error.
Then, I searched on the web and I tried to set the field through
env->GetObjectField(jobject, jfieldID)
and
env->SetIntArrayRegion(jintArray, start, end, myIntArray)
but here the first method returns always null.
JavaVM * mJVM; //My Java Virtual Machine
jobject mCameraObject, mThreadObject; //Previously initialize to call functions in the right thread
void onReceiveCameraList(void *ptr, uint32_t /*id*/, my::lib::Camera *arrayCamera, uint32_t nbCameras) {
JNIEnv *env;
mJVM->AttachCurrentThread(&env, nullptr);
if (env->ExceptionCheck())
return;
//Get Field, Method ID, Object and Class
jclass cameraClass = env->GetObjectClass(mCameraObject);
jfieldID camIDField = env->GetFieldID(cameraClass, "idCam", "[I");
jfieldID camNameField = env->GetFieldID(cameraClass, "label", "Ljava/lang/String;");
jfieldID camConnectedField = env->GetFieldID(cameraClass, "connected", "Z");
jfieldID camTypeField = env->GetFieldID(cameraClass, "typeProduit", "B");
jmethodID camReceptionMID = env->GetMethodID(env->GetObjectClass(mThreadObject), "onCamerasReception", "([Lcom/my/path/models/Camera;)V"); //Java function
jobjectArray cameraArray = env->NewObjectArray(nbCameras, cameraClass, mCameraObject); //Object return in the functions
//Put the cameras into the vector
std::vector<my::lib::Camera> vectorCameras;
if(!vectorCameras.empty())
vectorCameras.clear();
if ((arrayCamera != nullptr) && (nbCameras > 0)) {
for (uint32_t i = 0; i < nbCameras; ++i) {
vectorCameras.push_back(arrayCamera[i]);
}
}
//Set the my::lib::Camera field into Java::Camera
int c= 0;
for (auto & cam : vectorCameras)
{
jobject camera = env->AllocObject(cameraClass); //Object Camera to add in cameraArray object
// MY DATA TO SET ID FIELD ///
jint idArray[16];
for (int i = 0; i < 16 ; ++i) {
idArray[i] = cam.idCamera.data[i]; // uint8_t cam.idCamera.data[16]
}
///////// FIRST WAY /////////
jmethodID setIDCamMID = env->GetMethodID(env->GetObjectClass(camera), "setIDCam", "([I)V");
env->CallVoidMethod(camera, setIDCamMID, idArray);
///////// SECOND WAY /////////
jintArray jintArray1 = (jintArray)env->GetObjectField(camera, camIDField);
env->SetIntArrayRegion(jintArray1, 0, 16, idArray);
//Set<Primitives>Field : WORKING
env->SetObjectField(camera, camNameField, env->NewStringUTF((const char *) cam.labelCamera));
env->SetBooleanField(camera, camConnectedField, cam.isCameraConnected);
jbyte type;
if (cam.typeCamera == my::lib::TYPE_1 || cam.typeCamera == my::lib::TYPE_2 || cam.typeCamera == my::lib::TYPE_3) //type not known in JAVA
type = 0;
else
type = cam.typeCamera;
env->SetByteField(camera, camTypeField, type);
//Put camera object into cameraArray object
env->SetObjectArrayElement(cameraArray, c++, camera);
}//for
//Call Java method with cameraArray
env->CallVoidMethod(mThreadObject, camReceptionMID, dpCameraArray);
}//onreceiveCamera
Can someone tell me if I made a mistake or am using it the wrong way?
Is there any other way to set this data?
This yields a C++ array with elements of type jint:
// MY DATA TO SET ID FIELD ///
jint idArray[16];
for (int i = 0; i < 16 ; ++i) {
idArray[i] = cam.idCamera.data[i]; // uint8_t cam.idCamera.data[16]
}
It is important to understand that that is not a Java array. Therefore, this ...
///////// FIRST WAY /////////
jmethodID setIDCamMID = env->GetMethodID(env->GetObjectClass(camera), "setIDCam", "([I)V");
env->CallVoidMethod(camera, setIDCamMID, idArray);
... is incorrect. idArray is not the right type (and does not decay to a pointer to the right type) for the corresponding parameter to the Java method you are trying to invoke.
On the other hand, this ...
///////// SECOND WAY /////////
jintArray jintArray1 = (jintArray)env->GetObjectField(camera, camIDField);
env->SetIntArrayRegion(jintArray1, 0, 16, idArray);
... is ok, provided that the field already contains a reference to an int[] of length at least 16. Since it didn't work for you, I take it that the field's initial value does not satisfy that criterion.
If you need to create a new Java int[], then
use JNI's NewIntArray() function, which returns a jintArray with the length you specify. Then
use the appropriate JNI methods for setting the elements (see below), and finally
either assign the array directly to the target object's field with SetObjectField() or use the object's setter method to do so, as in your first attempt.
As for setting elements of the Java array, SetIntArrayRegion() will work fine for that (given an actual Java array of sufficient length), but that does require you to allocate a separate native array of primitives (your idArray) from which to copy the values. A slightly more efficient approach would be to use GetPrimitiveArrayCritical() to let Java provide the buffer -- possibly a direct pointer to the internal data -- and then ReleasePrimitiveArrayCritical() when you're done. Something like this:
// It is assumed here that the length of the array is sufficient, perhaps because
// we just created this (Java) array.
jint *idArray = (jint *) env->GetPrimitiveArrayCritical(jintArray1, NULL);
for (uint32_t i = 0; i < nbCameras; ++i) {
idArray[i] = cam.idCamera.data[i];
}
env->ReleasePrimitiveArrayCritical(jintArray1, idArray, 0);
For the first approach, you need to create a Java int[] first, fill it from idArray, and then call your method:
jintArray j_arr = env->NewIntArray(16);
env->SetIntArrayRegion(j_arr, 0, 16, idArray);
env->CallVoidMethod(camera, setIDCamMID, j_arr);
Your second approach does not work because you never called a constructor that filled the idCam field.
You can do that from JNI, however:
env->SetObjectField(camera, camIDField, j_arr);
ArrayList<String> myArraylist;
public ArrayList<String> getData(){
myArraylist = new ArrayList<String>();
myArraylist.add("1267982563");
myArraylist.add("2345678");
myArraylist.add("5432789");
return myArraylist;
}
How to get the each items from the above method in JNI side and Push to vector and return from the JNI to other CPP calls in the JNI layer.
Convert ArrayList to std::vector<std::string>:
jclass java_util_ArrayList;
jmethodID java_util_ArrayList_;
jmethodID java_util_ArrayList_size;
jmethodID java_util_ArrayList_get;
jmethodID java_util_ArrayList_add;
thread_local JNIEnv *env;
void init() {
java_util_ArrayList = static_cast<jclass>(env->NewGlobalRef(env->FindClass("java/util/ArrayList")));
java_util_ArrayList_ = env->GetMethodID(java_util_ArrayList, "<init>", "(I)V");
java_util_ArrayList_size = env->GetMethodID (java_util_ArrayList, "size", "()I");
java_util_ArrayList_get = env->GetMethodID(java_util_ArrayList, "get", "(I)Ljava/lang/Object;");
java_util_ArrayList_add = env->GetMethodID(java_util_ArrayList, "add", "(Ljava/lang/Object;)Z");
}
std::vector<std::string> java2cpp(jobject arrayList) {
jint len = env->CallIntMethod(arrayList, java_util_ArrayList_size);
std::vector<std::string> result;
result.reserve(len);
for (jint i=0; i<len; i++) {
jstring element = static_cast<jstring>(env->CallObjectMethod(arrayList, java_util_ArrayList_get, i));
const char *pchars = env->GetStringUTFChars(element, nullptr);
result.emplace_back(pchars);
env->ReleaseStringUTFChars(element, pchars);
env->DeleteLocalRef(element);
}
}
Push ArrayList from JNI back to Java
If you don't modify this list in JNI, then the best strategy would be to simply keep a global reference to it somewhere in your native code. If you modify it a little, keep this jobject always up-to-date (you will probably need the methods java_util_ArrayList_add or java_util_ArrayList_set).
If you choose to reconstruct the list from scratch the vector, you will unwind the above method:
jobject cpp2java(std::vector<std::string> vector) {
jobject result = env->NewObject(java_util_ArrayList, java_util_ArrayList_, vector.size());
for (std::string s: vector) {
jstring element = env->NewStringUTF(s.c_str());
env->CallBooleanMethod(result, java_util_ArrayList_add, element);
env->DeleteLocalRef(element);
}
return result;
}
At any rate, be careful with threads when you work with Jni, always attach your native threads and detach before the native thread is destroyed.
I am having a frustrating time resolving this issue and the JNI documentation is woefully sparse. Assistance would be greatly appreciated!
Am assigned a project on an Android networking app. The native side stores information about the users you are connected to and I need to send this user list to the Java side to update UI info. Essentially I need to compose a string array and a boolean array which contain the names and flags of connected users, but later more info may be sent. My difficulty is in releasing & cleaning up the Object array containing the String information. I am not clear on how to do this. Here is what I have:
void name_list_cb(struct user_info* user_list, size_t count, void *userdata)
{
jobject callbacks = (jobject)userdata;
JNIEnv *env;
(*g_vm)->GetEnv(g_vm, (void**)&env, JNI_VERSION_1_4);
jclass cls = (*env)->GetObjectClass(env, callbacks);
jmethodID method = (*env)->GetMethodID(env, cls, "user_list", "([Ljava/lang/String;[Z)V");
int i;
jobjectArray name_list;
jbooleanArray connected_list;
name_list = (jobjectArray)(*env)->NewObjectArray(env, count, (*env)->FindClass(env, "java/lang/String"), (*env)->NewStringUTF(env, ""));
connected_list = (jbooleanArray)(*env)->NewBooleanArray(env, count);
uint8_t boolean_arr[count];
for(i = 0; i < count; i++) {
(*env)->SetObjectArrayElement(env, name_list, i, (*env)->NewStringUTF(env, user_list[i].name));
boolean_arr[i] = user_list[i].connected;
}
(*env)->SetBooleanArrayRegion(env, connected_list, 0, count, (jboolean *)boolean_arr);
(*env)->CallVoidMethod(env, callbacks, method, name_list, connected_list);
(*env)->ReleaseBooleanArrayElements(env, connected_list, (jboolean *)boolean_arr, 0);
for(i = 0; i < count; i++) {
(*env)->ReleaseStringUTFChars(env, (*env)->GetObjectArrayElement(env, name_list, i), user_list[i].name);
//(*env)->ReleaseObjectArrayElements(env, name_list, count, 0);
}
(*env)->DeleteLocalRef(env, boolean_arr);
(*env)->DeleteLocalRef(env, name_list);
(*env)->DeleteLocalRef(env, connected_list);
(*env)->DeleteLocalRef(env, cls);
}
I get either a "referencetable overflow", or a "signal 11 (SIGSEGV), fault addr deadbaad". The overflow/memleak is the main prob. Basically I am not releasing the UTFChars and the Object elements. Although I have seen references to it online, my JNI version does not have ReleaseObjectArrayElement[s]. I have been researching how to do this exactly but no luck so far!
I think the problem is (*env)->DeleteLocalRef(env, boolean_arr); because boolean_arr is C function stack variable.
typedef jarray jobjectArray;
void (*DeleteLocalRef)(JNIEnv*, jobject);
So I think use DeleteLocalRef could release the jobjectArray.
Or you can just new the jobjectArray just one time in an initialize method.
I have a C++ program to receive binary data from network. After data is received, it will callback the data as a byte array from C++ to a Java client. I use the director feature in SWIG to easily achieve cross language polymorphism across C++ and Java for callback. Here's a link.
But I found there is no typemaps in SWIG from char* in C++ to byte[] in Java. So, I added a patch to the virous.i. Here's a link.
/*Director specific typemaps*/
%typemap(directorin, descriptor="[B") char *BYTE {
jbyteArray jb = (jenv)->NewByteArray(strlen(BYTE));
(jenv)->SetByteArrayRegion(jb, 0, strlen(BYTE), (jbyte*)BYTE);
$input = jb;
}
%typemap(directorout) char *BYTE {
$1 = 0;
if($input){
$result = (char *) jenv->GetByteArrayElements($input, 0);
if(!$1)
return $null;
jenv->ReleaseByteArrayElements($input, $result, 0);
}
}
%typemap(javadirectorin) char *BYTE "$jniinput"
%typemap(javadirectorout) char *BYTE "$javacall"
For example. I have a class in C++ called A:
class A{
public:
virtual void onDataReceived(const char* BYTE, const size_t len) {}
};
Then in Java code I have another class B to extend A:
class B extends A {
#Override
public void onDataReceived(byte[] data, long len) {
//handling the data.
}
}
I can receive the byte array in Java code, but it seems it is never garbage collected by JVM. The onDataReceived method in SWIG generated wrapper file is like this:
void SwigDirector_A::onDataReceived(char const *BYTE, size_t const len) {
JNIEnvWrapper swigjnienv(this);
JNIEnv * jenv = swigjnienv.getJNIEnv();
jobject swigjobj = (jobject) NULL;
jbyteArray jBYTE = 0;
jlong jlen;
if (!swig_override[3]) {
A::onDataReceived(BYTE,len);
return;
}
swigjobj = swig_get_self(jenv);
if (swigjobj && jenv->IsSameObject(swigjobj, NULL) == JNI_FALSE) {
{
jbyteArray jb = (jenv)->NewByteArray(strlen(BYTE));
(jenv)->SetByteArrayRegion(jb, 0, strlen(BYTE), (jbyte*)BYTE);
jBYTE = jb;
}
jlen = (jlong) len;
jenv->CallStaticVoidMethod(Swig::jclass_DataTransferJNI, Swig::director_methids[3], swigjobj, jBYTE, jlen);
if (jenv->ExceptionCheck() == JNI_TRUE) return;
} else {
SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "null upcall object");
}
if (swigjobj) jenv->DeleteLocalRef(swigjobj);
}
In my C++ code, I will delete the data in the receive buffer after the callback is done. But I checked from Java VisualVM that the memory used by java client process is not GC-ed after a long time. Thanks advance for any help!
PS. The data is quite large, it is around 32KB.
I got it solved by added deletion of reference to jb in Director typemaps: (jenv)->DeleteLocalRef(jb);
Here's the updated patch.