QCon 全球软件开发大会(北京站)门票 9 折倒计时 4 天,点击立减 ¥880 了解详情
写点什么

当 Java 遇上 C++: 使用 JNA 传递复杂数据结构

2019 年 11 月 12 日

当Java遇上C++: 使用JNA传递复杂数据结构

最近在 UMStor 的开发过程中,需要写一个 C/C++ 库的 Java SDK。试想,如果用 Java 完完全全重新写一个对应的 SDK,不免工作量太大,于是我搜了一下,是否有可能让 Java 访问 C/C++库中的接口(.dll, .so)。


JNI

JNI (Java Native Interface) 是一种技术,通过这种技术可以做到以下两点:


  • Java 程序中的函数可以调用 Native 语言写的函数,Native 一般指的是 C/C++ 编写的函数。

  • Native 程序中的函数可以调用 Java 层的函数,也就是在 C/C++ 程序中可以调用 Java 的函数。


我们都知道承载 Java 世界的虚拟机是用 Native 语言写的,而虚拟机又运行在具体平台上,所以虚拟机本身无法做到平台无关。然而,有了 JNI 技术,就可以对 Java 层屏蔽具体的虚拟机实现上的差异了。这样,就能实现 Java 本身的平台无关特性。其实 Java 一直在使用 JNI 技术,只是我们平时较少用到罢了。


JNI 的使用并不简单,如果已有一个编译好的 .dll/.so 文件,如果使用 JNI 技术调用,我们首先需要使用 C 语言另外写一个 .dll/.so 共享库,使用 SUN 规定的数据结构替代 C 语言的数据结构,调用已有的 dll/so 中公布的函数。然后再在 Java 中载入这个库 dll/so,最后编写 Java native 函数作为链接库中函数的代理。经过这些繁琐的步骤才能在 Java 中调用本地代码。


JNA

JNA (Java Native Access) 是建立在 JNI 技术基础之上的一个 Java 类库,它使我们可以方便地使用 Java 直接访问动态链接库中的函数。我们不需要重写我们的动态链接库文件,而是有直接调用的 API,大大简化了我们的工作量。但是 JNA 一般只适用于较为简单的 C/C++ 库,如果接口、数据结构复杂的话就不推荐。而且 JNA 也只提供了 C/C++ 对 Java 的接口转化。


SWIG

SWIG ( Simplified Wrapper and Interface Generator ),是一款开源软件,其目的是将 C/C++ 编写的函数库封装成其他语言的接口,包括: Java, Python, Perl, Ruby, C#, PHP 等诸多主流编程语言。SWIG 底层仍然还是 JNI,如果我们只是要访问简单的 C/C++ 接口,那么 JNA 更合适;但是如果接口较为复杂,那 SWIG 就是最佳方案。


我所要面对的 C/C++ 库的接口比较复杂,用到了不少自定义的结构体,按理说我应该使用 SWIG 才对,不过由于一些原因,我还是使用了 JNA 来开发这个 Java SDK,算是给自己挖了一个不小的坑。


本文会着重介绍如何用 JNA 传递复杂的数据结构,因此 JNA 的入门教程在这里不再赘述,大家可以自行百度,应该一搜一大把。


基本类型对照表

下表是 JNA 官网给出的基本类型的 C 语言和 Java 类型对照表:



这张对照表一般已经能够满足跨平台、跨语言调用的数据类型转换需求,因为如果我们要做跨语言调用,应当尽量使用基本和简单的数据类型,而不要过多使用复杂结构体传递数据,因为 C 语言的结构体中,每个成员会执行对齐操作与前一个成员保持字节对齐,也就是说,成员的书写顺序会影响结构体占用的空间大小,因此会在 Java 端定义结构体时会造成不小的麻烦。


