Try impl C library on Android but app crashes - android

I want to add C library to my project. lzfse for decode img via apple algorithm.
I have added c files to project
added CmakeLines file:
externalNativeBuild {
cmake {
path "src/main/lzfse/CMakeLists.txt"
version "3.10.2"
}
}
I have written JNI for decode:
JNIEXPORT jint JNICALL
Java_com_android_Decompressor_decode(
JNIEnv* env, jclass cls, jobject src, jobject dst
) {
uint8_t* src_buffer = (*env)->GetDirectBufferAddress(env,src);
const size_t src_size = (const size_t) (*env)->GetDirectBufferCapacity(env, src);
uint8_t* dst_buffer = (*env)->GetDirectBufferAddress(env,dst);
size_t dst_size = (size_t) (*env)->GetDirectBufferCapacity(env, dst);
jlong test = lzfse_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
return (jint) test;
}
then I call from kt decode fun:
val buf = ByteBuffer.wrap(byteArray)
val buf_out = ByteBuffer.allocateDirect(byteArray.size *20)
val size= decode(dstArray = buf_out, srcArray = buf)
But
My app just crashes
2020-02-25 20:12:25.717 28603-28603/com.android A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 28603 (), pid 28603 ()
Where did I lose?

Wrapping a ByteArray does not result in a direct buffer (at least on Android).
You will need to call GetByteArrayElements to get a (possibly copied) pointer.

I have fixed the crash! After some time I have decided to publish the working code.
So, the call native c++ from project:
private val dstArray: ByteBuffer = ByteBuffer.allocateDirect(DESTINATION_BUFFER_CAPACITY)
private val srcArray: ByteBuffer = ByteBuffer.allocateDirect(SRC_BUFFER_CAPACITY)
decode(srcArray.put(byteArray), dstArray)
Here instead of:
val buf = ByteBuffer.wrap(byteArray)
val size= decode(dstArray = buf_out, srcArray = buf)
I allocate a direct buffer via ByteBuffer.allocateDirect and put the source array in buffer srcArray.put(byteArray).
Because as #Botje said in his post :
Wrapping a ByteArray does not result in a direct buffer (at least on
Android).
my JNI:
JNIEXPORT JNICALL
Java_com_android_Decompressor_decode(
JNIEnv *env, jclass cls, jobject src, jobject dst
) {
uint8_t *src_buffer = (*env)->GetDirectBufferAddress(env, src);
const size_t src_size = (const size_t) (*env)->GetDirectBufferCapacity(env, src);
uint8_t *dst_buffer = (*env)->GetDirectBufferAddress(env, dst);
size_t dst_size = (size_t) (*env)->GetDirectBufferCapacity(env, dst);
lzfse_decode_buffer(dst_buffer, dst_size, src_buffer, src_size, NULL);
}
So I almost haven't changed JNI.
But in my first code sample, I have found that all works until the line:
//...
return (jint) test;
The line caused the crash.
In my case, I didn't need return value so I rewrite JNI function without return any values, but I think it possible to find where I have failed with the return value.
Thanks, guys who tried to give some help, I hope my post also can be helpful for someone.

Related

Concatenating byte arrays in Jni android

