
Java调用C方法的核心技术是JNIJava Native Interface它是一种Java平台的标准编程接口允许Java代码与用其他语言如C、C、Assembly编写的本地应用程序和库进行互操作。其基本思想是在Java层声明一个没有方法体、由native关键字修饰的方法然后在C/C层实现该方法并将实现编译成一个动态链接库在Linux/Android上是.so在Windows上是.dll最后在Java程序中加载该库并调用方法。以下是完整的实现流程与技术详解主要包括静态注册和动态注册两种方式。一、 Java调用C的整体流程整个流程可以分为五个核心步骤如下表所示步骤Java层操作C/C层操作产出物/关键点1. 声明本地方法创建Java类用native关键字声明方法。无。定义了接口契约。2. 生成头文件使用javac编译Java类然后使用javah旧或javac -h新生成C/C头文件。头文件 (*.h)。包含符合JNI规范的方法签名。3. 实现本地方法无。根据生成的头文件编写对应的C/C源文件实现具体逻辑。C/C源文件 (*.cpp)。4. 编译本地库无。使用编译器如g、clang及特定平台的编译脚本如Android.mk、CMakeLists.txt将C/C文件编译为动态库。动态库文件 (libxxx.so或.dll)。5. 加载并调用使用System.loadLibrary()或System.load()加载编译好的动态库。然后实例化Java类并调用native方法。无。完成Java到C的调用链路。二、 静态注册方式详解静态注册是JNI最传统和直接的方式其核心是让C/C函数名遵循一个由Java类名和方法名决定的特定格式。1. Java层代码示例首先我们创建一个简单的Java类NativeHelper.java其中声明了本地方法。// NativeHelper.java public class NativeHelper { // 声明一个本地方法用于计算两个整数的和 public native int add(int a, int b); // 静态代码块在类加载时自动执行用于加载动态链接库 static { // 加载名为 native-lib 的库系统会自动加上平台相关前缀如 libnative-lib.so System.loadLibrary(native-lib); } // 主方法用于测试 public static void main(String[] args) { NativeHelper helper new NativeHelper(); int result helper.add(10, 20); System.out.println(The result from C is: result); } }2. 生成JNI头文件编译Java源文件并生成对应的C/C头文件。# 编译Java类 javac NativeHelper.java # 生成JNI头文件JDK 10 推荐使用 javac -h 替代 javah javac -h ./jni NativeHelper.java执行上述命令后会在./jni目录下生成一个名为NativeHelper.h的头文件其关键内容如下/* DO NOT EDIT THIS FILE - it is machine generated */ #include jni.h /* Header for class NativeHelper */ #ifndef _Included_NativeHelper #define _Included_NativeHelper #ifdef __cplusplus extern C { #endif /* * Class: NativeHelper * Method: add * Signature: (II)I */ JNIEXPORT jint JNICALL Java_NativeHelper_add (JNIEnv *, jobject, jint, jint); #ifdef __cplusplus } #endif #endif这个头文件至关重要它定义了一个函数原型JNIEXPORT jint JNICALL Java_NativeHelper_add(JNIEnv *, jobject, jint, jint)。这个函数名是由Java_完整类名用下划线代替点_方法名构成的。JNIEnv*是指向JNI环境的指针提供了访问Java虚拟机功能的所有方法jobject是对调用此本地方法的Java对象本例中的helper实例的引用最后的jint类型是JNI中对应于Javaint的类型。3. C层实现接下来我们创建一个C源文件NativeHelper.cpp来实现这个函数。// NativeHelper.cpp #include NativeHelper.h // 包含生成的头文件 #include iostream // 实现头文件中声明的函数 JNIEXPORT jint JNICALL Java_NativeHelper_add(JNIEnv *env, jobject obj, jint a, jint b) { // 简单的加法运算 jint sum a b; // 在C端输出日志实际项目中可用于调试 std::cout [C] Received a a , b b , returning sum std::endl; return sum; }4. 编译C代码为动态库以Linux为例在Linux环境下使用g编译器将C代码编译为动态库。# 假设头文件在./jni目录源文件在当前目录 # 编译生成共享库。注意需要指定JDK中的jni.h和jni_md.h所在路径 g -shared -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux \ -I./jni \ NativeHelper.cpp \ -o libnative-lib.so这条命令将生成libnative-lib.so文件。-shared表示生成共享库-fPIC表示生成位置无关代码。5. 运行Java程序将编译好的libnative-lib.so放在Java库路径下例如放在项目根目录或通过-Djava.library.path指定路径然后运行Java程序。java -Djava.library.path. NativeHelper输出结果应为[C] Received a10, b20, returning 30 The result from C is: 30三、 动态注册方式详解静态注册的缺点是函数名冗长且不灵活。动态注册允许在C/C库加载时通常在JNI_OnLoad函数中主动将Java的本地方法与自定义的C函数进行绑定提供了更好的灵活性和性能。1. Java层代码Java层代码与静态注册几乎相同但通常不需要生成头文件。// NativeHelperDynamic.java public class NativeHelperDynamic { // 同样声明本地方法 public native int multiply(int a, int b); public native String sayHello(String name); static { System.loadLibrary(native-lib-dynamic); } public static void main(String[] args) { NativeHelperDynamic helper new NativeHelperDynamic(); System.out.println(Multiply: helper.multiply(5, 6)); System.out.println(Greeting: helper.sayHello(World)); } }2. C层实现动态注册核心在C实现中我们需要定义方法映射表JNINativeMethod并实现JNI_OnLoad函数。// NativeHelperDynamic.cpp #include jni.h #include string #include android/log.h // 如果在Android环境可使用此日志标准C用iostream // 实际实现函数1乘法 jint native_multiply(JNIEnv *env, jobject obj, jint a, jint b) { return a * b; } // 实际实现函数2字符串问候 jstring native_sayHello(JNIEnv *env, jobject obj, jstring jname) { // 将Java字符串转换为C风格字符串 const char* c_name env-GetStringUTFChars(jname, nullptr); // 构造返回字符串 std::string hello_msg Hello from C, ; hello_msg c_name; // 释放由GetStringUTFChars获取的字符串资源避免内存泄漏 env-ReleaseStringUTFChars(jname, c_name); // 将C字符串转换为Java字符串并返回 return env-NewStringUTF(hello_msg.c_str()); } // 定义方法映射表 // 每个条目包含Java方法名方法签名C函数指针 static JNINativeMethod gMethods[] { {multiply, (II)I, (void*)native_multiply}, {sayHello, (Ljava/lang/String;)Ljava/lang/String;, (void*)native_sayHello} }; // JNI库被加载时自动调用的函数 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env nullptr; // 获取JNI环境 if (vm-GetEnv((void**)env, JNI_VERSION_1_6) ! JNI_OK) { return JNI_ERR; } // 找到要注册本地方法的Java类 // 注意类名中的点(.)需要替换为斜杠(/) jclass clazz env-FindClass(NativeHelperDynamic); if (clazz nullptr) { return JNI_ERR; } // 注册本地方法 // 参数Java类方法数组要注册的方法数量 if (env-RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) 0) { return JNI_ERR; } // 返回所需的JNI版本 return JNI_VERSION_1_6; }在动态注册中JNINativeMethod结构体是关键它包含了Java方法名、方法签名和对应的本地函数指针。方法签名是一个字符串用于描述方法的参数和返回值类型例如(II)I表示两个int参数并返回int(Ljava/lang/String;)Ljava/lang/String;表示一个String参数并返回String。JNI_OnLoad函数是动态库加载的入口点在这里完成方法注册。四、 核心概念与注意事项1. JNI数据类型映射Java中的基本类型和引用类型在JNI中都有对应的类型。理解这些映射对于正确处理数据至关重要。Java类型JNI类型C/C类型近似描述booleanjbooleanunsigned char8位无符号bytejbytesigned char8位有符号charjcharunsigned short16位无符号shortjshortshort16位有符号intjintint32位有符号longjlonglong long64位有符号floatjfloatfloat32位浮点doublejdoubledouble64位浮点ObjectjobjectN/A任何Java对象的引用StringjstringN/AJava字符串对象的特殊引用ClassjclassN/Ajava.lang.Class对象的引用对于引用类型如jstring,jarray,jobject在本地代码中不能直接像C字符串一样使用。必须使用JNIEnv提供的函数进行转换例如使用GetStringUTFChars获取jstring的C字符串表示并在使用完毕后调用ReleaseStringUTFChars释放资源这是防止内存泄漏的关键。2. 线程安全性JNIEnv指针 (JNIEnv*) 是线程本地的不能在线程间共享。如果需要在本地创建的新线程中调用JNI函数必须先通过JavaVM对象在JNI_OnLoad中可获取的AttachCurrentThread函数获取当前线程的JNIEnv指针并在操作完成后调用DetachCurrentThread。3. 异常处理在本地代码中调用JNI函数可能会抛出Java异常。与Java不同本地代码中的异常不会改变程序的控制流。必须显式地检查并处理异常。常用的模式是调用ExceptionCheck()或ExceptionOccurred()检查是否有异常发生并及时使用ExceptionClear()清除异常否则可能导致后续JNI调用行为未定义或虚拟机崩溃。4. 在Android中的特殊实践在Android开发中流程与上述标准JNI类似但集成在NDKNative Development Kit框架内。编译系统通常使用CMake或传统的Android.mk来配置和编译本地库。库加载在Java类通常是MainActivity或一个专门的类的静态块中使用System.loadLibrary(mylib)加载Android Studio会自动将libmylib.so打包进APK。性能关键路径将计算密集型、图形处理或特定硬件操作如信号处理、加密用C实现通过JNI供Java调用是提升Android应用性能的常见手段。五、 复杂应用C回调Java方法JNI不仅支持Java调用C也支持C主动调用Java的方法实现双向通信或回调机制。其基本步骤是在C端通过JNIEnv找到Java类、获取方法ID、然后调用该方法。例如在C中触发一个Java回调// 假设这是一个C函数被某个事件触发 void trigger_callback(JNIEnv* env, jobject java_instance) { // 1. 获取Java对象的类 jclass clazz env-GetObjectClass(java_instance); // 2. 获取方法ID。方法名为 onEvent签名为 (I)V (一个int参数无返回值) jmethodID method_id env-GetMethodID(clazz, onEvent, (I)V); if (method_id nullptr) { // 方法未找到 return; } // 3. 调用Java实例方法 env-CallVoidMethod(java_instance, method_id, 100); }这种模式广泛用于将底层事件如传感器数据就绪、网络数据到达通知到Java层。参考来源Android JNI安卓JNI(Java Native Interface)jni编程 C调用java详解Android JNI复杂用法回调C中调用Java方法android实现app通过jni调用C/C方法Linux下java使用JNI调用C