Java 使用 JNI 调用本地动态库

  • 在 Java 中可以使用 JNI(Java Native Interface)调用本地动态库,它允许 Java 应用程序与本地 C/C++ 代码进行交互

1. 编写 Java 本地方法声明

  • 在 Java 类中声明本地方法,类似于普通的 Java 方法声明,但是使用 native 关键字。这些方法将在本地库中实现
1
2
3
4
5
package com.lb.test.jni;

public class JNITest {
public native String nativeMethod(String username, String password);
}

2. 生成 C/C++ 头文件

  • 首先编译 Java 类文件,生成 .class 文件
  • 然后使用 javah 命令生成本地方法的 C/C++ 头文件,提供包含本地方法声明的 Java 类的类名
1
$ javah -jni com.lb.test.jni.JNITest
  • com_lb_test_jni_JNITest.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_lb_test_jni_JNITest */

#ifndef _Included_com_lb_test_jni_JNITest
#define _Included_com_lb_test_jni_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_lb_test_jni_JNITest
* Method: nativeMethod
* Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_lb_test_jni_JNITest_nativeMethod
(JNIEnv *, jobject, jstring, jstring);

#ifdef __cplusplus
}
#endif
#endif
  • Java 提供了 JNI 头文件 jni.h,它包含了与本地代码交互所需的一些声明和定义。一般位于 JDK 的 include 目录下

3. 在 C/C++ 代码中实现本地方法

  • 使用生成的 C/C++ 头文件,实现 Java 类中声明的本地方法
  • JNITest.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "com_lb_test_jni_JNITest.h"

JNIEXPORT jstring JNICALL Java_com_lb_test_jni_JNITest_nativeMethod(JNIEnv *env, jobject obj, jstring username, jstring password) {
// Convert Java strings to C-style strings (const char *)
const char *c_username = env->GetStringUTFChars(username, NULL);
const char *c_password = env->GetStringUTFChars(password, NULL);

// Perform some operation with the username and password (Example: Concatenate them)
char result[256];
snprintf(result, sizeof(result), "Hello, %s! Your password is %s.", c_username, c_password);

// Release the C-style strings to avoid memory leaks
env->ReleaseStringUTFChars(username, c_username);
env->ReleaseStringUTFChars(password, c_password);

// Return the result as a Java string
return env->NewStringUTF(result);
}

4. 编译本地动态库

  • 使用 C/C++ 编译器编译本地动态库,确保生成与平台相匹配的动态库文件(.dll.so.dylib
1
2
3
4
5
# Windows
$ gcc/g++ -shared -o JNITest.so JNITest.c -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32"

# Linux/macOS
$ gcc/g++ -shared -o libJNITest.so JNITest.c -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux"
  • 在 Linux 和 macOS 系统上,共享库文件的命名约定中通常会在动态库文件名前面加上 lib 前缀

5. 运行 Java 程序

  • 确保本地动态库文件与 Java 类在同一目录或在 Java 的 java.library.path 路径中
1
2
3
4
5
6
7
8
9
10
11
public class Main {
static {
System.loadLibrary("JNITest"); // 加载本地动态库
}

public static void main(String[] args) {
JNITest jniTest = new JNITest();
System.out.println(jniTest.nativeMethod("aaa", "bbb"));
}
}
// Hello, aaa! Your password is bbb.
  • SpringBoot 程序可使用以下命令加载本地动态库文件:
1
$ java -Djava.library.path=/opt/app/test/ -jar jni-test.jar