I am new to JNI and trying to muddle my way through. Please can someone point me in the direction to do the following in JNI. I am a bit out of my depth here, Can JNI handle Byte Arrays. Also were is the best places to find JNI examples.
This is the JAVA code I want to convert
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(key1);
outputStream.write(key2);
byte[] key3 = outputStream.toByteArray();
Thank you in advance
Rob
Here is a literal translation of the Java code you posted:
jclass cls_BAOS = env->FindClass("java/io/ByteArrayOutputStream");
jmethodID ctr_BAOS = env->GetMethodID(cls_BAOS, "<init>", "()V");
jobject baos = env->NewObject(cls_BAOS, ctr_BAOS);
jmethodID mid_BAOS_writeBytes = env->GetMethodID(cls_BAOS, "writeBytes", "([B)V");
env->CallVoidMethod(baos, mid_BAOS_writeBytes, key1);
env->CallVoidMethod(baos, mid_BAOS_writeBytes, key2);
jmethodID mid_BAOS_toByteArray = env->GetMethodID(cls_BAOS, "toByteArray", "()[B");
jbyteArray key3 = (jbytearray)env->CallObjectMethod(baos, mid_BAOS_toByteArray);
And here is an implementation that uses lower-level operations:
jsize key1len = env->GetArrayLength(key1);
jsize key2len = env->GetArrayLength(key2);
jbyteArray key3 = env->NewByteArray(key1+key2);
{
jbyte *key1ptr = env->GetByteArrayElements(key1, nullptr);
env->SetByteArrayRegion(key3, 0, key1len, key1ptr);
env->ReleaseByteArrayElements(key1, key1ptr, JNI_ABORT);
}
{
jbyte *key2ptr = env->GetByteArrayElements(key2, nullptr);
env->SetByteArrayRegion(key3, key1len, key2len, key2ptr);
env->ReleaseByteArrayElements(key2, key2ptr, JNI_ABORT);
}
You could also memcpy both byte arrays to a single C++ array first and then use a single call to SetByteArrayRegion or anything else really, but what's the point? The Java code was simple and readable and all of this JNI code is just adding pointless obfuscation.
Thank you Botje, this is the final code that works.
jclass cls_BAOS = env->FindClass("java/io/ByteArrayOutputStream");
jmethodID ctr_BAOS = env->GetMethodID(cls_BAOS, "<init>", "()V");
jobject baos = env->NewObject(cls_BAOS,ctr_BAOS);
jmethodID mid_BAOS_writeBytes = env->GetMethodID(cls_BAOS, "writeBytes", "([B)V");
env->CallVoidMethod(baos, mid_BAOS_writeBytes, a);
env->CallVoidMethod(baos, mid_BAOS_writeBytes, b);
jmethodID mid_BAOS_toByteArray = env->GetMethodID(cls_BAOS, "toByteArray", "()[B");
jbyteArray key3 = (jbyteArray)env->CallObjectMethod(baos, mid_BAOS_toByteArray);

Error when trying to access nativeLibraryDir

