Подтвердить что ты не робот

Отправьте строку С++ на Java через JNI

Я работаю на стороне С++ проекта, который создает приложение для Android. Существует некоторая информация (через строки и строковые массивы), которые мне нужно передать в приложение Java (через JNI). Я никогда не делал этого раньше, и люди, работающие в обратном направлении, не имеют опыта работы с С++ и признают, что они действительно не могут помочь.

Я нашел следующий код (от здесь)

 #include <jni.h>  
    #include "ArrayHandler.h"  

    JNIEXPORT jobjectArray JNICALL Java_ArrayHandler_returnArray (JNIEnv *env, jobject jobj){        
      jobjectArray ret;  
      int i;  
      char *message[5]= {"first","second","third","fourth","fifth"};  
      ret= (jobjectArray)env->NewObjectArray(5,env->FindClass("java/lang/String"),env->NewStringUTF(""));  

      for(i=0;i<5;i++) {  
        env->SetObjectArrayElement(ret,i,env->NewStringUTF(message[i]));  
      }  
      return(ret);  
    }  

Но для меня это не имеет смысла. В основном, я не уверен, как я должен включить это в сторону С++ программы, и я не понимаю, как это работает. Является ли код отправлением сообщения после выполнения строки return(ret);? Или во время выполнения строки внутри цикла?

В идеале я хотел бы, чтобы массив строк/строк был отправлен "вживую" в строке, а не в конце функции, так что мне не нужно включать новую функцию.

Будет ли код, который я нашел, работать для того, что я хочу (с некоторой адаптацией)? Является ли то, что я ищу, даже возможно? Если да, то как я могу это сделать?

EDIT/UPDATE: Проведя день, изучая JNI и терминологию, я думаю, что мне не удалось правильно сообщить, что я ищу, чтобы достичь как здесь, так и в качестве комментария к ответу/ответу @jogabonito.

Это сказано. Код, над которым я работаю, предназначен для IM-клиента, который должен будет передавать обновления сообщений и присутствия в приложение Java java (через JNI), чтобы приложение Android не проводило опрос на наличие обновлений. Мне удалось узнать, как настроить функции для java-кода для вызова более подробной информации. Тем не менее, я не знаю, как направить новую информацию о сообщениях или присутствии (строки jabber stanza) в код Java, когда она приходит. Весь код, который я видел, как это сделать (см. Ниже, например), кажется, требуется получить информацию из java-кода (env, class, methodid и т.д.).

Мне не кажется, что это возможно, если это не код Java, вызывающий функцию, а мой код на С++. Любое объяснение/помощь будет очень оценено.

#include <string.h>
#include <stdio.h>
#include <jni.h>

jstring Java_the_package_MainActivity_getJniString( JNIEnv* env, jobject obj){

    jstring jstr = (*env)->NewStringUTF(env, "This comes from jni.");
    jclass clazz = (*env)->FindClass(env, "com/inceptix/android/t3d/MainActivity");
    jmethodID messageMe = (*env)->GetMethodID(env, clazz, "messageMe", "(Ljava/lang/String;)Ljava/lang/String;");
    jobject result = (*env)->CallObjectMethod(env, obj, messageMe, jstr);

    const char* str = (*env)->GetStringUTFChars(env,(jstring) result, NULL); // should be released but what a heck, it a tutorial :)
    printf("%s\n", str);

    return (*env)->NewStringUTF(env, str);
}
4b9b3361

Ответ 1

В используемой вами функции в коде С++ вы создаете массив объектов с NewObjectArray. Затем в вашем цикле for вы создаете строку с NewStringUTF и сохраняете ее по индексу в своем массиве с помощью SetObjectArrayElement. До сих пор ваш массив объектов известен только вашему коду С++, а не вашему java-коду. Только когда вы вернетесь, ваше приложение java получит доступ к нему.
    Я могу придумать пару способов отправить строку в java из С++, хотя это может быть не совсем то, что вы намеревались.

  • Передайте массив String в вашу собственную функцию. В вашем собственном коде вы можете получить доступ к каждому элементу с помощью GetObjectArrayElement и обновить его с помощью SetObjectArrayElement. Это, вероятно, будет бессмысленным, так как вы в конечном итоге должны вызвать функцию, которую, как я полагаю, вы не хотите.

  • Если у вас уже есть строка, определенная как поле в вашем java-коде, от вашей нации получите доступ к ней с помощью GetFieldID и GetObjectField, и вы можете обновить ее с помощью SetObjectField. Я не знаю, как вы будете сигнализировать ваш Java-код, что поле было обновлено, хотя (если вам это нужно)

