c#结构体4字节对齐_当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.ByRefere 结构体传递
如果需要在 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[] 回调函数
我们有时候会在 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); }
java jna定义该回调接⼝的实现:
复制代码
public class testCallbackImpl implements testCallback { @Override public int invoke(String name) { System.out.printf("Invoke Callback " + name + " successfully! test_myFun 函数 Java 端定义:
复制代码
int testMyFun(Callback cb, String name, int flag);
调⽤实例:
复制代码
int rc = testMyFun(new testCallbackImpl(), "helloworld", 1);
总结
其实我们可以看到,JNA 使⽤的主要难点在于结构体定义和传递,只要弄清楚如何对付结构体,剩下的事情也就⽔到渠成了。说了这么多,其实就想告诉⼤家,在做跨语⾔调⽤时,尽量还是要封装⼀下函数、结构体,让数据传递时更为简单。
转发 +关注 回复头条666 领取资料
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论