i'm trying to access getPackageManager.getApplicationInfo in jni.
const char* getNativeLibPath(JNIEnv* env, jobject thiz, const char* libraryName, const char* packageName) {
jclass contextClass = env->GetObjectClass(thiz);
jmethodID getPackageManager = env->GetMethodID(contextClass, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject instantiatePackageManager = env->CallObjectMethod(thiz, getPackageManager);
jclass packageManagerClass = env->GetObjectClass(instantiatePackageManager);
jmethodID getApplicationInfo = env->GetMethodID(packageManagerClass, "getApplicationInfo", "(Ljava/lang/String;I)Landroid/content/pm/ApplicationInfo;");
jobject instantiateApplicationInfo = env->CallObjectMethod(thiz, getApplicationInfo, packageName, 0);
jclass applicationInfoClass = env->GetObjectClass(instantiateApplicationInfo);
jfieldID nativeLibraryDir = env->GetFieldID(applicationInfoClass, "nativeLibraryDir", "Ljava/lang/String;");
auto string = (jstring) env->GetObjectField(instantiateApplicationInfo, nativeLibraryDir);
const char* returnValue = env->GetStringUTFChars(string, nullptr);
std::string appendedResult = std::string(returnValue) + std::string("/") + std::string(libraryName);
return appendedResult.c_str();
}
This is my code for it. However for some reason i'm getting this error: JNI ERROR (app bug): accessed stale WeakGlobal 0x74eecd21ff (index 1324143135 in a table of size 38) JNI DETECTED ERROR IN APPLICATION: use of deleted weak global reference 0x74eecd21ff
Any help is appreciated!
Your code has at least three problems:
You call getApplicationInfo with a const char * which expects a Java string:
jobject instantiateApplicationInfo = env->CallObjectMethod(instantiatePackageManager, getApplicationInfo, env->NewStringUTF(packageName), 0);
You need to call env->ReleaseStringUTF(returnValue) to release the string on the Java side
You cannot return a const char * like that. Either return the std::string directly, or allocate memory with new char[] and let the caller free it.

Converting char* to cv::Mat in NDK Android studio

I have a native C++ method, which I am using to read an image called "hi.jpg". The code below finds the asset, and loads the data into a char* buffer. (I've tried other methods such as imread() and the file is not found). I would then like to change this data into Mat format, so I've followed some instructions to put the char* buffer into std::vector , and then use cv::imdecode to convert the data to Mat.
JNIEXPORT jint JNICALL Java_com_example_user_application_MainActivity_generateAssets(JNIEnv* env,jobject thiz,jobject assetManager) {
AAsset* img;
AAssetManager *mgr = AAssetManager_fromJava(env, assetManager);
AAssetDir* assetDir = AAssetManager_openDir(mgr, "");
const char* filename;
while ((filename = AAssetDir_getNextFileName(assetDir)) != NULL) {
AAsset *asset = AAssetManager_open(mgr, filename, AASSET_MODE_UNKNOWN);
if(strcmp(filename, "hi.jpg")==0 ) {
img = asset;
}
}
long sizeOfImg = AAsset_getLength(img);
char* buffer = (char*) malloc (sizeof(char)*sizeOfImg);
AAsset_read(img, buffer, sizeOfImg);
std::vector<char> data(buffer, buffer + strlen(buffer));
cv::Mat dataToMat = cv::imdecode(data, IMREAD_UNCHANGED);
return 0;
}
My problem is that I don't know how to test that the data has been successfully converted into Mat. How can I test this? I have ran the debugger and inspected dataToMat, but it isn't making much sense.

Android M Bitmap crash

I am trying to create a Android Bitmap object(Java) in JNI(C++) through reflection. Below code was working fine until Android L version but not on Android M. In Android M, name of mNativeBitmap is modified to mNativePtr which is taken into consideration. Even after this change code is crashing.
bitmapClazz = env->FindClass("android/graphics/Bitmap");
createBitmapMethod = env->GetStaticMethodID(bitmapClazz, "createBitmap",
"(IILandroid/graphics/Bitmap$Config;)"
"Landroid/graphics/Bitmap;");
#ifdef AND_VER_6_0
jfieldID nativeBitmap = env->GetFieldID(bitmapClazz, "mNativePtr", "J");
#elif AND_VER_5_0
jfieldID nativeBitmap = env->GetFieldID(bitmapClazz, "mNativeBitmap", "J");
#endif
jclass configClazz = env->FindClass("android/graphics/Bitmap$Config");
jmethodID createConfigMethod = env->GetStaticMethodID(configClazz, "nativeToConfig","(I)Landroid/graphics/Bitmap$Config;");
jfieldID RB565FieldId = env->GetStaticFieldID(configClazz, "RGB_565", "Landroid/graphics/Bitmap$Config;");
jobject RGB565Config = env->GetStaticObjectField(configClazz, RB565FieldId);
jobject jBitmap = env->CallStaticObjectMethod( bitmapClazz,
createBitmapMethod,
width,
height,
RGB565Config);
#if defined AND_VER_5_0 || defined AND_VER_6_0
SkBitmap *bitmap =
(SkBitmap *) env->GetLongField(jBitmap, nativeBitmap);
#else
SkBitmap *bitmap =
(SkBitmap *) env->GetIntField(jBitmap, nativeBitmap);
#endif
memcpy((uint8_t*)bitmap->getPixels(),
(uint8_t*)thumbnail_PixelArray, height*width*2);
If I remove the memcpy of bitmap->getPixels() then the code is not crashing. So we suspect some memory corruption due to memcpy call. If anyone has solution to this problem please help me resolve it.

getArrayLength() is returning a huge number...

I am trying to understand jni, so I started hacking up hellojni, and I ran into this problem.
My java code looks like this:
short[] buf = new short[16];
Log.d("hello", "before!");
write(buf, 0, 16);
and my C code looks like this:
jint
Java_com_example_hellojni_HelloJni_write(JNIEnv* env, jshortArray buf, jint off, jint len)
{
char debug[1024];
int ii = 0;
jsize cbuflen = (*env)->GetArrayLength(env, buf);
sprintf(debug, "array length: %d", cbuflen);
LOGD(debug);
...
...
The output is:
array length: 1079082088
Why is the array length so big?
Could you show your entire JNI file? You are not declaring the target object in your JNI function. Usually the arguments are JNIEnv* env, jobject javaObject, etc. This means that what you believe is the jshortArray is actually the pointer to a Java object, which would explain the weird results you are getting.

Categories

Resources