JNA 教程

前言

只要你用过了 JNA (java native access) , 那你可能就再也不想用 JNI 了

实际上, JNA 搞定了 JNI 中最麻烦的数据类型映射, 可以让我们进行高效的开发, 不用再去写各种的转换接口.

  • char*
  • string
  • 数组
  • 结构体

上面的数据类型它都支持

可能有人会问 JNA 能完全代替 JNI 么? 不能, JNA只能实现Java访问C函数,如果你想实现C语言调用 Java 代码, 你还是需要使用 JNI 技术。

但其实在很大程度上已经够用了, 因为在很多应用领域都是由 C++算法工程师提供库 (.so/.dll) , java 工程师只需要负责调用就可以了

JNA 有什么用? 吹一波?

就一个 简化 jni 开发

想想当年写 jni 的时候, 下面这样的代码要写几百行, 你就知道我有多痛苦了

1570852575274

当然最痛苦的还不是这个

在开发中, 作为一个JAVA 程序员你会遇到各种各样奇葩的问题

还包含一部分你无法理解的C++问题

  • 类型转换
  • linux windows多环境编译
  • 内存泄漏
  • 异常处理
  • 各种找不到原因的报错
  • debug 困难
  • ...

而如果使用 JNA, 你可能只需要这样:

  • 你不需要通过 javah 生成头文件, 不需要给它写实现

  • 不需要在 windows/linux 环境各自编译成 .dll/.so 来调用真正的函数

  • 只需要声明一个接口, 其他的事情让 JNA 做好就行

public class HelloWorld {
    public interface CLibrary extends Library {
        CLibrary INSTANCE = Native.load((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class);

        void printf(String format, Object... args);
    }

    public static void main(String[] args) {
        CLibrary.INSTANCE.printf("Hello, World\n");
    }
}
// 该源码来自 https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md

哪些项目使用了 JNA

  • Apache Cassandra:大型NoSQL数据存储
  • JetBrains的IntelliJ IDEA
  • NetBeans IDE

...

每天都在用的 IDEA 都用了 JNA =.= 你还怕什么?

数据类型映射

了解一下 C -> java 的数据类型映射, 帮助你更好的完成之后的练习

Native Type Size Java Type Common Windows Types
char 8-bit integer byte BYTE, TCHAR
short 16-bit integer short WORD
wchar_t 16/32-bit character char TCHAR
int 32-bit integer int DWORD
int boolean value boolean BOOL
long 32/64-bit integer NativeLong LONG
long long 64-bit integer long __int64
float 32-bit FP float
double 64-bit FP double
char* C string String LPCSTR
void* pointer Pointer LPVOID, HANDLE, LPXXX

from JNA - Default Type Mappings

你可能会发现表中没有 native 的 boolean 类型, 默认上 boolean = true 会被默认映射成 -1(int), 在 C 中打印出来是 255. 所以我一般在定义接口时, 会避免使用 bool . 如果想自定义 boolean 映射, 可以参考 JNA maps Java boolean to -1 integer?

代码示例

本代码示例基于 64位 win10 + vs 2019

如果遇到 dll 依赖问题( UnsatisfiedLinkError ), 请下载 micro soft vc 运行库 或者使用 Dependency walker 查看 dll 依赖缺失情况

可以 clone 一下我的项目:

java 部分: https://github.com/giraffe-tree/jna-func

c++ 部分(vs 项目): https://github.com/giraffe-tree/jna-c

max

先来个简单的例子

先将下面的依赖加入你的 java maven 项目

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>5.3.1</version>
</dependency>
// c++  需要在 vs 中编译成 dll
int max(int num1, int num2) {
    return num1 > num2 ? num1 : num2;
}
// java
public interface JnaLibrary extends Library {
     // JNA 为 dll 名称
    JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class);
    int max(int a, int b);
}

将编译好的 JNA.dll 放入 resources 文件夹下的 win32-x86-64 目录中 (我使用的是 64位 windows), JNA 会自动到 win32-x86-64 中去找 JNA.dll

运行 main 函数即可

public static void main(String[] args) {
    int max = JnaLibrary.INSTANCE.max(100, 200);
    // out: 200
    System.out.println(max);
}

primitive array

 // java
public interface JnaLibrary extends Library {
     // JNA 为 dll 名称
    JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class);
    void testArray(short[] vals, int len);
}
// c++
void testArray(uint16_t* vals, int len) {
    for (int j = 0; j < len; j++) {
        printf("vals[%d]: %d \n", j, vals[j]);
    }
}
JnaLibrary.INSTANCE.testArray(new short[]{1, 2, 3, 4}, 4);
// out:
vals[0]: 1 
vals[1]: 2 
vals[2]: 3 
vals[3]: 4 

值传递与引用传递

通过值传递对象的时候需要注意

  1. 对象需要继承 Structure , 且它的属性必须为 public
    • Structure fields corresponding to native struct fields must be public. If your structure is to have no fields of its own, it must be declared abstract.
  2. JNA 有时候会判断错误, 导致原本的值传递, 变成引用传递, 从而报出Invalid memory access 的异常, 这时候最好实现一下 Structure.ByValue 接口
  1. FieldOrder 需要按顺序写, 否则会报出 Invalid memory access
// java
public interface JnaLibrary extends Library {
     // JNA 为 dll 名称
    JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class);
    // 实际测试下来 void printUser(User.ByValue user); 也是可以的
    void printUser(User user);
    void printUserRef(User user);

@Structure.FieldOrder({"name", "height", "weight"})
public static class User extends Structure {

public static class UserValue extends User implements Structure.ByValue {

