JNI 调用约定
源:JNI Types and Data Structures - Oracle
JNI 调用约定定义了 Java 与 Native 代码之间的接口规范。理解调用约定对编写正确的 JNI 代码至关重要。
函数命名规范
标准命名格式
Native 方法的函数名遵循固定模式:
Java_包名_类名_方法名包名中的点 (.) 和类名中的下划线 (_) 需要特殊处理:
.替换为__替换为_1$替换为_00024
java
package com.example.app;
public class NativeLib {
static {
System.loadLibrary("native-lib");
}
// 简单方法
public native void simpleMethod();
// 嵌套类
public static class Inner {
public native int calculate(int x);
}
}kotlin
@OptIn(ExperimentalForeignApi::class)
// 包名: com.example.app → com_example_app
// 类名: NativeLib
@CName("Java_com_example_app_NativeLib_simpleMethod")
fun simpleMethod(env: CPointer<JNIEnvVar>, thiz: jobject) {
println("Simple method called")
}
// 嵌套类: NativeLib$Inner → NativeLib_00024Inner
@CName("Java_com_example_app_NativeLib_00024Inner_calculate")
fun innerCalculate(
env: CPointer<JNIEnvVar>,
thiz: jobject,
x: jint
): jint {
return x * 2
}特殊字符编码
kotlin
// Java: com.my_company.MyClass
// Native: Java_com_my_1company_MyClass_method
// Java: com.example.Test$Inner
// Native: Java_com_example_Test_00024Inner_method方法重载处理
签名编码
当 Java 方法重载时,需要在函数名后追加编码后的方法签名:
Java_包名_类名_方法名__签名注意两个下划线 (__) 分隔方法名和签名。
java
public class Calculator {
// 重载方法
public native int add(int a, int b);
public native double add(double a, double b);
public native String add(String a, String b);
}kotlin
@OptIn(ExperimentalForeignApi::class)
// add(int, int) → (II)I
@CName("Java_com_example_Calculator_add__II")
fun addInt(
env: CPointer<JNIEnvVar>,
thiz: jobject,
a: jint,
b: jint
): jint {
return a + b
}
// add(double, double) → (DD)D
@CName("Java_com_example_Calculator_add__DD")
fun addDouble(
env: CPointer<JNIEnvVar>,
thiz: jobject,
a: jdouble,
b: jdouble
): jdouble {
return a + b
}
// add(String, String) → (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
// 特殊字符编码: ; → _2, / → _
@CName("Java_com_example_Calculator_add__Ljava_lang_String_2Ljava_lang_String_2")
fun addString(
env: CPointer<JNIEnvVar>,
thiz: jobject,
a: jstring?,
b: jstring?
): jstring? {
val jniEnv = env.pointed.pointed!!
val aStr = a?.toKString(env) ?: ""
val bStr = b?.toKString(env) ?: ""
return (aStr + bStr).toJString(env)
}方法签名规范
签名格式
(参数类型列表)返回类型基本类型签名
| Java 类型 | 签名 | 说明 |
|---|---|---|
void | V | 仅用于返回类型 |
boolean | Z | |
byte | B | |
char | C | |
short | S | |
int | I | |
long | J | |
float | F | |
double | D |
对象类型签名
对象类型格式:L完全限定类名;
kotlin
// java.lang.String
Ljava/lang/String;
// java.util.List
Ljava/util/List;
// com.example.User
Lcom/example/User;###数组签名
数组类型前缀 [:
kotlin
// int[]
[I
// String[]
[Ljava/lang/String;
// int[][]
[[I
// Object[]
[Ljava/lang/Object;完整示例
java
// Java 方法声明
void method1(); // ()V
int method2(int a); // (I)I
String method3(int a, String b); // (ILjava/lang/String;)Ljava/lang/String;
boolean method4(int[] arr); // ([I)Z
void method5(String[] strs, Object obj); // ([Ljava/lang/String;Ljava/lang/Object;)V
List<String> method6(Map<String, Integer> map); // (Ljava/util/Map;)Ljava/util/List;WARNING
泛型信息在 JNI 签名中会被擦除。List<String> 和 List<Integer> 都是 Ljava/util/List;
签名编码规则
特殊字符转义
签名中的特殊字符需要转义:
| 字符 | 转义 |
|---|---|
/ | _ |
; | _2 |
[ | _3 |
示例:
kotlin
// 原始签名: (Ljava/lang/String;)V
// 编码后: __Ljava_lang_String_2
// Native 函数名
Java_com_example_Test_method__Ljava_lang_String_2完整命名示例
java
package com.example;
public class Demo {
// 方法1:无参数
public native void foo();
// 方法2:单个 int 参数
public native int bar(int x);
// 方法3:重载 - String 参数
public native int bar(String s);
// 方法4:数组参数
public native String process(int[] data, String[] names);
}kotlin
@OptIn(ExperimentalForeignApi::class)
import kotlinx.cinterop.*
// 方法1
@CName("Java_com_example_Demo_foo")
fun foo(env: CPointer<JNIEnvVar>, thiz: jobject) {
// 实现
}
// 方法2
@CName("Java_com_example_Demo_bar__I")
fun barInt(env: CPointer<JNIEnvVar>, thiz: jobject, x: jint): jint {
return x * 2
}
// 方法3: 签名 (Ljava/lang/String;)I
// 编码: __Ljava_lang_String_2
@CName("Java_com_example_Demo_bar__Ljava_lang_String_2")
fun barString(env: CPointer<JNIEnvVar>, thiz: jobject, s: jstring?): jint {
return s?.toKString(env)?.length ?: 0
}
// 方法4: 签名 ([I[Ljava/lang/String;)Ljava/lang/String;
// 编码: __3I_3Ljava_lang_String_2
@CName("Java_com_example_Demo_process___3I_3Ljava_lang_String_2")
fun process(
env: CPointer<JNIEnvVar>,
thiz: jobject,
data: jintArray?,
names: jobjectArray?
): jstring? {
// 实现
return "Processed".toJString(env)
}静态方法与实例方法
参数差异
kotlin
@OptIn(ExperimentalForeignApi::class)
// 实例方法:第二个参数是 jobject (this)
@CName("Java_com_example_Test_instanceMethod")
fun instanceMethod(
env: CPointer<JNIEnvVar>,
thiz: jobject // 实例引用
) { }
// 静态方法:第二个参数是 jclass (类引用)
@CName("Java_com_example_Test_staticMethod")
fun staticMethod(
env: CPointer<JNIEnvVar>,
clazz: jclass // 类引用
) { }动态注册
RegisterNatives
除了使用命名约定,还可以动态注册:
kotlin
@OptIn(ExperimentalForeignApi::class)
import kotlinx.cinterop.*
// 实际的 native 函数(任意命名)
val nativeAdd = staticCFunction<CPointer<JNIEnvVar>, jobject, jint, jint, jint> {
env, thiz, a, b ->
a + b
}
// JNI_OnLoad 中注册
@CName("JNI_OnLoad")
fun onLoad(vm: CPointer<JavaVMVar>, reserved: COpaquePointer?): jint {
memScoped {
val env = allocPointerTo<JNIEnvVar>()
vm.pointed.pointed!!.GetEnv!!(vm, env.ptr.reinterpret(), JNI_VERSION_1_6)
val jniEnv = env.value!!.pointed.pointed!!
// 查找类
val clazz = jniEnv.FindClass!!(env.value, "com/example/Calculator")
// 定义方法映射
val methods = allocArray<JNINativeMethod>(1)
methods[0].apply {
name = "add".cstr.ptr
signature = "(II)I".cstr.ptr
fnPtr = nativeAdd.reinterpret()
}
// 注册
jniEnv.RegisterNatives!!(env.value, clazz, methods, 1)
}
return JNI_VERSION_1_6
}动态注册的优势推荐
- 解耦命名:Native 函数名不受 Java 包名/类名限制
- 易维护:重构 Java 代码时无需修改 Native 函数名
- 灵活性:可在运行时决定绑定关系
工具辅助
javap 查看签名
bash
# 编译 Java 文件
javac Calculator.java
# 查看方法签名
javap -s -p Calculator
# 输出
public int add(int,int);
descriptor: (II)I
public java.lang.String process(java.lang.String[]);
descriptor: ([Ljava/lang/String;)Ljava/lang/String;javah 生成头文件
bash
# 生成 JNI 头文件(包含正确的函数名)
javah -jni com.example.Calculator
# 生成 com_example_Calculator.h掌握 JNI 调用约定能避免 UnsatisfiedLinkError 等链接错误。推荐使用动态注册提升代码可维护性。