Skip to content

JNI 数据类型映射

源:JNI Types and Data Structures

深入理解 JNI 数据类型映射是编写高质量 Native 代码的基础。本文详细讲解 Java/Kotlin 类型如何在 JNI 层表示,以及如何在 Kotlin/Native 中正确使用这些类型。

基本数据类型

完整类型映射表

JNI 为 Java 的基本类型提供了对应的 C 类型定义:

Java/Kotlin 类型JNI 类型C/C++ 实际类型字节数说明
booleanjbooleanunsigned char10 或 1
bytejbytesigned char1-128 到 127
charjcharunsigned short2UTF-16 字符
shortjshortshort2-32,768 到 32,767
intjintint / long4-2³¹ 到 2³¹-1
longjlonglong long8-2⁶³ 到 2⁶³-1
floatjfloatfloat4IEEE 754 单精度
doublejdoubledouble8IEEE 754 双精度

Kotlin/Native 中的使用

kotlin
@OptIn(ExperimentalForeignApi::class)
import kotlinx.cinterop.*
import platform.android.*

@CName("Java_com_example_DataTypes_processPrimitives")
fun processPrimitives(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    boolVal: jboolean,    // Kotlin 中是 UByte
    byteVal: jbyte,       // Kotlin 中是 Byte
    charVal: jchar,       // Kotlin 中是 UShort
    shortVal: jshort,     // Kotlin 中是 Short
    intVal: jint,         // Kotlin 中是 Int
    longVal: jlong,       // Kotlin 中是 Long
    floatVal: jfloat,     // Kotlin 中是 Float
    doubleVal: jdouble    // Kotlin 中是 Double
): jint {
    // 类型可以直接在 Kotlin 中使用
    val sum = intVal + longVal.toInt()
    val result = (sum * floatVal).toInt()
    
    // boolean 需要特殊处理
    val isTrue = boolVal.toInt() != 0
    
    return result
}

boolean 特殊处理

JNI 的 jboolean 不保证只有 0 和 1 两个值:

kotlin
@OptIn(ExperimentalForeignApi::class)
// ❌ 错误:直接比较可能出问题
fun checkBoolean(value: jboolean): Boolean {
    return value == 1.toUByte()  // 不安全
}

// ✅ 正确:使用非零判断
fun checkBoolean(value: jboolean): Boolean {
    return value.toInt() != 0
}

// ✅ 或使用 JNI 常量
fun checkBoolean(value: jboolean): Boolean {
    return value == JNI_TRUE
}

引用类型

对象引用层次

JNI 引用类型形成继承关系:

jobject (基类)
├── jclass (Class 对象)
├── jstring (String 对象)
├── jarray (数组基类)
│   ├── jobjectArray (对象数组)
│   ├── jbooleanArray
│   ├── jbyteArray
│   ├── jcharArray
│   ├── jshortArray
│   ├── jintArray
│   ├── jlongArray
│   ├── jfloatArray
│   └── jdoubleArray
└── jthrowable (异常对象)

jobject - 通用对象引用

所有 Java 对象在 JNI 中都是 jobject 类型:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ObjectHandler_processObject")
fun processObject(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    obj: jobject  // 任意 Java 对象
): jobject? {
    val jniEnv = env.pointed.pointed!!
    
    // 检查是否为 null
    if (jniEnv.IsSameObject!!(env, obj, null) == JNI_TRUE) {
        return null
    }
    
    // 获取对象的类
    val clazz: jclass? = jniEnv.GetObjectClass!!(env, obj)
    
    // 获取类名
    val classClass = jniEnv.FindClass!!(env, "java/lang/Class")
    val getNameMethod = jniEnv.GetMethodID!!(
        env, classClass, "getName", "()Ljava/lang/String;"
    )
    val className = jniEnv.CallObjectMethod!!(env, clazz, getNameMethod) as jstring
    
    // 打印类名
    val nameStr = className.toKString(env)
    println("Object class: $nameStr")
    
    return obj
}

jclass - Class 对象引用

jclass 用于表示 Java Class 对象:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ClassUtil_getClassInfo")
fun getClassInfo(env: CPointer<JNIEnvVar>, thiz: jobject, clazz: jclass): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    // 获取类名
    val getNameMethod = jniEnv.GetMethodID!!(
        env, 
        jniEnv.FindClass!!(env, "java/lang/Class"),
        "getName",
        "()Ljava/lang/String;"
    )
    
    val className = jniEnv.CallObjectMethod!!(env, clazz, getNameMethod) as jstring
    return className
}

// 通过名称查找类
@CName("Java_com_example_ClassUtil_findClass")
fun findClass(env: CPointer<JNIEnvVar>, thiz: jobject, name: jstring): jclass? {
    val jniEnv = env.pointed.pointed!!
    val className = name.toKString(env)
    
    // 注意:类名使用 / 分隔,如 "java/lang/String"
    val normalizedName = className.replace('.', '/')
    return jniEnv.FindClass!!(env, normalizedName.cstr.ptr)
}

