最近团队内算法的同学对人脸算法进行封装,提供了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
使用方法
整个使用过程非常简单,下面就通过一个例子简单了解一下。
本地库加载
首先需要声明一个接口类,并动态加载对应的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();
看到这里,是不是觉得整个过程非常简单呢?
那么,接下来让我们看看各种数据类型要如何映射使用,“困难模式”启动~
基础类型映射
上面的简单例子并不涉及不同类型的对象传递,当需要做基础类型值传递时就需要做映射转换了。
上图是官方给出的基础类型映射关系,能覆盖常用的类型映射,实际使用时也需对照映射表确认,其中还是有一些坑点的,比如long
映射到NativeLong
、long 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封装的方式,开发效率会高很多,但由于我们的使用场景中,对性能的要求不高,所以没有测试这个部分的性能损耗,实际上肯定还是有影响的,毕竟官方自己也做了描述。
有兴趣的大佬可以实测一下,一起交流。