JNI 异常处理
JNI 异常处理机制与 Java 异常不同。Native 代码中不支持 try-catch,异常必须手动检查和处理。理解 JNI 异常处理对于编写健壮的 Native 代码至关重要。
JNI 异常机制
异常不会自动传播
在 Java 中,异常会自动向上传播:
java
// Java 代码
void method1() {
method2(); // 如果抛出异常,会自动传播
}
void method2() {
throw new Exception("Error"); // 自动传播到 method1
}但在 JNI 中,异常不会自动传播:
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ExceptionDemo_nativeMethod")
fun nativeMethod(env: CPointer<JNIEnvVar>, thiz: jobject) {
val jniEnv = env.pointed.pointed!!
// 调用可能抛出异常的 Java 方法
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(env, clazz, "dangerousMethod", "()V")
// 调用方法,可能抛出异常
jniEnv.CallVoidMethod!!(env, thiz, methodId)
// ❌ 异常不会自动传播!
// 如果 dangerousMethod 抛出异常,程序会继续执行
// 但 JNI 函数可能会失败或返回 null
println("This line will still execute even if exception occurred!")
}检查异常
ExceptionOccurred
检查是否有未处理的异常:
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ExceptionCheck_checkException")
fun checkException(env: CPointer<JNIEnvVar>, thiz: jobject) {
val jniEnv = env.pointed.pointed!!
// 调用可能抛出异常的方法
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(env, clazz, "riskyMethod", "()I")
val result = jniEnv.CallIntMethod!!(env, thiz, methodId)
// 检查是否有异常
val exception: jthrowable? = jniEnv.ExceptionOccurred!!(env)
if (exception != null) {
println("Exception occurred!")
// 必须清除异常才能继续使用 JNI 函数
jniEnv.ExceptionClear!!(env)
// 处理异常...
} else {
println("No exception, result: $result")
}
}ExceptionCheck
更轻量的异常检查(推荐):
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ExceptionCheck_quickCheck")
fun quickCheck(env: CPointer<JNIEnvVar>, thiz: jobject): jint {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(env, clazz, "calculate", "()I")
val result = jniEnv.CallIntMethod!!(env, thiz, methodId)
// ✅ ExceptionCheck 更快,不会创建局部引用
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
jniEnv.ExceptionClear!!(env)
return -1 // 错误码
}
return result
}TIP
ExceptionCheck 比 ExceptionOccurred 更快,因为它只返回 boolean,不创建异常对象的局部引用。
清除异常
ExceptionClear
必须清除异常后才能继续调用 JNI 函数:
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ClearException_process")
fun process(env: CPointer<JNIEnvVar>, thiz: jobject) {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(env, clazz, "step1", "()V")
// 调用可能抛出异常的方法
jniEnv.CallVoidMethod!!(env, thiz, methodId)
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
// ✅ 必须清除异常
jniEnv.ExceptionClear!!(env)
println("Exception cleared, continuing...")
// 现在可以安全调用其他 JNI 函数
val method2Id = jniEnv.GetMethodID!!(env, clazz, "step2", "()V")
jniEnv.CallVoidMethod!!(env, thiz, method2Id)
}
}WARNING
在清除异常前调用大多数 JNI 函数会导致未定义行为。只有少数函数(如 ExceptionOccurred、ExceptionDescribe、ExceptionClear)可以在有异常时调用。
抛出异常
Throw - 抛出现有异常对象
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ThrowException_throwExisting")
fun throwExisting(env: CPointer<JNIEnvVar>, thiz: jobject, exception: jthrowable) {
val jniEnv = env.pointed.pointed!!
// 抛出传入的异常对象
jniEnv.Throw!!(env, exception)
// 函数仍会继续执行!
// 异常会在返回 Java 后抛出
}ThrowNew - 创建并抛出新异常
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ThrowException_throwNew")
fun throwNew(env: CPointer<JNIEnvVar>, thiz: jobject, value: jint): jint {
val jniEnv = env.pointed.pointed!!
if (value < 0) {
// 查找异常类
val exceptionClass = jniEnv.FindClass!!(
env,
"java/lang/IllegalArgumentException"
)
// ✅ 抛出新异常
jniEnv.ThrowNew!!(env, exceptionClass, "Value must be non-negative".cstr.ptr)
// 返回错误值(虽然会被异常覆盖)
return -1
}
return value * 2
}常用异常类
kotlin
@OptIn(ExperimentalForeignApi::class)
object JNIExceptions {
// 抛出 NullPointerException
fun throwNPE(env: CPointer<JNIEnvVar>, message: String) {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.FindClass!!(env, "java/lang/NullPointerException")
jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
}
// 抛出 IllegalArgumentException
fun throwIllegalArgument(env: CPointer<JNIEnvVar>, message: String) {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.FindClass!!(env, "java/lang/IllegalArgumentException")
jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
}
// 抛出 IllegalStateException
fun throwIllegalState(env: CPointer<JNIEnvVar>, message: String) {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.FindClass!!(env, "java/lang/IllegalStateException")
jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
}
// 抛出 RuntimeException
fun throwRuntime(env: CPointer<JNIEnvVar>, message: String) {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.FindClass!!(env, "java/lang/RuntimeException")
jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
}
// 抛出 IOException
fun throwIOException(env: CPointer<JNIEnvVar>, message: String) {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.FindClass!!(env, "java/io/IOException")
jniEnv.ThrowNew!!(env, clazz, message.cstr.ptr)
}
}获取异常信息
读取异常消息
kotlin
@OptIn(ExperimentalForeignApi::class)
fun getExceptionMessage(env: CPointer<JNIEnvVar>): String? {
val jniEnv = env.pointed.pointed!!
// 获取异常对象
val exception = jniEnv.ExceptionOccurred!!(env) ?: return null
// 清除异常(必须在使用 JNI 函数前)
jniEnv.ExceptionClear!!(env)
// 获取 Throwable 类
val throwableClass = jniEnv.FindClass!!(env, "java/lang/Throwable")
// 调用 getMessage() 方法
val getMessageMethod = jniEnv.GetMethodID!!(
env,
throwableClass,
"getMessage",
"()Ljava/lang/String;"
)
val messageObj = jniEnv.CallObjectMethod!!(env, exception, getMessageMethod)
val message = (messageObj as? jstring)?.toKString(env)
// 清理
jniEnv.DeleteLocalRef!!(env, exception)
jniEnv.DeleteLocalRef!!(env, messageObj)
return message
}打印异常堆栈
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ExceptionUtil_printStackTrace")
fun printStackTrace(env: CPointer<JNIEnvVar>, thiz: jobject) {
val jniEnv = env.pointed.pointed!!
// 调用可能抛出异常的方法
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(env, clazz, "dangerousOp", "()V")
jniEnv.CallVoidMethod!!(env, thiz, methodId)
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
// ✅ 打印异常堆栈到标准错误输出
jniEnv.ExceptionDescribe!!(env)
// 清除异常
jniEnv.ExceptionClear!!(env)
}
}异常处理模式
模式1:传播异常到 Java
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Pattern1_propagate")
fun propagate(env: CPointer<JNIEnvVar>, thiz: jobject, input: jstring): jstring? {
val jniEnv = env.pointed.pointed!!
// 调用 Java 方法
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(
env, clazz, "process", "(Ljava/lang/String;)Ljava/lang/String;"
)
val result = jniEnv.CallObjectMethod!!(env, thiz, methodId, input) as? jstring
// ✅ 直接返回,异常会传播到 Java
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
return null // 或返回默认值
}
return result
}模式2:捕获并转换异常
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Pattern2_convert")
fun convert(env: CPointer<JNIEnvVar>, thiz: jobject, value: jint): jint {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(env, clazz, "calculate", "(I)I")
val result = jniEnv.CallIntMethod!!(env, thiz, methodId, value)
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
// 获取原始异常信息
val message = getExceptionMessage(env)
// 清除原异常
jniEnv.ExceptionClear!!(env)
// ✅ 抛出新的异常类型
val newExClass = jniEnv.FindClass!!(env, "java/lang/RuntimeException")
jniEnv.ThrowNew!!(
env,
newExClass,
"Calculation failed: $message".cstr.ptr
)
return -1
}
return result
}模式3:吞掉异常(谨慎使用)
kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_Pattern3_swallow")
fun swallow(env: CPointer<JNIEnvVar>, thiz: jobject): jboolean {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(env, clazz, "tryOperation", "()V")
jniEnv.CallVoidMethod!!(env, thiz, methodId)
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
// 记录日志
println("Operation failed, ignoring...")
// ✅ 清除异常,不向 Java 传播
jniEnv.ExceptionClear!!(env)
return JNI_FALSE // 返回失败标志
}
return JNI_TRUE // 成功
}最佳实践
实践1:总是检查异常
kotlin
@OptIn(ExperimentalForeignApi::class)
// ✅ 好的模式
@CName("Java_com_example_BestPractice_safeCall")
fun safeCall(env: CPointer<JNIEnvVar>, thiz: jobject) {
val jniEnv = env.pointed.pointed!!
// 查找类
val clazz = jniEnv.FindClass!!(env, "com/example/MyClass")
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
println("Class not found")
return
}
// 查找方法
val methodId = jniEnv.GetMethodID!!(env, clazz, "myMethod", "()V")
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
println("Method not found")
return
}
// 调用方法
jniEnv.CallVoidMethod!!(env, thiz, methodId)
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
println("Method call failed")
return
}
}实践2:使用辅助函数
kotlin
@OptIn(ExperimentalForeignApi::class)
// 辅助函数:安全调用 JNI 方法
inline fun <T> safeJNICall(
env: CPointer<JNIEnvVar>,
onError: () -> T,
block: () -> T
): T {
val jniEnv = env.pointed.pointed!!
val result = block()
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
jniEnv.ExceptionClear!!(env)
return onError()
}
return result
}
// 使用示例
@CName("Java_com_example_Helper_useHelper")
fun useHelper(env: CPointer<JNIEnvVar>, thiz: jobject): jint {
val jniEnv = env.pointed.pointed!!
return safeJNICall(env, onError = { -1 }) {
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(env, clazz, "getValue", "()I")
jniEnv.CallIntMethod!!(env, thiz, methodId)
}
}实践3:RAII 风格异常管理
kotlin
@OptIn(ExperimentalForeignApi::class)
class JNIExceptionScope(private val env: CPointer<JNIEnvVar>) {
private val jniEnv = env.pointed.pointed!!
private var hasException = false
fun checkAndClear(): Boolean {
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
hasException = true
jniEnv.ExceptionClear!!(env)
return true
}
return false
}
fun hadException(): Boolean = hasException
}
@CName("Java_com_example_RAII_process")
fun process(env: CPointer<JNIEnvVar>, thiz: jobject): jint {
val scope = JNIExceptionScope(env)
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.GetObjectClass!!(env, thiz)
if (scope.checkAndClear()) return -1
val methodId = jniEnv.GetMethodID!!(env, clazz, "step1", "()I")
if (scope.checkAndClear()) return -1
val result = jniEnv.CallIntMethod!!(env, thiz, methodId)
if (scope.checkAndClear()) return -1
return result
}常见错误
错误1:忘记检查异常
kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误
@CName("Java_com_example_Bad_noCheck")
fun noCheck(env: CPointer<JNIEnvVar>, thiz: jobject) {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.FindClass!!(env, "NonExistentClass")
// clazz 可能为 null,且有异常pending
// 继续使用会崩溃或产生未定义行为
val methodId = jniEnv.GetMethodID!!(env, clazz, "method", "()V")
}错误2:异常未清除就调用 JNI 函数
kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误
@CName("Java_com_example_Bad_noClean")
fun noClear(env: CPointer<JNIEnvVar>, thiz: jobject) {
val jniEnv = env.pointed.pointed!!
val clazz = jniEnv.GetObjectClass!!(env, thiz)
val methodId = jniEnv.GetMethodID!!(env, clazz, "bad", "()V")
jniEnv.CallVoidMethod!!(env, thiz, methodId)
// 假设抛出异常
if (jniEnv.ExceptionCheck!!(env) == JNI_TRUE) {
// ❌ 未清除异常就调用其他 JNI 函数
val method2 = jniEnv.GetMethodID!!(env, clazz, "another", "()V")
// 未定义行为!
}
}错误3:抛出异常后继续执行关键代码
kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误
@CName("Java_com_example_Bad_continueAfterThrow")
fun continueAfterThrow(env: CPointer<JNIEnvVar>, thiz: jobject, ptr: Long) {
val jniEnv = env.pointed.pointed!!
if (ptr == 0L) {
JNIExceptions.throwNPE(env, "Null pointer")
// ❌ 异常抛出后函数仍会继续执行!
// 不应该继续访问无效指针
val data = ptr.toCPointer<ByteVar>()
data!![0] = 0 // 崩溃!
}
}
// ✅ 正确
@CName("Java_com_example_Good_returnAfterThrow")
fun returnAfterThrow(env: CPointer<JNIEnvVar>, thiz: jobject, ptr: Long) {
val jniEnv = env.pointed.pointed!!
if (ptr == 0L) {
JNIExceptions.throwNPE(env, "Null pointer")
return // ✅ 立即返回
}
val data = ptr.toCPointer<ByteVar>()
data!![0] = 0 // 安全
}正确的异常处理是编写健壮 JNI 代码的关键。始终检查异常、及时清除、合理传播或转换异常,能够避免大部分运行时问题。