jstring - 字符串引用

jstring 是特殊的对象引用,表示 Java String:

kotlin
@OptIn(ExperimentalForeignApi::class)
// 扩展函数:jstring → Kotlin String
fun jstring.toKString(env: CPointer<JNIEnvVar>): String {
    val jniEnv = env.pointed.pointed!!
    
    // 获取 UTF-8 字符串
    val isCopy = nativeHeap.alloc<jbooleanVar>()
    val cString = jniEnv.GetStringUTFChars!!(env, this, isCopy.ptr)
    
    val result = cString?.toKString() ?: ""
    
    // 释放字符串
    jniEnv.ReleaseStringUTFChars!!(env, this, cString)
    nativeHeap.free(isCopy)
    
    return result
}

// 扩展函数:Kotlin String → jstring
fun String.toJString(env: CPointer<JNIEnvVar>): jstring? {
    val jniEnv = env.pointed.pointed!!
    return jniEnv.NewStringUTF!!(env, this.cstr.ptr)
}

// 处理大字符串(避免 UTF-8 转换开销)
@CName("Java_com_example_StringUtil_processLargeString")
fun processLargeString(env: CPointer<JNIEnvVar>, thiz: jobject, str: jstring): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    // 获取字符串长度
    val length = jniEnv.GetStringLength!!(env, str)
    
    // 直接访问 Unicode 字符(UTF-16)
    val isCopy = nativeHeap.alloc<jbooleanVar>()
    val chars = jniEnv.GetStringChars!!(env, str, isCopy.ptr)
    
    // 处理字符...
    // chars 是 CPointer<jcharVar>,指向 UTF-16 字符数组
    
    // 释放字符串
    jniEnv.ReleaseStringChars!!(env, str, chars)
    nativeHeap.free(isCopy)
    
    return str
}

数组类型

基本类型数组

每种基本类型都有对应的数组类型:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ArrayHandler_processIntArray")
fun processIntArray(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    array: jintArray  // int[] 数组
): jint {
    val jniEnv = env.pointed.pointed!!
    
    // 获取数组长度
    val length = jniEnv.GetArrayLength!!(env, array)
    
    // 方法1:获取整个数组的副本
    val isCopy = nativeHeap.alloc<jbooleanVar>()
    val elements = jniEnv.GetIntArrayElements!!(env, array, isCopy.ptr)
    
    var sum: jint = 0
    for (i in 0 until length) {
        sum += elements!![i]
    }
    
    // JNI_COMMIT: 提交更改但不释放
    // JNI_ABORT: 不提交更改,直接释放
    // 0: 提交更改并释放
    jniEnv.ReleaseIntArrayElements!!(env, array, elements, JNI_ABORT)
    nativeHeap.free(isCopy)
    
    return sum
}

// 方法2:批量访问(推荐用于大数组)
@CName("Java_com_example_ArrayHandler_processLargeArray")
fun processLargeArray(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    array: jintArray
): jint {
    val jniEnv = env.pointed.pointed!!
    val length = jniEnv.GetArrayLength!!(env, array)
    
    var sum: jint = 0
    val BUFFER_SIZE = 1024
    
    memScoped {
        val buffer = allocArray<jintVar>(BUFFER_SIZE)
        
        var offset = 0
        while (offset < length) {
            val count = minOf(BUFFER_SIZE, length - offset)
            
            // 获取部分元素
            jniEnv.GetIntArrayRegion!!(env, array, offset, count, buffer)
            
            for (i in 0 until count) {
                sum += buffer[i]
            }
            
            offset += count
        }
    }
    
    return sum
}

创建数组

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ArrayFactory_createIntArray")
fun createIntArray(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    size: jint
): jintArray? {
    val jniEnv = env.pointed.pointed!!
    
    // 创建数组
    val array = jniEnv.NewIntArray!!(env, size)
    
    // 填充数据
    memScoped {
        val buffer = allocArray<jintVar>(size)
        for (i in 0 until size) {
            buffer[i] = i * i
        }
        
        jniEnv.SetIntArrayRegion!!(env, array, 0, size, buffer)
    }
    
    return array
}

对象数组

处理对象数组需要逐个元素操作:

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ObjectArrayHandler_processStringArray")
fun processStringArray(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    array: jobjectArray  // String[] 数组
): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    val length = jniEnv.GetArrayLength!!(env, array)
    val result = StringBuilder()
    
    for (i in 0 until length) {
        // 获取数组元素
        val element = jniEnv.GetObjectArrayElement!!(env, array, i) as? jstring
        
        if (element != null) {
            val str = element.toKString(env)
            result.append(str)
            if (i < length - 1) {
                result.append(", ")
            }
        }
    }
    
    return result.toString().toJString(env)
}

