Why jni calls fail on modification of composable remembered values? - android

This is my PseudoStream class which interfaces with C++
class PseudoStream{
companion object{
// This native function receives a String as a Input and returns a Modified output
external fun output(input: String): String
}
}
This is the C++ Inteface code to for the output function
// Creates a jstring from a std::string passed as mystr
jstring Create_Java_String(JNIEnv *environment, const std::string& mystr){
return environment->NewStringUTF(mystr.data());
}
// Converts a jstring to std::string and returns it
std::string jstringtostring(JNIEnv *env, jstring jStr) {
if(!jStr)
return "";
const jclass stringClass = env->GetObjectClass(jStr);
const jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
const jbyteArray stringJbytes = (jbyteArray) env->CallObjectMethod(jStr, getBytes, env->NewStringUTF("UTF-8"));
size_t length = (size_t) env->GetArrayLength(stringJbytes);
jbyte* pBytes = env->GetByteArrayElements(stringJbytes, NULL);
std::string ret = std::string((char *)pBytes, length);
env->ReleaseByteArrayElements(stringJbytes, pBytes, JNI_ABORT);
env->DeleteLocalRef(stringJbytes);
env->DeleteLocalRef(stringClass);
return ret;
}
// This is the actual function called by jni to modify the String
extern "C"
JNIEXPORT jstring JNICALL
Java_com_phantom_automath_PseudoStream_00024Companion_output(JNIEnv *env, jobject thiz, jstring input) {
std::string expression = jstringtostring(env, input);
return Create_Java_String(env, expression + " (Modified by C++)");
}
Now, this code is the main UI code
var text_input = remember {mutableStateOf("Initial value ")} // Used by the TextField for Input
var text_output = remember {mutableStateOf("")} // Used by Text() for modified output
TextField(
value = text_input.value,
onValueChange = { text_input.value = it },
)
Button(onClick = {
// The jni method is called here
text_output.value = PseudoStream.output(text_input.value)
}
){
Text("Modify")
}
Text(text_output.value) // Displays the output by the jni call
So here is my problem, Whenever I click the Modify Button without changing the initial value of the TextField. The code works as expected. But, once I modify the TextField and then click Modify Button again this runtime error occurs in kotlin code.
java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter interactionSource
at androidx.compose.foundation.ClickableKt.clickable(Unknown Source:7)
at androidx.compose.foundation.ClickableKt.clickable$default(Clickable.kt:112)
at androidx.compose.material.ButtonKt.Button(Button.kt:117)
The code works fine as long as I don't modifiy the value inside the TextField

This is not the error caused by JNI , this issue is with how you are passing your TextField value to the jniMethod . The error in the stack trace says :
java.lang.NullPointerException: Parameter specified as non-null is null.
This means that you are passing null value to the function .
The code works fine as long as I don't modifiy the value inside the TextField -> This is because you have set the initial value of text_input , so a non-null value is passed ,thus causing no-error .
You need to do the following to correct your error :
Just change this line :
text_output.value = PseudoStream.output(text_input.value)
To :
text_output.value = PseudoStream.output(text_input.value.text)
Here you pass the text that you given as an input to the TextField instead of the TextField data class which will do the trick .
To know more about the above said , debug text_input.value and know your mistake .

I have finally figured out why my app is not working. It has something to do with nullables inside the code logic of recompostion. Since, I have updated my android project to use the stable release of compose. The code is actually working as expected.

Related

argument passed to jni from java are giving me weird values

I'm trying to call a JNI function from my Java code in an android app,
The function is called but the parameters value in the JNI function is not the same as the one passed in the function.
here are my java declaration and call:
public native void setIA(Integer model);
setIA(1);
and here is my JNI function
extern "C" JNIEXPORT void JNICALL
Java_com_sfy_vitaltechnics_Utils_Parameters_setIA(JNIEnv *env, jobject thiz, jint model) {
LOGD( "This is a number from JNI: %d", model );
}
I get value like -578062932 but it's never the same value.
I tried several cast and type of arguement(long, double, float, string and thir java equivalent).
and everytime it give me weird value(for string it give me non UTF-8 character)
I think the problem come from my way of declaring the function but I'm not sure
Integer corresponds to jobject in C, so you should either alter C function or change java declaration to setIA(int model); to match existing C function.

