简体   繁体   中英

call a static JNI method to return a String from C++

I am trying to call the following java method in Android

public static String getLevelFile(String levelName) { /*body*/}

from c++ using the following jni code

JniMethodInfoJavaApi methodInfo;

    if (! getStaticMethodInfo(methodInfo, "getLevelFile", "(Ljava/lang/String;)Ljava/lang/String;"))
        {
            return std::string("");
        }
    LOGD("calling getLevelFile");
    jstring returnString = (jstring) methodInfo.env->CallStaticObjectMethod(methodInfo.classID, methodInfo.methodID, levelName.c_str());
    LOGD("returned from getLevelFile");
    methodInfo.env->DeleteLocalRef(methodInfo.classID);

    const char *js = methodInfo.env->GetStringUTFChars(returnString, NULL);
    std::string cs(js);
    methodInfo.env->ReleaseStringUTFChars(returnString, js);
    LOGD("returning Level data");

The app crashes when doing the CallStaticMethodObject() . I have verified that the method signature is correct by using javap . And the LOGD("calling getLevelFile"); prints fine and then it crashes. I am able to do other CallStaticVoidMethod() s from the same class but not this one. Any ideas what I am doing wrong?

You're in luck, Java and Android both use the Unicode character set. However, Android (by default) use the UTF-8 encoding, which JNI doesn't intrisically support. Nonetheless, Java classes are fully capable of converting between character set encodings. The lang.java.String constructors allow you to specify a character set/encoding or use the OS-default, which on Android, of course, is coded as UTF-8.

To make it easier (I prefer coding in Java, minimizing code that calls the JNI library), create an overload of your method and do some of the implementation in Java:

private static byte[] getLevelFile(byte[] levelName) {
    return getLevelFile(new String(levelName)).getBytes();
}

Now the JNI code only has to deal with jbytearray, both for the parameter and the return value:

JniMethodInfoJavaApi methodInfo;

if (! getStaticMethodInfo(methodInfo, "getLevelFile", "([B)[B"))
{
    return std::string("");
}

LOGD("calling getLevelFile");

int nameLength = levelName.length();
jbyteArray nameBytes = methodInfo.env->NewByteArray(nameLength);
methodInfo.env->SetByteArrayRegion(nameBytes, 0, nameLength, reinterpret_cast<const jbyte*>(levelName.c_str()));

jbyteArray returnString = (jbyteArray) methodInfo.env->CallStaticObjectMethod(methodInfo.classID, methodInfo.methodID, nameBytes);
LOGD("returned from getLevelFile");
methodInfo.env->DeleteLocalRef(methodInfo.classID);
methodInfo.env->DeleteLocalRef(nameBytes);

int returnLength = methodInfo.env->GetArrayLength(returnString);
std::string data;
data.reserve(returnLength);
methodInfo.env->GetByteArrayRegion(returnString, 0, returnLength, reinterpret_cast<jbyte*>(&data[0]));
methodInfo.env->DeleteLocalRef(returnString);

LOGD("returning Level data");
return data;

You can't directly pass a nul terminated string (returned from c_str() ) as a parameter to a Java/JNI method.

To pass it to the method, create a jstring from the nul terminated string (for example using NewStringUTF ) and pass that instead.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM