对,不再手动封装JNI接口,试试JNA吧

最近团队内算法的同学对人脸算法进行封装,提供了so库给到业务团队使用,以替换外部供应商的算法库。

就当我们以为万事俱备只差接入的时候,业务团队反馈没有做过JNI的封装,且研发资源比较吃紧,需要我们出资源来解决,所以只能自己顶上。

也是在这个过程中了解到JNA的存在,能够在不需要JNI or native code的情况下更简单的使用本地库。

JNA简介

JNA(Java Native Access)是一个Java工具,它允许Java程序无需编写JNI或本地代码就能直接调用本地共享库中的函数。通过使用Java接口描述本地库的函数和结构,JNA简化了对本地平台特性的访问,同时注重性能和易用性。

从整体实现上来看,JNA使用一个小的 JNI(Java Native Interface)库存根动态调用本地代码,开发者使用 Java 接口来描述目标本地库中的函数和结构。这样,开发者可以很容易地利用本地平台的特性,而无需承担为多个平台配置和构建 JNI 代码的高开销。

目前JNA已经是一个相对成熟的Java库,非常多的开发者、商业/非商业项目已经在使用,其中也不乏大家熟知的组件/工具,具体可以参考官方主页。java-native-access/jna: Java Native Access

image.png

使用方法

整个使用过程非常简单,下面就通过一个例子简单了解一下。

本地库加载

首先需要声明一个接口类,并动态加载对应的so库。

 

swift

代码解读

复制代码

import com.sun.jna.Library; public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) Native.load("cvAPI", CLibrary.class); }

PS:如库名为libcvAPI,则库加载时的名字为cvAPI

整理了这份面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记【点击此处】即可免费获取

接口声明

然后针对我们要调用so库中的接口进行声明。

举个例子,假设本地库中的方法如下:

 

csharp

代码解读

复制代码

//C++ int InitAll();

则转换后的接口声明如下:

 

csharp

代码解读

复制代码

//Java public int InitAll();

方法调用

方法调用时就非常简单了,直接通过接口中的常量对象进行方法调用即可:

 

ini

代码解读

复制代码

int result = CLibrary.INSTANCE.InitAll()

完整代码

 

csharp

代码解读

复制代码

public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) Native.load("cvCAPI", CLibrary.class); public int InitAll(); } int result = CLibrary.INSTANCE.InitAll();

看到这里,是不是觉得整个过程非常简单呢?

那么,接下来让我们看看各种数据类型要如何映射使用,“困难模式”启动~

基础类型映射

上面的简单例子并不涉及不同类型的对象传递,当需要做基础类型值传递时就需要做映射转换了。

image 2.png

上图是官方给出的基础类型映射关系,能覆盖常用的类型映射,实际使用时也需对照映射表确认,其中还是有一些坑点的,比如long映射到NativeLonglong long才是映射到的long、enum枚举映射到的是int等。

数组映射

在我们的场景中,有一个512维的数据用数组保存,转换前后的定义如下:

 

arduino

代码解读

复制代码

//转换前(c++) float feat[MAX_FEATURE_LEN]; void fill_buffer(int *buf, int len); void fill_buffer(int buf[], int len); //转换后(java) float[] feat = new float[MAX_FEATURE_LEN]; void fill_buffer(int[] buf, int len);

结构体

JNA中进行结构体的映射,是通过继承com.sun.jna.Structure来实现的,通过结构体中的元素映射顺序通过注解com.sun.jna.Structure@FieldOrder来实现。

举个栗子,

 

arduino

代码解读

复制代码

typedef struct _CUFeature { unsigned long long id; float feat[MAX_FEATURE_LEN]; int len; } CUFeature;

转换后的Java中的类定义,

 

scala

代码解读

复制代码

@Structure.FieldOrder({"id", "feat", "len"}) public static class CUFeature extends Structure { public long id; public float[] feat = new float[MAX_FEATURE_LEN]; public int len; }

类型指针

既然是C++的代码,那一定逃不出指针的魔掌,针对基础类型指针,已经有明确的定义。

 

arduino

代码解读

复制代码

//C++ void allocate_buffer(char **bufp, int* lenp); int DetectFeatureCount(int* outCount); //JAVA void allocate_buffer(PointerByReference bufp, IntByReference lenp); int DetectFeatureCount(IntByReference outCount);