比如,如果跨语言调用的函数中参数包含 stat 这个结构体:我们都知道,stat 这个结构体是用来描述 linux 文件系统的文件元数据的基本结构,但麻烦的是,这个结构体的成员定义次序在不同的机型上并不相同,如果我们在 Java 端重写这个结构体,会产生兼容性问题。


结构体定义

有时候我们需要在 Java 端访问某个 C/C++ 结构体中的成员,我们就需要在 Java 端复写这个结构体,在复写的时候需要注意两点:


  • 需要在结构体定义中定义 2 个内部类 ByReference 和 ByValue,来实现指针类型接口和值类型接口;

  • 重写 getFieldOrder( ) 来告诉 C/C++ 的成员取值次序。


下面我们通过一个栗子来看一下在 Java 只不过怎么模拟定义一个 C/C++ 结构体:


C/C++代码


typedef struct A { B* b;  void* args; int len;};
typedef struct B { int obj_type;};
复制代码


Java 代码


/* 结构体A定义 */public static A extends Structure {
//构造函数定义 public A() { super(); }
public A(Pointer _a) { super(_a); }
//结构体成员定义 public B.ByReference b; PointerByReference args; int len;
//添加2个内部类,分别实现指针类型接口、值类型接口 public static class ByReference extends A implements Structure.ByReference {} public static class ByValue extends A implements Structure.ByValue{}
//定义取值次序,需要与C/C++中对齐,不然会出现NoSuchFieldError @Override protected List getFieldOrder() { return Arrays.asList(new String[]{"b", "args", "len"}); }}
/* 结构体B定义 */public static B extends Structure { public B() { super(); }
public B(Pointer _b) { super(_b); }
int obj_type;
public static class ByReference extends B implements Structure.ByReference {} public static class ByValue extends B implements Structure.ByValue{}
@Override protected List getFieldOrder() { return Arrays.asList(new String[]{"obj_type"}); }}
复制代码


结构体传递

如果需要在 Java 端访问某个结构体的成员,需要使用 ByReference (指针、引用)或是 ByValue(拷贝参数);如果只是起到数据传递,不关心具体内部结构,可以使用 PointerByReference 和 Pointer。


test_myFun(struct A a)
test_myFun(A.ByValue a)

test_myFun(struct A *a)
test_myFun(A.ByReference a)

test_myFun(struct A **a)
test_myFun(A.ByReference[] a)

test_myFun(A a)
test_myFun(Pointer a)

test_myFun(A *a)
test_myFun(PointerByReference a)

test_myFun(A **a)
test_myFun(PointerByReference[] a)
复制代码


回调函数

我们有时候会在 C/C++ 中加一个带回调函数的函数,例如:


typedef bool (*test_cb)(const char *name);
int test_myFun(test_cb cb, const char *name, uint32_t flag);
复制代码


如果我们需要在 Java 端调用 test_myFun 函数,则我们需要在 Java 端定义一个与 test_cb 相同的回调接口:


public interface testCallback extends Callback {  //invoke对应test_cb,注意参数顺序需要保持一致 boolean invoke(String name); }
复制代码


定义该回调接口的实现:


public class testCallbackImpl implements testCallback { @Override  public int invoke(String name) {  System.out.printf("Invoke Callback " + name + " successfully!");  return true;  } }
复制代码


test_myFun 函数 Java 端定义:


int testMyFun(Callback cb, String name, int flag);
复制代码


调用实例:


int rc = testMyFun(new testCallbackImpl(), "helloworld", 1);
复制代码


总 结

其实我们可以看到,JNA 使用的主要难点在于结构体定义和传递,只要弄清楚如何对付结构体,剩下的事情也就水到渠成了。说了这么多,其实就想告诉大家,在做跨语言调用时,尽量还是要封装一下函数、结构体,让数据传递时更为简单。


本文转载自公众号 UCloud 技术(ID:ucloud_tech)。


原文链接:


https://mp.weixin.qq.com/s/8UCivb74OUAPcV93cuvDVw


2019 年 11 月 12 日 15:271417

评论

发布
暂无评论
发现更多内容

Linux入门篇 —— Linux 用户与组管理详解(system-config-users && 命令行)

若尘

Linux 命令行 用户

探索图神经网络的网络架构和训练方法

华为云开发者社区

神经网络 AI 图神经网络 网络架构 GNNs

紧急寻人,还缺75万!区块链产业为何“一才难求”?

CECBC区块链专委会

区块链人才

刷了一个月leetcode算法,成功收下阿里巴巴、网易等大厂的offer

程序员改bug

架构 算法

GitHub上连夜被下架!阿里巴巴2021版JDK源码笔记(2月第三版)

Java架构追梦

Java 阿里巴巴 jdk源码 金三银四 跳槽面试

公链,区块链的未来和归宿

CECBC区块链专委会

区块链

日记 2021年2月27日(周六)

Changing Lin

2月春节不断更

观点 | 破解云管理平台在数据中心管理体系中定位模糊的困局

BoCloud博云

云计算 PaaS 服务目录 多云管理平台 数据中心管理

七年阿里升级路,熬到P7,想给正在成长的Java程序员一点建议

Java成神之路

Java 程序员 架构 面试 编程语言

涨薪50%,从小厂逆袭快手 - 附面经

haxianhe

面试

神操:凭借“阿里Java脑图”,成功斩获腾讯、蚂蚁、B站、字节、滴滴等5个Offer

Java架构师迁哥

多年阅读《经济学人》是一种什么体验?

wbliu85

深入解读华为云细粒度文本情感分析及其应用

华为云开发者社区

AI 华为云 情感分析 语言语义 文本情感分析

程序员成长第十四篇:做好时间管理(二)

石云升

时间管理 程序员成长 28天写作 2月春节不断更

2021年第一次凡尔赛!2个月肝完阿里技术官“亲码”Java核心思维脑图+核心知识整理,成功入职大厂

Java王路飞

Java 程序员 面试 项目经验 大厂Offer

金融数字化转型浪潮呼啸而来 大数据区块链落地场景全面开花

CECBC区块链专委会

金融

备战明年金三银四,阿里P8大佬总结的这份Java面试文档,你可一定要好好看看

Java成神之路

Java 程序员 架构 面试 编程语言

我看JAVA 之 枚举类型

awen

Java 源码 jdk 枚举

双指针高频面试题:「三数之和」的姐妹篇 ...

宫水三叶的刷题日记

LeetCode 数据结构与算法 面试数据结构与算法

Enterprise Tech30(2021)

行人23

Tech ET30

2021突击金三银四必备:BAT1000Java面试真题合集!

比伯

Java 编程 架构 面试 计算机

推荐程序员平时使用最多的绘图软件!!!

秦怀杂货店

软件 流程图 绘图

牛批!Github一夜爆火,阿里JDK源码小册2021全新开源!

程序员小毕

Java 源码 jdk 面试 并发

基于matlab的控制系统与仿真6-PID控制模型

AXYZdong

matlab 2月春节不断更

我看JAVA 之 Annotation

awen

Java 源码 jdk 注解 annotation

《经济学人》2021年2月27日刊精彩文章导读及资源下载

wbliu85

第十二周 作业

简简单单

第十二周 学习总结

简简单单

一波N折,3面+HR面成功入职蚂蚁金服实现“财务自由”,自曝狂刷N遍的面试题!

Java成神之路

Java 程序员 架构 面试 编程语言

面试不知道如何做准备? 来看看这一套BATJ最爱问的面试题精选吧

Java成神之路

Java 程序员 架构 面试 编程语言

你知道 HTTP 是如何使用 TCP 连接的吗?今天我就来告诉你

程序员改bug

Java 架构 HTTP

边缘计算隔离技术的挑战与实践

边缘计算隔离技术的挑战与实践

当Java遇上C++: 使用JNA传递复杂数据结构-InfoQ