一、简介
Java Native Interface (JNI) 是Java提供的一种框架,允许Java代码和其他编程语言(如C、C++)编写的代码进行交互。JNI使得Java程序能够调用本地方法,并且允许本地代码调用Java方法。它是实现Java与本地系统或库交互的重要工具。
1. 基本概念
- JNI的角色:JNI作为Java虚拟机(JVM)和本地代码之间的桥梁,使得Java可以利用已有的本地库或操作系统功能。
 - 应用场景 :
- 性能优化:在需要高性能的计算或特定算法时,使用本地代码实现更高效的操作。
 - 系统功能访问:访问底层操作系统功能或硬件设备,这些是Java无法直接做到的。
 - 复用现有代码:调用已有的本地代码库或集成第三方库。
 
 
2. JNI的核心概念
- 本地方法 :用
native关键字声明的方法,这些方法的实现是用非Java语言编写的。 - 本地方法库 :包含本地方法实现的动态库文件,如
.dll(Windows)、.so(Linux/Unix)、.dylib(macOS)。 - JNI头文件 :通过
javah工具生成的C/C++头文件,定义了Java类和本地方法之间的接口。 
3. JNI的基本步骤
- 
声明本地方法 :
在Java类中声明一个或多个本地方法。例如:
javapublic class NativeExample { public native void nativeMethod(); static { System.loadLibrary("nativeexample"); } } - 
生成头文件 :
使用
javac编译Java代码后,利用javah工具生成C/C++头文件。或者在Java 8及之后,可以直接使用javac -h命令:shjavac NativeExample.java javac -h . NativeExample.java - 
实现本地方法 :
在C或C++中实现本地方法,代码示例:
c#include <jni.h> #include "NativeExample.h" // 通过javah生成的头文件 JNIEXPORT void JNICALL Java_NativeExample_nativeMethod(JNIEnv *env, jobject obj) { printf("This is a native method implementation.\n"); } - 
编译和链接 :
编译本地方法实现为动态库。例如:
shgcc -shared -o libnativeexample.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux NativeExample.c - 
加载本地库 :
在Java代码中通过
System.loadLibrary加载动态库,以便调用本地方法。 
4. JNI的主要功能
- 
方法调用:
- 调用本地方法:从Java调用C/C++编写的本地方法。
 - 从本地代码调用Java方法:在本地代码中调用Java方法。
 
 - 
数据交换:
- 基本数据类型 :如
int,float,double等可以直接在Java和本地代码之间传递。 - 对象和数组:可以在Java和本地代码之间传递Java对象和数组。
 
 - 基本数据类型 :如
 - 
异常处理:
- 抛出和处理异常:本地代码可以通过JNI接口抛出异常,也可以检查Java中发生的异常。
 
 - 
对象操作:
- 创建和访问Java对象:在本地代码中创建Java对象并访问其字段和方法。
 
 
5. JNI的关键函数
- 
获取Java环境:
cJNIEnv *env; - 
访问类和方法:
- 
FindClass:查找Java类。cjclass cls = (*env)->FindClass(env, "com/example/NativeExample"); - 
GetMethodID:获取方法ID。cjmethodID mid = (*env)->GetMethodID(env, cls, "methodName", "()V"); - 
CallVoidMethod:调用Java方法。c(*env)->CallVoidMethod(env, obj, mid); 
 - 
 - 
操作Java字符串和数组:
- 
GetStringUTFChars:将Java字符串转换为C字符串。cconst char *nativeString = (*env)->GetStringUTFChars(env, javaString, NULL); - 
NewObjectArray:创建Java数组。cjobjectArray array = (*env)->NewObjectArray(env, size, cls, initObject); 
 - 
 
6. JNI的优缺点
- 
优点:
- 性能:可以利用本地代码进行高效处理。
 - 功能扩展:可以访问Java无法直接调用的系统级功能。
 - 复用代码:可以利用现有的本地库或系统资源。
 
 - 
缺点:
- 复杂性:涉及到不同编程语言的交互,编写和调试相对复杂。
 - 平台依赖:本地方法的实现通常与平台相关,可能导致跨平台兼容性问题。
 - 安全性:本地代码可能带来安全隐患,如果实现不当,可能会影响JVM的稳定性或安全性。
 
 
7. 示例
Java类:
            
            
              java
              
              
            
          
          public class HelloWorld {
    public native void sayHello();
    static {
        System.loadLibrary("hello");
    }
}
        C实现:
            
            
              c
              
              
            
          
          #include <jni.h>