结构体指针

这个部分也是卡住最近的一个问题,在GitHub官方文档首页的介绍中,没有看到这个部分的说中,最终是在jna/www/FrequentlyAskedQuestions.md中找到,记录了开发者常见问题,其中就有对结构体指针的使用说明。

当需要使用结构体指针时,可以在JAVA类定义中增加下面的内容:

 

scala

代码解读

复制代码

public class MyStructure extends Structure { public static class ByValue extends MyStructure implements Structure.ByValue { } public static class ByReference extends MyStructure implements Structure.ByReference { } }

其中MyStructure.ByReference映射的就是对应结构体的指针。 可以看看官方自带的几个例子,描述的其实也比较清楚。

 

c

代码解读

复制代码

typedef struct _simplestruct { int myfield; } simplestruct; typedef struct _outerstruct { simplestruct nested; // use Structure } outerstruct; typedef struct _outerstruct2 { simplestruct *byref; // use Structure.ByReference } outerstruct2; typedef struct _outerstruct3 { simplestruct array[4]; // use Structure[] } outerstruct3; typedef struct _outerstruct4 { simplestruct* ptr_array[4]; // use Structure.ByReference[] } outerstruct4; // Field is a pointer to an array of struct typedef struct _outerstruct5 { simplestruct* ptr_to_array; // use Structure.ByReference, and use // Structure.toArray() to allocate the array, // then assign the first array element to the field } outerstruct5; // struct pointers as return value or argument simplestruct *myfunc(); // use Structure void myfunc(simplestruct* data); // use Structure void myfunc(simplestruct* data_array, int count); // use Structure[], and use Structure.toArray() to generate the array void myfunc(simplestruct** data_array, int count); // use Structure.ByReference[] // struct (by value) as return value or argument // use Structure.ByValue simplestruct myfunc(); void myfunc(simplestruct);

总结

以上就是JNA的一些基础使用方法,也是在这次实践中要到的一些基础内容。

从实际使用情况来看,对比之前JNI封装的方式,开发效率会高很多,但由于我们的使用场景中,对性能的要求不高,所以没有测试这个部分的性能损耗,实际上肯定还是有影响的,毕竟官方自己也做了描述。

image 3.png

有兴趣的大佬可以实测一下,一起交流。

封装 C 程序的 JNI(Java Native Interface)接口,你可以按照以下步骤进行操作: 1. 创建一个 C 文件,编写你想要封装的 C 函数的实现代码。确保该函数具有正确的参数和返回值类型。 2. 在 C 文件中包含 `jni.h` 头文件,该头文件包含了 JNI 接口的定义。 3. 在 C 文件中实现一个名为 `Java_类名_方法名` 的函数,该函数将作为 JNI 接口方法的入口点。这里的 `类名` 是你要封装的 Java 类的全名,`方法名` 是你要封装的 Java 方法的名称。 4. 在 `Java_类名_方法名` 函数中,使用 JNI 函数调用来处理 Java 方法传递过来的参数和返回值。可以通过 JNI 函数获取 Java 方法的参数和调用相应的 C 函数进行处理。 5. 在你的 C 代码中使用 `JNIEnv` 指针来访问 JNI 接口函数。可以使用 `JNIEnv` 指针调用 JNI 函数来获取 Java 对象、调用 Java 方法等。 6. 编译你的 C 代码为共享库(例如,动态链接库或静态链接库),确保使用正确的编译选项和链接选项。 7. 在 Java 代码中使用 `System.loadLibrary` 方法加载你的共享库。加载后,你就可以在 Java 代码中调用封装的 C 函数了。 请注意,JNI 接口是与平台相关的,因此你需要针对不同的操作系统和架构编译和配置你的 C 代码。 这是一个简单的示例,演示了如何封装一个 C 函数来计算两个整数的和: ```c #include <jni.h> JNIEXPORT jint JNICALL Java_com_example_MyClass_add(JNIEnv *env, jobject obj, jint a, jint b) { return a + b; } ``` 在这个示例中,我们封装了一个名为 `add` 的函数,并将其作为 `com.example.MyClass` 类的方法。在 Java 代码中,你可以通过创建 `MyClass` 类的实例并调用 `add` 方法来访问该函数。 这只是一个简单的示例,你可以根据你的需求扩展和修改这个示例来封装更复杂的 C 函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值