How to copy a JNI jstring into a C++ std::string [duplicate]

I have a Java instance method which returns a String and I'm calling this method through JNI in C++. I have written the following code:
const char *DiagLayerContainer_getDESC(JNIEnv *env, jobject diagLayer) {
jclass diagLayerClass = env->FindClass(PARSER_CLASS);
jmethodID getDESCDiagLayerMethodID = env->GetMethodID(diagLayerClass, "getDESCDiagLayer", "(Ljava/lang/Object;)Ljava/lang/String;");
jstring returnString = (jstring) env->CallObjectMethod(diagLayer, getDESCDiagLayerMethodID);
return env->GetStringUTFChars(returnString, JNI_FALSE);
}
How do I get the string and convert it to a const char *?
My program crashes on the last line with access violation to 0x00000000. returnString is not NULL.
According to GetStringUTFChars, the last parameter is a pointer to jboolean.
Change
return env->GetStringUTFChars(returnString, JNI_FALSE);
to
return env->GetStringUTFChars(returnString, NULL);
Or better yet, return a std::string
std::string DiagLayerContainer_getDESC(...) {
...
const char *js = env->GetStringUTFChars(returnString, NULL);
std::string cs(js);
env->ReleaseStringUTFChars(returnString, js);
return cs;
}
I've built a similar simple example and the code as is, seems fine so far.
Although, there are two possible error sources.
The first one is the method signature. Try "()Ljava/lang/String;" instead of "(Ljava/lang/Object;)Ljava/lang/String;".
The second one is in the java source itself. If the java method returns a null string, CallObjectMethod() will return a NULL jstring and GetStringUTFChars() fails.
Add a
if (returnString == NULL)
return NULL;
after CallObjectMethod().
So look into the java source and see, whether the method getDESCDiagLayer() might return a null string.

What is the lifespan of a const char* in JNI?

I'm working on a Android NDK-based code and I don't get how "long" is the lifespan of my string literals.
My program is a C++ game which interacts with the Android Java-side from time to time – like uploading data on servers, connecting to Facebook, etc.
In a downcall from Java to (native) C++, I use different ways to convert jstring to const char*, and I realized that some work but another doesn't. And I don't understand why.
E.g.: in a JNI downcall, where toCall(const char*) is a regular C++ function which is defined elsewhere.
JNIEXPORT void JNICALL Java_com_alpha_beta_Gamma_onDelta(JNIEnv *env, jclass, jstring jstr) {
// #1
const char* cVar = jnu::ToString(jstr).c_str();
toCall(cVar);
// #2
std::string strVar = jnu::ToString(jstr);
toCall(strVar.c_str())
// #3
toCall(jnu::ToString(jstr).c_str());
// #4
std::string strVara;
jnu::SetString(jstr, strVara);
toCall(strVara.c_str());
}
And the jnu functions are:
std::string jnu::ToString(JNIEnv *env, jstring jstr) {
if (jstr) {
const char *cstr = env->GetStringUTFChars(jstr, NULL);
std::string str = cstr;
env->ReleaseStringUTFChars(jstr, cstr);
return str;
}
return std::string();
}
std::string& jnu::SetString(JNIEnv* env, jstring jstr, std::string& output) {
if (jstr) {
const char *cstr = env->GetStringUTFChars(jstr, NULL);
output = cstr;
env->ReleaseStringUTFChars(jstr, cstr);
}
return output;
}
Cases #2, #3 and #4 work just fine, whereas #1 just send plain garbage to the function to call.
Maybe it's C++ 101 but I don't get why my #1 case is buggy. Someone please give me any clue?
Thanks!
The char array pointed to by the pointer returned by std::string::c_str is owned by the std::string instance. That means that when the string that you called c_str on goes out of scope the data pointed to is deleted.
This is what happens in case #1. The return value of jnu::ToString (or any other temporary) goes out of scope at the end of the expression, so as soon as you initialize cVar the data it points to gets deleted and you have a dangling pointer. Any attempt to dereference a dangling pointer will result in undefined behavior.
In case #2 the string returned by jnu::ToString gets copied to strVar (or maybe moved, or maybe Return Value Optimization kicks in and no temporary ever actually gets created; it doesn't really matter). The data pointed to by the pointer returned by strVar.c_str() will continue to exist until strVar goes out of scope or needs to reallocate its storage.
In case #3 the temporary string returned by jnu::ToString will continue to exist through the full expression, so it survives through the call to toCall.
Case #4 is similar to #2 except for how jnu::SetString fills strVara.

