JNI 数据类型映射
源:JNI Types and Data Structures
深入理解 JNI 数据类型映射是编写高质量 Native 代码的基础。本文详细讲解 Java/Kotlin 类型如何在 JNI 层表示,以及如何在 Kotlin/Native 中正确使用这些类型。
基本数据类型
完整类型映射表
JNI 为 Java 的基本类型提供了对应的 C 类型定义:
| Java/Kotlin 类型 | JNI 类型 | C/C++ 实际类型 | 字节数 | 说明 |
|---|---|---|---|---|
boolean | jboolean | unsigned char | 1 | 0 或 1 |
byte | jbyte | signed char | 1 | -128 到 127 |
char | jchar | unsigned short | 2 | UTF-16 字符 |
short | jshort | short | 2 | -32,768 到 32,767 |
int | jint | int / long | 4 | -2³¹ 到 2³¹-1 |
long | jlong | long long | 8 | -2⁶³ 到 2⁶³-1 |
float | jfloat | float | 4 | IEEE 754 单精度 |
double | jdouble | double | 8 | IEEE 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 代码的关键。掌握基本类型、引用类型、数组类型的正确使用方法,能够帮助你避免运行时错误和内存问题。