ИЗМЕНИТЬ
Обновленная функция, которую вы написали, предназначена для вызова из уровня java. Подсказка для этого - это имя функции Java_the_package_MainActivity_getJniString. Чтобы вызвать java-код из собственного контекста, вам понадобятся ссылки на env и obj из java. Посмотрите Как загрузить мой собственный Java-класс в C на Android? для получения такого подхода. Вам также, вероятно, придется искать способы использования глобальных ссылок в JNI

Ответ 2

По запросу @Sam, это метод, который позволяет избежать использования измененного UTF-8, потому что мы не знаем, что это безопасно.

NewStringUTF создает строку из своей модифицированной кодировки UTF-8. Неправильно использовать его с пользовательскими данными - вряд ли он будет закодирован с измененным UTF-8. Мы можем просто надеяться, что символы в данных ограничены, чтобы поддерживать их совместимость. Вместо этого мы можем его правильно преобразовать.

JNI использует модифицированные строки UTF-8 во всем своем API. Мы можем использовать строки, которые, как мы знаем, совместимы, особенно литералы для Java-идентификаторов (кроме не всех символов валюты).

Ниже приведены две встроенные методы реализации. Второй вариант лучше всего.

Для этого нативного метода:

private static native String getJniString();

Вот реализация:

JNIEXPORT jstring JNICALL 
Java_the_Package_MainActivity_getJniString(JNIEnv *env, jclass)
{   
    std::string message = "Would you prefer €20 once "
                          "or ₹10 every day for a year?";

    int byteCount = message.length();
    jbyte* pNativeMessage = reinterpret_cast<const jbyte*>(message.c_str());
    jbyteArray bytes = env->NewByteArray(byteCount);
    env->SetByteArrayRegion(bytes, 0, byteCount, pNativeMessage);

    // find the Charset.forName method:
    //   javap -s java.nio.charset.Charset | egrep -A2 "forName"
    jclass charsetClass = env->FindClass("java/nio/charset/Charset");
    jmethodID forName = env->GetStaticMethodID(
      charsetClass, "forName", "(Ljava/lang/String;)Ljava/nio/charset/Charset;");
    jstring utf8 = env->NewStringUTF("UTF-8");
    jobject charset = env->CallStaticObjectMethod(charsetClass, forName, utf8);

    // find a String constructor that takes a Charset:
    //   javap -s java.lang.String | egrep -A2 "String\(.*charset"
    jclass stringClass = env->FindClass("java/lang/String");
    jmethodID ctor = env->GetMethodID(
       stringClass, "<init>", "([BLjava/nio/charset/Charset;)V");

    jstring jMessage = reinterpret_cast<jstring>(
      env->NewObject(stringClass, ctor, bytes, charset));

    return jMessage;
}

JNI неловко. поэтому, если мы сможем перенести знание о том, что родная строка является "UTF-8" на стороне Java, мы можем сделать это:

private static String getJniString2()
{
    return new String(getJniStringBytes(), Charset.forName("UTF-8"));
}
private static native byte[] getJniStringBytes();

И гораздо более простая реализация:

JNIEXPORT jbyteArray JNICALL Java_the_Package_MainActivity_getJniStringBytes(JNIEnv *env, jclass)
{   
    std::string message = "Would you prefer €20 once "
                          "or ₹10 every day for a year?";

    int byteCount = message.length();
    jbyte* pNativeMessage = reinterpret_cast<const jbyte*>(message.c_str());
    jbyteArray bytes = env->NewByteArray(byteCount);
    env->SetByteArrayRegion(bytes, 0, byteCount, pNativeMessage);

    return bytes;
}

Ответ 3

Обычно с JNI вызовы переходят из JVM в код C. Нормальная парадигма была бы следующей:

  • Java-программисты создают Java class с несколькими методами, объявленными как native (без реализации)
  • Java-программисты скомпилируют class с помощью javac
  • Java-программисты запускают javah по скомпилированному файлу .class, это создает заголовочный файл .h
  • C programmer #include новый заголовочный файл и реализовать интерфейс

Единственные примеры, которые я видел для этого в обратном направлении (C-код, инициирующий контакт с Java), подразумевают, что C-код фактически создает JVM.

Чтобы ответить на вопрос о образце кода, создаваемые строки Java возвращаются с помощью оператора return в конце выполнения кода, логически это происходит, когда поток программы для этого потока выполнения возвращается обратно в JVM.

Ответ 4

Вы можете преобразовать c-строку в jstring и вернуть ее. Пример будет выглядеть примерно так:

JNIEXPORT jstring JNICALL Java_Class_Method(jstring data) 
{

    // jstring to char *
    const char *cStr = (*env)->GetStringUTFChars(env, data, NULL);

    // convert char * to jstring and return it
    return ((*env)->NewStringUTF(env, cStr));
}