JNA案例

wen java案例 3

本文目录导读:

JNA案例

  1. 第一部分:环境准备与基础概念
  2. 案例 1:Hello World (最简入门)
  3. 案例 2:传递与返回结构体 (Struct)
  4. 案例 3:处理字符串数组 (String[] 与 char**)
  5. 案例 4:回调函数 (Callback)
  6. 案例 5:直接操作内存 (Pointer 与 Memory)
  7. 案例 6:按值传递结构体 (ByValue)
  8. 案例 7:获取错误码 (GetLastError)
  9. 案例 8:处理联合体 (Union)
  10. 高级技巧与最佳实践
  11. 完整实例:调用系统时间函数
  12. 常见问题与排查

这是一个关于 JNA (Java Native Access) 的完整案例合集,从基础到进阶,包括实际开发中常见的场景,JNA 让你能像调用普通 Java 方法一样调用 C/C++ 动态库(.dll, .so, .dylib)。


第一部分:环境准备与基础概念

  1. 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>
  2. 核心概念

    • 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);
        }
    }
}

高级技巧与最佳实践

  1. 加载路径Native.load("test", ...) 会按系统默认路径查找,可以使用绝对路径:Native.load("/absolute/path/libtest.so", ...)

  2. 性能优化

    • JNA 默认会进行类型映射转换,对于性能敏感场景,考虑使用 com.sun.jna.ptr 包中的 IntByReference 等类,减少对象创建。
    • 使用 com.sun.jna.CallbackThreadInitializer 控制回调线程。
  3. 调试:设置 Native.setProtected(true); 可以在 native 崩溃时打印更多信息。

  4. 内存管理

    • JNA 不会自动释放 PointerMemory 对象(除非使用 com.sun.jna.Native.free())。
    • 始终确保与 C 代码的内存管理匹配(谁分配谁释放)。
  5. 复杂类型

    • 对于 char* 返回的类型,JNA 默认映射为 String,如果返回的是内部缓冲区指针,需要小心生命周期。
    • 对于 void** 或二级指针,使用 PointerByReference 类。
  6. 跨平台

    • 使用 Platform.isWindows(), Platform.isLinux(), Platform.isMac() 选择不同的库或处理方式。
    • 库名自动处理前缀后缀(Windows 加 .dll,Linux 加 .so,Mac 加 .dylib)。
  7. 异常处理:所有 native 方法默认可能抛出 UnsatisfiedLinkError(库加载失败),建议捕获。

  8. 直接映射 (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 头文件。

上一篇Java调用Python案例

下一篇JNI案例

抱歉,评论功能暂时关闭!