// 创建对象数组
@CName("Java_com_example_ObjectArrayFactory_createStringArray")
fun createStringArray(
    env: CPointer<JNIEnvVar>,
    thiz: jobject,
    size: jint
): jobjectArray? {
    val jniEnv = env.pointed.pointed!!
    
    // 查找 String 类
    val stringClass = jniEnv.FindClass!!(env, "java/lang/String")
    
    // 创建初始值(可以为 null)
    val initialElement = "".toJString(env)
    
    // 创建数组
    val array = jniEnv.NewObjectArray!!(env, size, stringClass, initialElement)
    
    // 设置数组元素
    for (i in 0 until size) {
        val element = "Item $i".toJString(env)
        jniEnv.SetObjectArrayElement!!(env, array, i, element)
    }
    
    return array
}

类型转换与检查

类型检查

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_TypeChecker_checkType")
fun checkType(env: CPointer<JNIEnvVar>, thiz: jobject, obj: jobject): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    // 检查是否为 String
    val stringClass = jniEnv.FindClass!!(env, "java/lang/String")
    val isString = jniEnv.IsInstanceOf!!(env, obj, stringClass) == JNI_TRUE
    
    // 检查是否为 Integer
    val integerClass = jniEnv.FindClass!!(env, "java/lang/Integer")
    val isInteger = jniEnv.IsInstanceOf!!(env, obj, integerClass) == JNI_TRUE
    
    val result = when {
        isString -> "String"
        isInteger -> "Integer"
        else -> "Unknown"
    }
    
    return result.toJString(env)
}

类型转换

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_TypeConverter_objectToString")
fun objectToString(env: CPointer<JNIEnvVar>, thiz: jobject, obj: jobject): jstring? {
    val jniEnv = env.pointed.pointed!!
    
    // 调用 toString() 方法
    val objectClass = jniEnv.FindClass!!(env, "java/lang/Object")
    val toStringMethod = jniEnv.GetMethodID!!(
        env, objectClass, "toString", "()Ljava/lang/String;"
    )
    
    val result = jniEnv.CallObjectMethod!!(env, obj, toStringMethod) as jstring
    return result
}

特殊类型

jthrowable - 异常对象

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_ExceptionHandler_catchException")
fun catchException(env: CPointer<JNIEnvVar>, thiz: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    // ... 一些可能抛出异常的代码 ...
    
    // 检查是否有异常
    val exception: jthrowable? = jniEnv.ExceptionOccurred!!(env)
    
    if (exception != null) {
        // 清除异常(必须在处理前清除)
        jniEnv.ExceptionClear!!(env)
        
        // 获取异常信息
        val throwableClass = jniEnv.FindClass!!(env, "java/lang/Throwable")
        val getMessageMethod = jniEnv.GetMethodID!!(
            env, throwableClass, "getMessage", "()Ljava/lang/String;"
        )
        
        val message = jniEnv.CallObjectMethod!!(env, exception, getMessageMethod) as jstring?
        val messageStr = message?.toKString(env) ?: "Unknown error"
        
        println("Caught exception: $messageStr")
    }
}

void 类型

Java 的 void 在 JNI 中没有对应类型,但在方法签名中使用 V

kotlin
@OptIn(ExperimentalForeignApi::class)
@CName("Java_com_example_VoidHandler_doSomething")
fun doSomething(env: CPointer<JNIEnvVar>, thiz: jobject) {
    // 无返回值的函数
    println("Doing something...")
}

// 调用 void 方法
fun callVoidMethod(env: CPointer<JNIEnvVar>, obj: jobject) {
    val jniEnv = env.pointed.pointed!!
    
    val clazz = jniEnv.GetObjectClass!!(env, obj)
    val methodId = jniEnv.GetMethodID!!(
        env, clazz, "doAction", "()V"  // V 表示 void
    )
    
    jniEnv.CallVoidMethod!!(env, obj, methodId)
}

类型安全最佳实践

使用类型别名

kotlin
@OptIn(ExperimentalForeignApi::class)
// 为常用类型创建别名以提高可读性
typealias JString = jstring
typealias JClass = jclass
typealias JIntArray = jintArray

@CName("Java_com_example_Example_process")
fun process(env: CPointer<JNIEnvVar>, thiz: jobject, str: JString): JString? {
    // 代码更易读
    return str
}

空安全检查

kotlin
@OptIn(ExperimentalForeignApi::class)
fun checkNull(env: CPointer<JNIEnvVar>, obj: jobject?): Boolean {
    if (obj == null) return true
    
    val jniEnv = env.pointed.pointed!!
    return jniEnv.IsSameObject!!(env, obj, null) == JNI_TRUE
}

类型断言

kotlin
@OptIn(ExperimentalForeignApi::class)
fun safeStringCast(env: CPointer<JNIEnvVar>, obj: jobject): jstring? {
    val jniEnv = env.pointed.pointed!!
    val stringClass = jniEnv.FindClass!!(env, "java/lang/String")
    
    return if (jniEnv.IsInstanceOf!!(env, obj, stringClass) == JNI_TRUE) {
        obj as jstring
    } else {
        null
    }
}

深入理解 JNI 类型映射是编写正确、安全的 Native 代码的关键。掌握基本类型、引用类型、数组类型的正确使用方法,能够帮助你避免运行时错误和内存问题。