#include <stdio.h>
#include "HelloWorld.h"
JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv *env, jobject obj) {
    printf("Hello from native code!\n");
}
        编译和运行:
- 
编译Java类:
shjavac HelloWorld.java - 
生成头文件:
shjavah HelloWorld - 
编译本地代码生成动态库:
shgcc -shared -o libhello.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux HelloWorld.c - 
运行Java程序:
shjava HelloWorld 
JNI是Java与本地代码交互的强大工具,但使用时需要仔细管理内存和处理跨语言调用的复杂性。
二、完整代码示例
下面是一个完整的JNI示例,展示了如何在Java中声明本地方法,如何用C语言实现这些方法,以及如何编译和运行这段代码。这个示例将实现一个简单的本地方法,该方法将打印一条消息到控制台。
1. Java代码
首先,我们编写一个Java类,声明一个本地方法,并在静态块中加载本地库。
HelloWorld.java:
            
            
              java
              
              
            
          
          public class HelloWorld {
    // 声明本地方法
    public native void sayHello();
    // 加载本地库
    static {
        System.loadLibrary("hello");
    }
    public static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld();
        helloWorld.sayHello(); // 调用本地方法
    }
}
        2. 生成JNI头文件
编译Java代码,并使用javah(或者在Java 8及以后的版本中使用javac -h)生成JNI头文件。
步骤:
            
            
              sh
              
              
            
          
          # 编译Java类
javac HelloWorld.java
# 生成JNI头文件(使用javah或javac -h)
javac -h . HelloWorld.java
        生成的头文件HelloWorld.h将包含JNI接口的C声明。
3. C实现
根据生成的头文件编写C语言的实现。
HelloWorld.c:
            
            
              c
              
              
            
          
          #include <jni.h>
#include <stdio.h>
#include "HelloWorld.h" // 由javah生成的头文件
// 实现本地方法
JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv *env, jobject obj) {
    printf("Hello from native code!\n");
}
        4. 编译和链接本地代码
将C代码编译成动态库。不同平台的编译命令有所不同。
Linux/Unix:
            
            
              sh
              
              
            
          
          # 编译生成共享库(.so文件)
gcc -shared -o libhello.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux HelloWorld.c
        Windows:
            
            
              sh
              
              
            
          
          # 编译生成动态库(.dll文件)
gcc -shared -o hello.dll -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" HelloWorld.c
        macOS:
            
            
              sh
              
              
            
          
          # 编译生成共享库(.dylib文件)
gcc -shared -o libhello.dylib -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin HelloWorld.c
        5. 运行Java程序
确保动态库文件位于Java的库路径中,然后运行Java程序。
步骤:
            
            
              sh
              
              
            
          
          # 设置库路径
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH  # Linux/Unix
set PATH=%PATH%;.  # Windows
export DYLD_LIBRARY_PATH=.:$DYLD_LIBRARY_PATH  # macOS
# 运行Java程序
java HelloWorld
        6. 完整目录结构
假设您的目录结构如下:
/path/to/project/
    HelloWorld.java
    HelloWorld.h  (由javah生成)
    HelloWorld.c
    libhello.so  (生成的动态库)
        7. 注意事项
- 路径:确保动态库文件的路径被正确设置,以便Java虚拟机可以找到它。
 - 头文件 :
HelloWorld.h文件由javah或javac -h自动生成,包含JNI函数的声明。 - JNI函数命名 :JNI函数的名称必须符合约定,即 
Java_<包名>_<类名>_<方法名>的格式。 
8. 完整代码
HelloWorld.java:
            
            
              java
              
              
            
          
          public class HelloWorld {
    public native void sayHello();
    static {
        System.loadLibrary("hello");
    }
    public static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld();
        helloWorld.sayHello();
    }
}
        HelloWorld.h(自动生成的头文件):
            
            
              c
              
              
            
          
          /* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_sayHello
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
        HelloWorld.c:
            
            
              c
              
              
            
          
          #include <jni.h>
#include <stdio.h>
#include "HelloWorld.h"
JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv *env, jobject obj) {
    printf("Hello from native code!\n");
}
        编译命令(Linux/Unix 示例):
            
            
              sh
              
              
            
          
          javac HelloWorld.java
javac -h HelloWorld.java
gcc -shared -o libhello.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux HelloWorld.c
java HelloWorld
        这样,通过JNI,你可以将Java程序与本地系统功能进行交互,实现高效的计算或系统功能访问。