本文目录导读:

- 第一部分:环境准备与基础概念
- 案例 1:Hello World (最简入门)
- 案例 2:传递与返回结构体 (Struct)
- 案例 3:处理字符串数组 (String[] 与 char**)
- 案例 4:回调函数 (Callback)
- 案例 5:直接操作内存 (Pointer 与 Memory)
- 案例 6:按值传递结构体 (ByValue)
- 案例 7:获取错误码 (GetLastError)
- 案例 8:处理联合体 (Union)
- 高级技巧与最佳实践
- 完整实例:调用系统时间函数
- 常见问题与排查
这是一个关于 JNA (Java Native Access) 的完整案例合集,从基础到进阶,包括实际开发中常见的场景,JNA 让你能像调用普通 Java 方法一样调用 C/C++ 动态库(.dll, .so, .dylib)。
第一部分:环境准备与基础概念
-
Maven 依赖:
<dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.14.0</version> </dependency> <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna-platform</artifactId> <version>5.14.0</version> </dependency> -
核心概念:
Library:继承com.sun.jna.Library的接口,定义你要调用的 native 方法。Native.load():加载动态库,返回接口的实例。Structure:继承com.sun.jna.Structure,对应 C 语言的struct。Pointer:对应 C 语言的void*或指针。Callback:实现回调函数。ByReference:用于传递引用类型参数(模拟int*)。
案例 1:Hello World (最简入门)
场景:调用一个 C 函数,它打印字符串并返回一个整数。
C 代码 (test.c):
#include <stdio.h>
int hello_from_c(char* name) {
printf("Hello from C, %s!\n", name);
return 42;
}
编译:gcc -shared -o libtest.so test.c (Linux/Mac) 或 clang -shared -o test.dll test.c (Windows/MinGW)
Java 代码:
import com.sun.jna.Library;
import com.sun.jna.Native;
public class JNAHelloWorld {
// 1. 定义接口,继承 Library
public interface TestLibrary extends Library {
// 方法签名必须与 C 函数完全一致
int hello_from_c(String name);
}
public static void main(String[] args) {
// 2. 加载动态库,第二个参数是接口的 Class 对象
TestLibrary lib = Native.load("test", TestLibrary.class);
// 3. 调用 Native 方法
int result = lib.hello_from_c("JNA User");
System.out.println("C function returned: " + result);
}
}
案例 2:传递与返回结构体 (Struct)
场景:C 函数接收一个结构体,修改它并返回。
C 代码:
typedef struct {
int id;
char name[256];
double score;
} Student;
void update_student(Student* s) {
s->id = 100;
snprintf(s->name, sizeof(s->name), "Updated by C");
s->score = 99.9;
}
Java 代码:
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
public class StructExample {
// 1. 定义 Java 结构体,继承 Structure
@Structure.FieldOrder({"id", "name", "score"}) // 字段顺序必须与 C 一致
public static class Student extends Structure {
public int id;
public String name; // 对于 char[],JNA 默认映射为 String 或 byte[]
public double score;
// C 是定长 char[256],需要指定 size 或使用 byte[]
public static class ByValue extends Student implements Structure.ByValue {}
public static class ByReference extends Student implements Structure.ByReference {}
}
// 2. 定义接口
public interface MyLib extends Library {
void update_student(Student.ByReference s); // 传递指针
}
public static void main(String[] args) {
MyLib lib = Native.load("mylib", MyLib.class);
// 3. 创建结构体实例
Student s = new Student();
s.id = 1;
s.name = "Initial Name";
s.score = 80.0;
s.write(); // 将 Java 数据写入 native 内存
System.out.println("Before: " + s.id + ", " + s.name + ", " + s.score);
// 4. 调用 native 方法(传递指针)
lib.update_student(s.getPointer()); // 或者使用 s 本身,如果方法参数声明为 Student.ByReference
s.read(); // 从 native 内存读取修改后的数据
System.out.println("After: " + s.id + ", " + s.name + ", " + s.score);
}
}
案例 3:处理字符串数组 (String[] 与 char**)
场景:C 函数接收一个字符串数组。
C 代码:
void print_strings(char** arr, int count) {
for (int i = 0; i < count; i++) {
printf("String %d: %s\n", i, arr[i]);
}
}
Java 代码:
import com.sun.jna.Library;
import com.sun.jna.Native;
public class StringArrayExample {
public interface MyLib extends Library {
void print_strings(String[] arr, int count);
}
public static void main(String[] args) {
MyLib lib = Native.load("mylib", MyLib.class);
String[] names = {"Alice", "Bob", "Charlie"};
lib.print_strings(names, names.length);
}
}
案例 4:回调函数 (Callback)
场景:C 函数接收一个函数指针作为回调。
C 代码:
typedef int (*callback_t)(int a, int b);
int perform_operation(int x, int y, callback_t cb) {
return cb(x, y);
}
Java 代码:
import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Native;
public class CallbackExample {
// 1. 定义回调接口,继承 Callback
public interface MyCallback extends Callback {
int invoke(int a, int b);
}
// 2. 定义库接口
public interface MyLib extends Library {
int perform_operation(int x, int y, MyCallback callback);
}
public static void main(String[] args) {
MyLib lib = Native.load("mylib", MyLib.class);
// 3. 实现回调(可以是匿名类或 Lambda)
MyCallback add = (a, b) -> a + b;
MyCallback multiply = (a, b) -> a * b;
int result1 = lib.perform_operation(5, 3, add);
int result2 = lib.perform_operation(5, 3, multiply);
System.out.println("5 + 3 = " + result1);
System.out.println("5 * 3 = " + result2);
}
}
案例 5:直接操作内存 (Pointer 与 Memory)
场景:C 函数返回一个指针,你需要手动读写内存。
C 代码:
void* create_buffer(int size) {
return malloc(size);
}
void fill_buffer(void* buf, int value, int size) {
memset(buf, value, size);
}
void free_buffer(void* buf) {
free(buf);
}
Java 代码:
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
public class PointerExample {
public interface MyLib extends Library {
Pointer create_buffer(int size);
void fill_buffer(Pointer buf, int value, int size);
void free_buffer(Pointer buf);
}
public static void main(String[] args) {
MyLib lib = Native.load("mylib", MyLib.class);
int size = 1024;
Pointer buf = lib.create_buffer(size);
// 使用 JNA 的 Memory 类(等价于 malloc)
// Memory mem = new Memory(size);
// 填充数据
lib.fill_buffer(buf, 0xFF, size);
// 读取数据(例如前10个字节)
byte[] data = buf.getByteArray(0, 10); // 从偏移0读10个字节
// 打印
for (byte b : data) {
System.out.printf("%02X ", b & 0xFF);
}
// 释放内存
lib.free_buffer(buf);
}
}
案例 6:按值传递结构体 (ByValue)
场景:有时 C 函数接受结构体的副本(值传递)而不是指针。
C 代码:
typedef struct {
int x;
int y;
} Point;
Point move_point(Point p, int dx, int dy) {
p.x += dx;
p.y += dy;
return p; // 返回结构体
}
Java 代码:
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
public class ByValueExample {
@Structure.FieldOrder({"x", "y"})
public static class Point extends Structure {
public int x;
public int y;
// 必须定义 ByValue 静态内部类
public static class ByValue extends Point implements Structure.ByValue {}
}
public interface MyLib extends Library {
Point.ByValue move_point(Point.ByValue p, int dx, int dy);
}
public static void main(String[] args) {
MyLib lib = Native.load("mylib", MyLib.class);
Point.ByValue p = new Point.ByValue();
p.x = 10;
p.y = 20;
p.write();
Point.ByValue result = lib.move_point(p, 5, -3);
result.read();
System.out.println("New Point: (" + result.x + ", " + result.y + ")");
}
}
案例 7:获取错误码 (GetLastError)
场景:某些 Windows API 需要检查 GetLastError。
import com.sun.jna.LastErrorException;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
public class WinApiExample {
public interface Kernel32 extends Library {
// 告诉 JNA 这个方法可能抛 LastErrorException
int GetCurrentProcessId() throws LastErrorException;
}
public static void main(String[] args) {
Kernel32 lib = Native.load(Platform.isWindows() ? "kernel32" : "c", Kernel32.class);
try {
int pid = lib.GetCurrentProcessId();
System.out.println("PID: " + pid);
} catch (LastErrorException e) {
System.out.println("Error code: " + e.getErrorCode());
}
}
}
注意:通常需要调用 Native.setPreserveLastError(true) 才能获取最后的错误码。
案例 8:处理联合体 (Union)
场景:C 的 union 类型。
C 代码:
union Data {
int i;
float f;
char str[20];
};
Java 代码:
import com.sun.jna.Union;
import java.util.Arrays;
import java.util.List;
public class UnionExample {
public static class Data extends Union {
public int i;
public float f;
public byte[] str = new byte[20]; // 定长 byte 数组
// 必须指定字段顺序用于内存布局
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("i", "f", "str");
}
// 设置当前使用的字段(JNA 需要知道当前哪个字段有效)
public void setType(Class<?> type) {
setType(type);
}
}
}
高级技巧与最佳实践
-
加载路径:
Native.load("test", ...)会按系统默认路径查找,可以使用绝对路径:Native.load("/absolute/path/libtest.so", ...)。 -
性能优化:
- JNA 默认会进行类型映射转换,对于性能敏感场景,考虑使用
com.sun.jna.ptr包中的IntByReference等类,减少对象创建。 - 使用
com.sun.jna.CallbackThreadInitializer控制回调线程。
- JNA 默认会进行类型映射转换,对于性能敏感场景,考虑使用
-
调试:设置
Native.setProtected(true);可以在 native 崩溃时打印更多信息。 -
内存管理:
- JNA 不会自动释放
Pointer或Memory对象(除非使用com.sun.jna.Native.free())。 - 始终确保与 C 代码的内存管理匹配(谁分配谁释放)。
- JNA 不会自动释放
-
复杂类型:
- 对于
char*返回的类型,JNA 默认映射为String,如果返回的是内部缓冲区指针,需要小心生命周期。 - 对于
void**或二级指针,使用PointerByReference类。
- 对于
-
跨平台:
- 使用
Platform.isWindows(),Platform.isLinux(),Platform.isMac()选择不同的库或处理方式。 - 库名自动处理前缀后缀(Windows 加
.dll,Linux 加.so,Mac 加.dylib)。
- 使用
-
异常处理:所有 native 方法默认可能抛出
UnsatisfiedLinkError(库加载失败),建议捕获。 -
直接映射 (Direct Mapping):JNA 5.0+ 支持不使用接口,直接调用
Native.invokeXxx(),性能更高但代码更复杂,适合框架开发者。
完整实例:调用系统时间函数
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
public class SystemTimeExample {
// Windows TIME_OF_DAY 结构体
@Structure.FieldOrder({"wYear", "wMonth", "wDayOfWeek", "wDay", "wHour", "wMinute", "wSecond", "wMilliseconds"})
public static class SYSTEMTIME extends Structure {
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
public interface Kernel32 extends Library {
void GetLocalTime(SYSTEMTIME systemTime);
}
public static void main(String[] args) {
Kernel32 lib = Native.load("kernel32", Kernel32.class);
SYSTEMTIME time = new SYSTEMTIME();
lib.GetLocalTime(time);
time.read();
System.out.printf("Current time: %04d-%02d-%02d %02d:%02d:%02d.%03d",
time.wYear, time.wMonth, time.wDay,
time.wHour, time.wMinute, time.wSecond, time.wMilliseconds);
}
}
常见问题与排查
| 问题 | 可能原因 |
|---|---|
UnsatisfiedLinkError |
库文件找不到 / 格式不对 / 32位与64位不匹配 |
IllegalArgumentException: Structure size mismatch |
Java 结构体字段顺序或字节对齐与 C 不一致 |
java.lang.Error: Invalid memory access |
读写非法指针 / 结构体未调用 write() 或 read() |
| 回调不执行或崩溃 | 回调对象被垃圾回收 (保持引用) / 线程冲突 |
| 中文乱码 | 设置 Native.setEncoding("UTF-8") |
案例涵盖了 JNA 90% 以上的日常使用场景,实际开发中,最常出错的是结构体的字段顺序和内存布局,建议在定义 Structure 时使用 @FieldOrder 注解并仔细对照 C 头文件。