appending values each time when calling JNI method

i am using a JNI to fetch signature of apk and i am getting it very well. when i calling this method from java first time i am getting the exact value. calling it again i am getting appended values with exact value (eg 1234456123456). PFB the code which i am using
char* getSignatureMd5(JNIEnv* env, jobject obj)
{
char* sign = loadSignature(env, obj);
MD5_CTX context = { 0 };
MD5Init(&context);
MD5Update(&context, (unsigned char*)sign, strlen(sign));
unsigned char dest[16] = { 0 };
MD5Final(dest, &context);
int i;
static char destination[32]={0};
for (i = 0; i < 16; i++) {
sprintf(destination, "%s%02x", destination, dest[i]);
}
return destination;
}
getToken JNI method
JNIEXPORT jstring JNICALL Java_com_sign_signaturecapturesbi_MyAdapter_getToken(JNIEnv *env, jobject obj)
{
char* signValue = getSignatureMd5(env, obj);
__android_log_print(ANDROID_LOG_VERBOSE, "MyApp", "signValue %s", signValue);
return (*env)->NewStringUTF(env, signValue);
}
These lines cause undefined behavior:
for (i = 0; i < 16; i++) {
sprintf(destination, "%s%02x", destination, dest[i]);
}
man 3 printf:
C99 and POSIX.1-2001 specify that the results are undefined if a call
to sprintf(), snprintf(), vsprintf(), or vsnprintf() would cause
copying to take place between objects that overlap (e.g., if the
target string array and one of the supplied input arguments refer to
the same buffer).
Moreover destination is static and because of this it keeps its content between calls. Together these points give you such a weird behavior.
Since dest size is well known, you can simply unroll loop, also don't forget to add one extra cell to destination for terminating \0. And, if possible, you should use snprintf() instead:
static char destination[33];
snprintf(destination, sizeof destination,
"%02x%02x%02x%02x%02x%02x%02x%02x"
"%02x%02x%02x%02x%02x%02x%02x%02x",
dest[0], dest[1], dest[2], dest[3],
dest[4], dest[5], dest[6], dest[7],
dest[8], dest[9], dest[10], dest[11],
dest[12], dest[13], dest[14], dest[15]);
In this case you can leave destination as static one, since your code doesn't relay on its content anymore. But note that getSignatureMd5() returns pointer to the same buffer each time you call it, as result subsequent calls erase result obtained by previous calls.

native c++ code returns garbage value

When I call a native code for first time it returns proper result, but the second time it returns garbage value.
Here's the native code :
JNIEXPORT jstring JNICALL
Java_com_example_project_NativeCodes_method2(JNIEnv *env, jobject thisObj,
jstring st) {
const char* st1=env->GetStringUTFChars(st,0);
string str=st1;
const int len=str.length();
for(int i=0;i<len;i++){
if (str[i]>=97 && str[i]<=122)
str[i] = str[i]-32;
}
.....
env->ReleaseStringUTFChars(st, st1);
return env->NewStringUTF(str.c_str());
}
And in Java class I declare native method as follows
public class NativeCodes(){
static{
System.loadLibrary("abclib");
}
public synchronized native String method2(String s);
}
In MainActivity.java I call native method like:
String s1,s2,s3,s4;
s1=edttxt1.getText().toString();
s2=edttxt2.getText().toString();
NativeCodes nc=new NativeCodes();
s3=nc.method2(s1);
s4=nc.method2(s2);
While debugging I find s3 gets the proper result whereas s4 receives garbage value.
I think I'm releasing the pointer properly with env->ReleaseStringUTFChars(st, st1);
But if a Log() statement is inserted between two calls for the method, both calls return correct result. For example,
s3=nc.method2(s1);
Log.i("String","Value: "+s3);
s4=nc.method2(s2);
Log.i("String","Value: "+s4);
This gives expected result. But I don't want to insert unnecessary code as there are many such native method calls made.
where am I doing wrong? Any help is highly appreciated.

Categories

Resources