通过JNI将C ++字符串发送到Java

时间:2012-07-23 22:26:29

标签: java android c++ android-ndk java-native-interface

我正在开发构建Android应用程序的项目的C ++方面。我需要传递给Java应用程序(通过JNI)的一些信息(通过字符串和字符串数组)。我之前从未这样做过,反向工作的人没有C ++经验,并承认他们无法真正帮助。

我找到了以下代码(来自here

 #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);  
    }  

但这对我没有意义。大多数情况下,我不确定我应该如何将其纳入程序的C ++方面,而我却无法确切了解其工作原理。代码是否在执行return(ret);行时发送消息?或者在for循环中执行行?

理想情况下,我希望字符串/字符串数组在行中“实时”发送,而不是在函数末尾发送,这样我就不必合并新函数。

我发现的代码是否适用于我想要的(通过一些改编)?我正在寻找甚至可能吗?如果是这样,我该怎么做?

修改/更新: 花了一天时间研究JNI和术语,我认为我未能正确地传达我在这里想要实现的内容以及对@jogabonito的答案/回复的评论。

话虽如此。我正在处理的代码是一个IM客户端,需要将消息和状态更新推送到Android Java应用程序(通过JNI),以便Android应用程序不会轮询更新。我已经设法学习如何设置java代码的函数来调用requrest信息。但是,我不知道如何将新消息或状态信息(jabber节字符串)推送到java代码中。所有关于如何执行此操作的代码(请参阅下面的示例)似乎需要从java代码(env,class,methodid等)获取信息。

当我不是调用函数的java代码,而是我的c ++代码时,这对我来说是不可能的。任何解释/帮助将非常感激。

#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's a tutorial :)
    printf("%s\n", str);

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

4 个答案:

答案 0 :(得分:13)

根据@Sam的要求,这是一种避免使用修改过的UTF-8的方法,因为我们不知道这样做是安全的。

NewStringUTF根据修改后的UTF-8编码创建一个字符串。 将它与用户数据一起使用是不正确的 - 它不太可能用修改后的UTF-8编码。我们可以希望数据中的字符被限制以保持兼容。相反,我们可以正确地转换它。

JNI在其API中使用修改后的UTF-8字符串。我们可以使用我们知道兼容的字符串,特别是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;
}

答案 1 :(得分:3)

在您共享的函数中,您在c ++代码中创建了一个NewObjectArray的对象数组。然后在for循环中,您将使用NewStringUTF创建一个字符串,并使用SetObjectArrayElement将其存储在数组中的索引处。到目前为止,您的对象数组仅为您的c ++代码所知,而不是您的java代码。只有当你返回它时,你的java应用程序才能访问它       我可以想到几种方法将字符串从c ++发送到java,尽管它可能不是你想要的。

  1. 将String数组传递给本机函数。在本机代码中,您可以使用GetObjectArrayElement访问每个元素,并使用SetObjectArrayElement进行更新。这可能毫无意义,因为你最终不得不调用一个我不想要的函数。

  2. 如果您已经在java代码中将字符串定义为字段,则可以使用GetFieldIDGetObjectField从本机访问该字段,然后使用{{1}更新它}}。我不知道你将如何发信号通知你的java代码该字段已被更新(如果你需要)

  3. 修改
    您编写的更新函数旨在从java层调用。这个的线索是函数SetObjectField的名称。要从本机上下文调用java代码,您需要从java引用Java_the_package_MainActivity_getJniStringenv。请查看How do I load my own Java class in C on Android?获取此方法的方法。您可能还必须查找如何在JNI中使用全局引用

答案 2 :(得分:1)

通常使用JNI,调用从JVM进入C代码。通常的范例是:

  1. Java程序员使用声明为class的几个方法创建Java native(无实现)
  2. Java程序员使用class
  3. 编译javac
  4. Java程序员针对编译的javah文件运行.class,这将生成.h头文件
  5. C程序员#include新的头文件并实现接口
  6. 我看到的反向执行此操作的唯一示例(C代码启动与Java的联系)涉及让C代码实际创建JVM。

    要回答关于代码示例的问题,正在创建的Java字符串将在代码执行结束时返回return语句,逻辑上,这是将该执行线程的程序流返回给JVM的时间。 / p>

答案 3 :(得分:0)

您可以将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));
}