    public UserValue(String name, int height, double weight) {
        super(name, height, weight);
        }
    }

    public User(String name, int height, double weight) {
        this.name = name;
        this.height = height;
        this.weight = weight;
    }

        public String name;
        public int height;
        public double weight;
    }
}
// .h
struct User {
    char* name;
    int height;
    double weight;
};
void printUser(User user);
void printUserRef(User& user);

// cpp
void printUser(User user) {
    printf("printUser user: %s height: %d weight: %.2f \n", user.name, user.height, user.weight);
}
void printUserRef(User& user) {
    printf("printUserRef user: %s height: %d weight: %.2f \n", user.name, user.height, user.weight);
}
JnaLibrary.User.UserValue user1 = new JnaLibrary.User.UserValue("user1", 186, 65.2);
JnaLibrary.INSTANCE.printUserRef(user1);
JnaLibrary.INSTANCE.printUser(user1);
// out:
printUserRef user: user1 height: 186 weight: 65.20 
printUser user: user1 height: 186 weight: 65.20

Pointer

// java
public interface JnaLibrary extends Library {
    // JNA 为 dll 名称
    JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class);
    
    void testStruct(ArrInfo arrInfo);
    
    @Structure.FieldOrder({"vals", "len"})
        public static class ArrInfo extends Structure {
            public Pointer vals;
            public int len;

            public ArrInfo(Pointer vals, int len) {
            this.vals = vals;
            this.len = len;
        }
    }
}
// .h
struct ArrInfo
{
    uint16_t* vals;
    int len;
};
void testStruct(ArrInfo arrInfo);

// cpp
void testStruct(ArrInfo arrInfo) {
    for (int j = 0; j < arrInfo.len; j++) {
        printf("arrInfo[%d]: %d \n", j, arrInfo.vals[j]);
    }
}
// java main test
int len = 3;
int shortSize = Native.getNativeSize(Short.class);
Pointer pointer = new Memory(len * shortSize);
for (int i = 0; i < len; i++) {
    pointer.setShort(shortSize * i, (short) i);
}
JnaLibrary.ArrInfo arrInfo = new JnaLibrary.ArrInfo(pointer, len);
JnaLibrary.INSTANCE.testStruct(arrInfo);
// out
arrInfo[0]: 0 
arrInfo[1]: 1 
arrInfo[2]: 2 

关于 JNA 调试

虽然 JNA 相比于 JNI 好用很多, 但我在使用的过程中还是遇到一些"坑"

这些 bug 常常很难直接找出, JNA 统一都报了一个 Invalid memory access , 导致我们找不到真正错误的原因. 这时候就需要调试了

我使用的开发环境是 IDEA + jdk8 + VS2019

步骤

先简单讲下步骤, 具体图文可以看下面的实战

  1. 通过 vs 编译 debug 版本的 dll , 放入 java 项目指定的目录下 (我这里是resources/win32-x86-64/JNA.dll)
  2. 启动 java 程序, 停止在你指定的断点上
  3. jps -l 找到 java 程序的 pid
  4. 在 vs 中 ctrl+alt+p 附加到进程, 选择刚刚找到的 pid, 点击附加
    • 其实一般是最上面的那个 java 进程, 就是你刚刚运行起来的那个
    • 点击 j 可以快速查找
  5. 在 vs 中设置一个断点
  6. 在 idea 中继续debug, 它会跳到 vs 的debug 界面中
  7. 在 vs 中点击继续就可以接着调试啦

实战

在 idea 中进入调试

1572006969955

在下图中, jps -l 找到 java 程序的 pid = 10560

1572006665036

在 vs 中 ctrl+alt+p 附加到进程, 选择刚刚找到的 pid, 点击附加

1572006913302

此时断点不会命中

1572008782675

接着在 idea 中点击继续, 然后回到 vs 界面, 我们发现 断点被触发了

1572008818053

查看局部变量

1572008861802

yeah, 调试完成 =.=

其他问题

C++ 中 printf 控制台打印缺失

目前测试下来, 可能存在 java 主线程停止, 但 C++ print 缓存区未被清空/不输出的情况, 导致控制台打印内容缺失

在这种情况下, 请延长 java 主线程运行的时间.

在C++调试时, 遇到未加载 jvm.pdb

目前测试下来, 在 vs 中 debug 继续后会报出一个 未加载 jvm.pdb , 我确实没有找到这个文件

但还可以继续调试, 没啥大问题

不过什么时候来个调试 openjdk 想想应该蛮有趣的 哈哈

Invalid memory access

这个问题怎么说呢, JNA 好多地方都能报出这个错误, 我遇到这个错误时, 大部分都是我的java 参数 -> C++ 参数的映射问题

包括参数类型写错, 顺序写错等, 仔细检查映射关系就能解决

dll 兼容性问题

java.lang.UnsatisfiedLinkError: 找不到指定的模块。

这个错误我遇到过的情况分两种

  1. 我调用的 dll 没有放进指定目录
    • 比如我的平台是 win32-x86-64, 但是没有放入这个目录中
    • 这种情况下, jna 会提示找不到指定目录的资源文件比较好解决
  2. 我调用的 dll 依赖的其他 dll 没有找到
    • 这个情况, 我在 win10 下编译, 然后在 windower server 2012 上运行时遇到过
    • 通过解决方案是这样
      1. 通过 Dependency walker 查看 dll 依赖缺失情况
      2. 补全缺失的 dll
    • 当时我的情况是 缺失了 vc140, 然后我下载 micro soft vc 运行库 就解决了这个问题

个人网站文章链接: https://giraffetree.me/2019/10/28/jna_tutorial/ 欢迎留言~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容