JNI调用c/c++,env->和(*env)->是有区别的

本文深入解析了在JNI调用C/C++时,env->和(*env)->的使用区别,解释了在C++环境下使用env->的必要性和原因,并提供了两者在实际编程中的应用示例。

JNI调用c/c++,env->和(*env)->是有区别的 



转载自:http://blog.chinaunix.net/uid-20406995-id-1686163.html

如果是c程序,要用 (*env)-> 

如果是C++要用 env->

ps:在linux下如果.c文件中用 “env->” 编译会找不到此结构,必须用“(*env)->”,或者改成.cpp文件,以 c++的方式来编译。


以下是两者的区别:

jni.h中

struct JNINativeInterface_;
struct JNIEnv_;
#ifdef __cplusplus
typedef JNIEnv_ JNIEnv;
#else
typedef const struct JNINativeInterface_ *JNIEnv;
#endif
/*
* We use inlined functions for C++ so that programmers can write:
*
*   env->FindClass("java/lang/String")
*
* in C++ rather than:
*
*    (*env)->FindClass(env, "java/lang/String")
*
* in C.
*/


即C++中使用
env->FindClass("java/lang/String")


C中使用

(*env)->FindClass(env, "java/lang/String")


<think> 我们正在解决一个JNI编程中的常见问题:当使用`env->NewStringUTF("")`将C/C++字符串转换为jstring返回给Java层时,可能会出现崩溃或乱码。根据引用内容,我们可以总结出以下解决方案: 1. Android 4.3及以下版本中,`NewStringUTF`对非标准UTF-8字符(如4字节的UTF-8字符)处理有问题,可能导致崩溃或乱码。 2. 解决方案一:使用Base64编码绕过。将原始字符串转换为Base64编码的字符串,然后在Java层解码。 3. 解决方案二:实现一个安全的字符串转换函数(如`stringTojstring`),但需要注意处理4字节UTF-8字符(使用代理对转换),可参考ART的`GetUtf16FromUtf8`函数。 下面我们将详细说明这两种方法,并提供代码示例。 ### 方法1:Base64编码转换(推荐简单场景) 原理:将C/C++中的原始字符串(可能是任意字节序列)进行Base64编码,然后作为标准UTF-8字符串传递给Java层,在Java层解码得到原始字符串。 **步骤:** 1. 在C++层将原始字符串进行Base64编码(使用一个Base64编码函数)。 2. 使用`env->NewStringUTF`将Base64编码后的字符串转换为jstring。 3. 在Java层,将返回的字符串进行Base64解码。 **C++层代码示例:** ```cpp #include <string> #include <vector> #include <cctype> // Base64编码函数(简单实现,实际中可使用更健壮的实现) std::string base64_encode(const std::string &in) { std::string out; const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; int i = 0, j = 0; unsigned char char_array_3[3]; unsigned char char_array_4[4]; size_t in_len = in.size(); const char* bytes_to_encode = in.data(); while (in_len--) { char_array_3[i++] = *(bytes_to_encode++); if (i == 3) { char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for(i = 0; i <4; i++) out += base64_chars[char_array_4[i]]; i = 0; } } if (i) { for(j = i; j < 3; j++) char_array_3[j] = '\0'; char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); char_array_4[3] = char_array_3[2] & 0x3f; for (j = 0; j < i+1; j++) out += base64_chars[char_array_4[j]]; while(i++ < 3) out += '='; } return out; } // JNI函数示例 extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapp_MainActivity_getStringFromNative(JNIEnv *env, jobject thiz) { // 原始字符串(可能包含非标准UTF-8字符) std::string original_str = "Hello, 世界! \xF0\x9F\x98\x82"; // 包含一个emoji(4字节UTF-8) // 进行Base64编码 std::string encoded_str = base64_encode(original_str); // 转换为jstring return env->NewStringUTF(encoded_str.c_str()); } ``` **Java层代码示例:** ```java import android.util.Base64; public class MainActivity extends AppCompatActivity { static { System.loadLibrary("mynative"); } public native String getStringFromNative(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String base64Str = getStringFromNative(); // 使用Base64解码 String originalStr = new String(Base64.decode(base64Str, Base64.DEFAULT), StandardCharsets.UTF_8); Log.d("JNI", "Received: " + originalStr); } } ``` **注意:** 此方法会增加编码解码的开销,但能保证在各种Android版本上安全运行。 ### 方法2:实现安全的字符串转换函数(高效但复杂) 参考ART虚拟机中的`GetUtf16FromUtf8`函数,处理4字节UTF-8字符(即代理对转换)。我们可以自己实现一个转换函数,将UTF-8字符串转换为UTF-16字符串,然后使用`env->NewString`创建jstring。 **步骤:** 1. 将UTF-8字符串转换为UTF-16字符串(包括处理4字节字符)。 2. 使用`env->NewString`函数创建jstring。 由于实现一个完整的UTF-8到UTF-16的转换函数较为复杂,这里我们提供一个简化版本(仅处理基本多文种平面辅助平面): ```cpp #include <jni.h> #include <string> #include <vector> // 将UTF-8字符串转换为UTF-16字符串(支持4字节字符) std::u16string utf8_to_utf16(const std::string &utf8) { std::u16string utf16; for (size_t i = 0; i < utf8.size();) { uint32_t ch = 0; unsigned char c = utf8[i++]; if (c < 0x80) { ch = c; } else if ((c & 0xE0) == 0xC0) { ch = (c & 0x1F) << 6; ch |= (utf8[i++] & 0x3F); } else if ((c & 0xF0) == 0xE0) { ch = (c & 0x0F) << 12; ch |= (utf8[i++] & 0x3F) << 6; ch |= (utf8[i++] & 0x3F); } else if ((c & 0xF8) == 0xF0) { // 4字节字符 ch = (c & 0x07) << 18; ch |= (utf8[i++] & 0x3F) << 12; ch |= (utf8[i++] & 0x3F) << 6; ch |= (utf8[i++] & 0x3F); // 转换为代理对(surrogate pair) if (ch > 0xFFFF) { uint32_t high = ((ch - 0x10000) >> 10) + 0xD800; uint32_t low = (ch & 0x3FF) + 0xDC00; utf16 += static_cast<char16_t>(high); utf16 += static_cast<char16_t>(low); continue; } } else { // 非法字符,跳过或用占位符 ch = '?'; } utf16 += static_cast<char16_t>(ch); } return utf16; } // JNI函数示例 extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapp_MainActivity_getStringFromNative(JNIEnv *env, jobject thiz) { std::string original_str = "Hello, 世界! \xF0\x9F\x98\x82"; // 包含一个emoji(4字节UTF-8) // 转换为UTF-16 std::u16string utf16_str = utf8_to_utf16(original_str); // 创建jstring return env->NewString(reinterpret_cast<const jchar*>(utf16_str.data()), utf16_str.length()); } ``` **注意:** - 此方法直接创建UTF-16字符串,避免了`NewStringUTF`的问题,且效率较高。 - 但转换函数需要正确处理所有UTF-8序列,上述示例仅作演示,生产环境建议使用成熟库(如ICU)或系统API(Android版本>=21可使用`std::codecvt_utf8_utf16`,但需注意NDK支持)。 ### 总结 | 解决方案 | 适用场景 | 优点 | 缺点 | |-----------------|----------------------------------|-----------------------------|------------------------------| | Base64编码转换 | 简单场景,兼容所有Android版本 | 实现简单,安全可靠 | 有编码解码开销,字符串变长 | | UTF8转UTF16 | 追求效率,且能保证正确转换 | 高效,直接使用JNI接口 | 实现复杂,需处理代理对 | ### 异常处理建议 当`NewStringUTF`遇到非法UTF-8序列时,会抛出`java.lang.IllegalArgumentException`。为避免崩溃,可先检查字符串: ```cpp if (env->ExceptionCheck()) { env->ExceptionClear(); // 处理异常,例如返回空字符串或错误信息 return env->NewStringUTF("(error)"); } ``` ### 相关问题 1. 如何判断Android系统版本以选择不同的字符串转换策略? 2. 在JNI中除了字符串转换,还有哪些常见的数据类型转换问题? 3. 如何利用NDK中的`<codecvt>`头文件进行字符串编码转换? 4. JNI中如何捕获并处理Java异常? 5. 使用Base64编码转换时,如何选择Base64编码模式(如DEFAULT, NO_WRAP等)? [^1]: 针对Android 4.3及以下版本,使用`NewStringUTF`转换包含4字节UTF-8字符的字符串会导致崩溃或乱码。 [^2]: JNIEnv在不同语言(CC++)中的调用方式不同,C++中直接使用`env->`,而C中则使用`(*env)->`。 [^3]: 使用Base64编码转换是一种解决`NewStringUTF`崩溃问题的有效方法。 [^4]: 在JNI中,可以通过将字节数组传递给Java层,然后调用Java方法将字节数组转换为字符串,从而避免直接使用`NewStringUTF`。 [^5]: 在JNI中查找Java类时,如果类名不正确或类加载器不同,`FindClass`可能返回null。 </think>### 安卓JNI中`env->NewStringUTF("")`报错的解决方案 当在JNI中使用`env->NewStringUTF()`返回jstring给Java层时,常见报错包括**应用崩溃****乱码****空指针异常**。根本原因是Android系统对UTF-8字符串的处理差异,尤其在低版本(如4.3及以下)中无法正确处理4字节UTF-8字符(如emoji)。以下是系统化的解决方案: --- ### 一、核心解决方案 #### 1. Base64编解码方案(推荐,兼容所有版本) ```cpp // C++层:编码为Base64后返回 JNIEXPORT jstring JNICALL getString(JNIEnv* env) { const char* c_str = "Hello, 世界! \xF0\x9F\x98\x82"; // 含4字节emoji std::string base64_str = base64_encode(c_str); // 实现Base64编码 return env->NewStringUTF(base64_str.c_str()); } // Java层:解码Base64 String base64Str = nativeGetString(); byte[] decoded = Base64.decode(base64Str, Base64.DEFAULT); String result = new String(decoded, StandardCharsets.UTF_8); ``` **优点**:100%兼容所有Android版本 **缺点**:增加编解码开销[^3] #### 2. UTF-8转UTF-16方案(高效但复杂) ```cpp // 实现安全的UTF-8转UTF-16(处理4字节字符) std::u16string utf8_to_utf16(const char* utf8) { std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter; return converter.from_bytes(utf8); } JNIEXPORT jstring JNICALL getString(JNIEnv* env) { const char* utf8_str = "Hello, 世界! \xF0\x9F\x98\x82"; std::u16string utf16_str = utf8_to_utf16(utf8_str); return env->NewString( reinterpret_cast<const jchar*>(utf16_str.data()), utf16_str.length() ); } ``` **适用条件**- Android 5.0+(需NDK r21+) - 添加`#include <codecvt>``-std=c++11`编译选项[^1] --- ### 二、特殊场景处理 #### 1. 低版本Android(4.x)代理对转换 参考ART虚拟机的`GetUtf16FromUtf8`实现: ```cpp void utf8_to_utf16(const uint8_t* src, jchar* dst) { while (*src) { uint32_t unicode = 0; // 处理4字节字符(代理对转换) if ((*src & 0xF8) == 0xF0) { unicode = (*src++ & 0x07) << 18; unicode |= (*src++ & 0x3F) << 12; unicode |= (*src++ & 0x3F) << 6; unicode |= (*src++ & 0x3F); *dst++ = (jchar)((unicode - 0x10000) / 0x400 + 0xD800); *dst++ = (jchar)((unicode - 0x10000) % 0x400 + 0xDC00); } // 处理1-3字节字符... } } ``` #### 2. 字节数组中转方案 ```cpp // C++层:返回字节数组 JNIEXPORT jbyteArray JNICALL getBytes(JNIEnv* env) { const char* data = "原始二进制数据"; jbyteArray array = env->NewByteArray(strlen(data)); env->SetByteArrayRegion(array, 0, strlen(data), (jbyte*)data); return array; } // Java层:转换字节数组为字符串 byte[] bytes = nativeGetBytes(); String result = new String(bytes, StandardCharsets.UTF_8); ``` --- ### 三、错误处理最佳实践 ```cpp JNIEXPORT jstring JNICALL safeGetString(JNIEnv* env) { try { const char* str = generateString(); if (str == nullptr) { return env->NewStringUTF(""); // 返回空字符串而非NULL } // 检查UTF-8有效性 if (!is_valid_utf8(str)) { throw std::runtime_error("Invalid UTF-8"); } return env->NewStringUTF(str); } catch (...) { env->ExceptionClear(); // 清除可能的Pending异常 return env->NewStringUTF("[ERROR]"); } } ``` --- ### 四、版本兼容方案选择 | Android版本 | 推荐方案 | 注意事项 | |----------------|-----------------------------|----------------------------| | < 5.0 (API 21) | Base64编解码 | 需处理编解码性能问题 | | ≥ 5.0 (API 21) | UTF-8转UTF-16 | 需要C++11支持 | | 全版本通用 | 字节数组中转 | 增加JNI调用次数 | --- ### 关键错误排查点 1. **检查字符串终止符** C字符串必须以`\0`结尾,否则导致内存越界 2. **验证UTF-8有效性** 使用`is_valid_utf8()`函数检测非法序列 3. **异常处理** 调用`env->ExceptionCheck()`检测Java层异常 4. **内存管理** 避免在返回jstring后立即释放原始C字符串 > ⚠️ **重要提示**:在Android 4.3及以下版本中,直接使用`NewStringUTF()`传递含4字节UTF-8字符的字符串会导致JVM崩溃,必须使用代理对转换或Base64方案[^1]。 --- ### 相关问题 1. 如何检测Android系统版本以动态选择字符串转换策略? 2. JNI中处理中文字符有哪些特殊注意事项? 3. 使用Base64编码转换对性能的影响如何量化? 4. 在JNI层如何正确处理Java字符串的编码转换? 5. 使用`NewString``NewStringUTF`在内存管理上有何差异? [^1]: Android 4.3及以下版本需要特殊处理4字节UTF-8字符 [^2]: JNIEnv在CC++中的调用方式差异 [^3]: Base64编码方案可绕过UTF-8字符处理问题 [^4]: 通过字节数组中转可避免直接字符串转换 [^5]: 正确使用FindClass是JNI调用的基础
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值