Android ICS 4.0 NDK NewStringUTF вылетает из приложения

У меня есть метод в JNI C/C++, который принимает jstring и возвращает обратно jstring некоторые вещи, как показано ниже,

  NATIVE_CALL(jstring, method)(JNIEnv * env, jobject obj, jstring filename)
  {

// Get jstring into C string format.
  const char* cs = env->GetStringUTFChars (filename, NULL);
  char *file_path = new char [strlen (cs) + 1]; // +1 for null terminator
  sprintf (file_path, "%s", cs);
  env->ReleaseStringUTFChars (filename, cs);


  reason_code = INTERNAL_FAILURE;
  char* info = start_module(file_path);  


  jstring jinfo ;


  if(info==NULL)
  {
      jinfo = env->NewStringUTF(NULL);
  }
  else
  {
      jinfo = env->NewStringUTF(info);

  }


  delete info;

  info = NULL;
  return jinfo;
  }

Код прекрасно работает с предыдущими версиями Android 4.0, такими как 2.2,2.3 и так далее. В ICS 4.0 проверка JNI включена по умолчанию, и из-за этого приложение вылетает, выдавая следующую ошибку

 08-25 22:16:35.480: W/dalvikvm(24027): **JNI WARNING: input is not valid Modified UTF-8: illegal  continuation byte 0x40**
08-25 22:16:35.480: W/dalvikvm(24027):              
08-25 22:16:35.480: W/dalvikvm(24027): ==========
08-25 22:16:35.480: W/dalvikvm(24027): /tmp/create
08-25 22:16:35.480: W/dalvikvm(24027): ==========
08-25 22:16:35.480: W/dalvikvm(24027): databytes,indoorgames,drop
08-25 22:16:35.480: W/dalvikvm(24027): ==========���c_ag����ϋ@�ډ@�����@'
 08-25 22:16:35.480: W/dalvikvm(24027):              in Lincom/inter       /ndk/comNDK;.rootNDK:(Ljava/lang/String;)Ljava/lang/String; **(NewStringUTF)**
08-25 22:16:35.480: I/dalvikvm(24027): "main" prio=5 tid=1 NATIVE
08-25 22:16:35.480: I/dalvikvm(24027):   | group="main" sCount=0 dsCount=0 obj=0x40a4b460   self=0x1be1850
08-25 22:16:35.480: I/dalvikvm(24027):   | sysTid=24027 nice=0 sched=0/0 cgrp=default handle=1074255080
08-25 22:16:35.490: I/dalvikvm(24027):   | schedstat=( 49658000 26700000 48 ) utm=1 stm=3 core=1
08-25 22:16:35.490: I/dalvikvm(24027):   at comrootNDK(Native Method)

Я не знаю, где я не прав. Если вы видите выше, NewStringUTF добавляет некоторое значение мусора в байты c Char*.

  1. Любая идея о том, почему это происходит
  2. Любое альтернативное решение для достижения вышеизложенного приветствуется

Я действительно ценю, если один из вас может помочь мне. заранее спасибо

уважает меня

10 ответов

Решение

Я решил эту проблему, возвращая байтовый массив вместо String. На стороне Java теперь я преобразовываю массив байтов в строки. Работает хорошо! Не используйте NewStringUTF() для Android 4.0 и выше, поскольку в Google Android NDK уже есть сообщение об ошибке.

Причина этой проблемы напрямую связана с известной ошибкой UTF-8 в функции GetStringUTFChars() NDK/JNI (и, возможно, связанной с ней функцией, такой как NewStringUTF). Эти функции NDK не преобразуют дополнительные символы Unicode (т.е. символы Unicode со значением U+10000 и выше) правильно. Это приводит к неправильному UTF-8 и последующим сбоям.

Я столкнулся с падением при обработке пользовательского ввода текста, который содержит символы смайлика ( см. Соответствующую таблицу Юникода). Символы смайликов находятся в диапазоне дополнительных символов Юникод.

Анализ проблемы

  1. Клиент Java передает строку, содержащую дополнительный символ Unicode, в JNI / NDK.
  2. JNI использует функцию NDK GetStringUTFChars() для извлечения содержимого строки Java.
  3. GetStringUTFChars() возвращает строковые данные как неверные и недействительные UTF-8.

Существует известная ошибка NDK, из-за которой GetStringUTFChars() неправильно преобразует дополнительные символы Юникода, создавая неверную и недопустимую последовательность UTF-8.

В моем случае полученная строка представляла собой буфер JSON. Когда буфер был передан в синтаксический анализатор JSON, он сразу потерпел неудачу, потому что один из символов UTF-8 извлеченного UTF-8 имел недопустимый байт префикса UTF-8.

Возможное решение

Решение, которое я использовал, можно резюмировать следующим образом:

  1. Цель состоит в том, чтобы предотвратить выполнение GetStringUTFChars() неправильной кодировки UTF-8 дополнительного символа Unicode.
  2. Это делается клиентом Java, кодирующим строку запроса как Base64.
  3. Запрос в кодировке Base64 передается в JNI.
  4. JNI вызывает GetStringUTFChars(), который извлекает строку в кодировке Base64 без выполнения какого-либо кодирования UTF-8.
  5. Затем код JNI декодирует данные Base-64, создавая исходную строку запроса UTF-16 (широкий символ), включая дополнительный символ Unicode.

Таким образом, мы обходим проблему извлечения дополнительных символов Unicode из строки Java. Вместо этого мы преобразуем данные в Base-64 ASCII перед вызовом GetStringUTFChars(), извлекаем символы Base-64 ASCII с помощью GetStringUTFChars() и преобразуем данные Base-64 обратно в широкие символы.

Вот как я это сделал.

1- Char Array для JByteArray.

2- JByteArray to JString.

3- Верните jstring в сторону Java.

Код JNI; (.c) формат

jstring Java_com_x_y_z_methodName(JNIEnv *env, jobject thiz) {
    int size = 16;
    char r[] = {'P', 'K', 'd', 'h', 't', 'X', 'M', 'm', 'r', '1', '8', 'n', '2', 'L', '9', 'K'};
    jbyteArray array = (*env)->NewByteArray(env, size);
    (*env)->SetByteArrayRegion(env, array, 0, size, r);
    jstring strEncode = (*env)->NewStringUTF(env, "UTF-8");
    jclass cls = (*env)->FindClass(env, "java/lang/String");
    jmethodID ctor = (*env)->GetMethodID(env, cls, "<init>", "([BLjava/lang/String;)V");
    jstring object = (jstring) (*env)->NewObject(env, cls, ctor, array, strEncode);

    return object;
}

Java-код;

native String methodName();

Другой подход не работает для меня;

Я тоже пробовал return (*env)->NewStringUTF(env, r) но возвращает некоторые символы, которых нет в массиве char, в конце строки, где с предупреждением JNI ПРЕДУПРЕЖДЕНИЕ: ввод недопустим Модифицирован UTF-8: недопустимый байт продолжения 0x40.

Пример; PKdhtXMmr18n2L9K ؾ- Д.Л.

Редактировать:

Версия C++

jstring clientStringFromStdString(JNIEnv *env,const std::string &str){
//    return env->NewStringUTF(str.c_str());
    jbyteArray array = env->NewByteArray(str.size());
    env->SetByteArrayRegion(array, 0, str.size(), (const jbyte*)str.c_str());
    jstring strEncode = env->NewStringUTF("UTF-8");
    jclass cls = env->FindClass("java/lang/String");
    jmethodID ctor = env->GetMethodID(cls, "<init>", "([BLjava/lang/String;)V");
    jstring object = (jstring) env->NewObject(cls, ctor, array, strEncode);
    return object;
}

У меня была эта проблема, когда я меняю файл Application.mk

Из этой строки:

APP_STL := stlport_static

Для того, чтобы:

APP_STL := gnustl_static

Как только я изменил это снова, это решило проблему.

Строки, которые вы передаете в NewStringUTF(), должны быть действительными Modified UTF-8. Похоже, строка, возвращаемая вашей функцией start_Inauthroot(), находится в какой-то другой кодировке или просто возвращает недопустимую строку. Вам нужно преобразовать строку в UTF-8, прежде чем передать ее функциям JNI. Или вы можете использовать один из конструкторов String с поддержкой charset, чтобы вместо этого создать объект String.

На мой взгляд, это не ошибка.

NewStringUTF Создает новый объект java.lang.String из массива символов в модифицированной кодировке UTF-8.

Модифицированный UTF-8 не является стандартным UTF-8. Смотрите Модифицированный UTF-8

В большинстве случаев строка в кодировке UTF-8 является действительной Modified UTF-8. Потому что модифицированные UTF-8 и UTF-8 довольно похожи. Однако, когда речь идет о строке Unicode, выходящей за рамки базовой многоязычной плоскости, они несовместимы.

Решение: передать байты UTF-8 на уровень Java и новую строку (байты, "UTF-8"), а затем передать jstring в JNI.

Я также боролся с той же проблемой с прошлого дня. Наконец-то выяснили решение через день.. Надеюсь, этот ответ может спасти чей-то день..

Проблема заключалась в том, что я вызывал другую функцию внутри нативной функции, напрямую использовал возвращенную строку, что вызывало сбой в старых версиях Android

Итак, сначала я сохранил строку, возвращенную из другой функции, в переменную, затем использовал ее, и проблема исчезла:D

Приведенный ниже пример может очистить вашу концепцию

//older code with error
//here key_ is the string from java code

const char *key = env->GetStringUTFChars(key_, 0);
const char *keyx = getkey(key).c_str();
return env->NewStringUTF(keyx);

И вот как я решил эту ошибку

//newer code which is working
//here key_ is the string from java code

const char *key = env->GetStringUTFChars(key_, 0);
string k = getkey(key);
const char *keyx = k.c_str();
return env->NewStringUTF(keyx);

Удачного кодирования:D

Для меня решение было поместить контент на постоянный символ *:

const char* string = name_sin.c_str();
jstring utf8 = env_r->NewStringUTF(string);

и функция:

jclass cls_Env = env_r->FindClass(CLASS_ACTIVITY_NAME); 
jmethodID mid = env_r->GetMethodID(cls_Env, "Delegate",
                                 "(Ljava/lang/String;)V");


//todo importante hacerlo asi, si pasas directamente c_str a veces da error de carater no UTF 8
const char* string = name_sin.c_str();
jstring utf8 = env_r->NewStringUTF(string);

env_r->CallVoidMethod(*object_r, mid, utf8);

env_r->DeleteLocalRef(utf8);

c Android ndk работает следующим образом

JNIEXPORT jstring JNICALL
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz,jstring str )
{

    jboolean isCopy;
    const char* szHTML = (*env)->GetStringUTFChars(env, str, &isCopy);
     return (*env)->NewStringUTF(env, szHTML);
}

Это работает для меня в C++

extern "C" JNIEXPORT
jstring Java_com_example_ndktest_MainActivity_TalkToJNI(JNIEnv* env, jobject javaThis, jstring strFromJava)
{
    jboolean isCopy;
    const char* szHTML = env->GetStringUTFChars(strFromJava, &isCopy);

    std::string strMine;
    strMine = szHTML;
    strMine += " --- Hello from the JNI!!";

    env->ReleaseStringUTFChars(strFromJava, szHTML);
    return env->NewStringUTF(strMine.c_str());
}
Другие вопросы по тегам