背景
最近在做一个与硬件和第三方平台有关的项目,平台厂商扔过来一个DLL,但我方平台是Java编写的所以需要实现Java调用C的DLL。特此做了一些调研,现在记录一下。
主要实现途径
脚本
其实Java调用其他程序最简单的方式就是直接通过shell或是bat脚本调用,但这只局限于一些简单没有交互的应用,这里就不展开讨论。
JNI
简单介绍
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。 [1] 从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。
使用JNI往往意味着复杂性提高一个数量级,对于未来的扩展和维护都极为不便,但可能 是不得不使用这样的技术,原因很可能是受限的SDK或是极高的性能要求。PS要是C与Java的性能差了一个数量级,主要原因有两个,一种是可能是没有充分的预热,java还没有走JIT,另一种可能是代码本身写的有问题。
基本流程
静态注册
原理:根据函数名建立 Java 方法和 JNI 函数的一一对应关系。流程如下:
先编写 Java 的 native 方法;
然后用 javah 工具生成对应的头文件,执行命令 javah packagename.classname可以生成由包名加类名 命名的 jni 层头文件,或执行命名javah -o custom.h packagename.classname,其中 custom.h 为自定义的文件名;
实现 JNI 里面的函数,再在Java中通过System.loadLibrary加载 so 库即可;
动态注册
原理:直接告诉 native 方法其在JNI 中对应函数的指针。通过使用 JNINativeMethod 结构来保存 Java native 方法和 JNI 函数关联关系,步骤:
先编写 Java 的 native 方法;
编写 JNI 函数的实现(函数名可以随便命名);
利用结构体 JNINativeMethod 保存Java native方法和 JNI函数的对应关系;
利用registerNatives(JNIEnv* env)注册类的所有本地方法;
在 JNI_OnLoad 方法中调用注册方法;
在Java中通过System.loadLibrary加载完JNI动态库之后,会调用JNI_OnLoad函数,完成动态注册;
JNA
JNA是除了JNI在Java跨语言调用最为知名的库。JNA包括一个特定于平台的小型共享库,该库支持所有本机访问。由于实际项目使用的就是JNA,下面详细介绍一下JNA。
基本原理
也就是说JNA里面包括了一个DLL或是so库,你的JAVA代码调用JNA的jar包,这个jar包再去调用他的中间库,然后中间库再去处理真正的C/C++的库。
这么说起来是不是跟JNI的实现思路是一样,就是更加简单,不需要把你的java编译为头文件然后包含到对应库,而是借助中间库来实现。
默认类型对应
JNA面对的问题有一半是与类型有关的,所以在开发有关项目时搞清楚对应类型关系是十分重要的
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
这里面有一个特别的问题一定要注意。
C/C++中的char相当于Java中的byte。
C/C++中的char相当于Java中的byte。
C/C++中的char相当于Java中的byte。
这个问题特别恶心,Java中的char是0~255,C/C++的char是-127到128。所以千万不要用Java中的char[]来接C/C++中的char数组或是指针。
代码示例
我的库文件是放在工程目录的natives中,名字叫dll。为了隔离工具类,做了一个DemoService中间层。所有的内部服务都调用DemoService不直接与JNA有任何关系。还做了一整套的DTO来隔离类型上的耦合。
pom.xml
<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.5.0</version>
</dependency>
接口
public interface DemoLibrary extends Library {
DemoLibrary INSTANCE = Native.load("natives/dll", DemoLibrary .class);
int Do_Something();
}
1
2
3
4
中间Service
public class DemotService {
private static final String SDK_NAME = "dll";
private static volatile boolean initialized;
public TransportService() {
initialized = false;
}
@PostConstruct
public void init() {
DllUtil.loadNative(SDK_NAME);
initialized = true;
}
@PreDestroy
public void clear() {
}
public void do(){
getInstance().Do_Something();
}
private DemoLibrary getInstance() {
return DemoLibrar.INSTANCE;
}
}
工具类
package com.zw.ump.gateway4g.utils;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
/**
* 加载DLL库的工具类
* @author zew
*/
@Slf4j
public class DllUtil {
public synchronized static void loadNative(String nativeName) {
String systemType = System.getProperty("os.name");
String fileExt = (systemType.toLowerCase().contains("win")) ? ".dll" : ".so";
String path = System.getProperty("user.dir")+ File.separator+"natives"+File.separator+nativeName+fileExt;
File sdkFile = new File(path);
System.load(sdkFile.getPath());
log.info("------>> 加载SDK文件 :" + sdkFile + "成功!!");
}
}
效率问题
直接使用原生的方法
不要使用非映射类型,比如String,直接使用原生方法
Java原语数组通常比直接内存(指针,内存或ByReference)或NIO缓冲区使用速度慢
大型结构体也会有一定的性能问题
最后错误再抛出错误
官网
JNative
基本跟JNA十分类似。
基本流程
下载jnative。jar 及JNativeCpp.dll
将使用的dll文件及JNativeCpp.dll拷贝至系统system32下
写代码
public class JNativeTest {
// 1.实现demo.dll 文件接口
public interface DemoLibrary extends Library {
// 2.PegRoute.dll 中 HCTInitEx方法
public int do(int Version, String src);
}
public static void main(String[] args) {
//3.加载DLL文件,执行dll方法
DemoLibrary libary = (DemoLibrary) Native.loadLibrary("demo",
DemoLibrary.class);
if (libary!= null) {
System.out.println("DLL加载成功!");
int success = libary.do(0,"");
System.out.println("1.设备初始化信息!" + success);
}
}
}
结论
JNI没有额外的中间层,效率应该是最高的。当然我没有进行过多的调研和实际的基准测试,所以我只能说JNI应该比JNA效率高一点。
JNA经过了额外的中间库,但是由于不需要JAVA编译h导入库中过程,使用起来较为方便,对于只会Java的同学是最为合适的。
JNI用的人比较多,但相对来说比较麻烦要熟悉c并且要使用javac 及javah命令,同时JNI能做到JNA实现不了的那就是通过C也可以通过JNI来调Java的内容,JNA只能单向调用。
JNA与JNative类似,但感觉JNA可以更好的分层。而且JNative要下载单独的dll。而且JNative貌似很久不更新了,并不建议。
参考
https://blog.csdn.net/u011627980/article/details/51970658
https://www.jianshu.com/p/ac00d59993aa
https://baike.baidu.com/item/JNI/9412164
————————————————
版权声明:本文为CSDN博主「卡萨巴」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhaoenweiex/article/details/104051853
免责声明: | |
1、 | 资源售价只是赞助,不代表代码或者素材本身价格。收取费用仅维持本站的日常运营所需。 |
2、 | 本站资源来自用户上传,仅供用户学习使用,不得用于商业或者非法用途,违反国家法律一切后果用户自负。用于商业用途,请购买正版授权合法使用。 |
3、 | 本站资源不保证其完整性和安全性,下载后自行检测安全,在使用过程中出现的任何问题均与本站无关,本站不承担任何技术及版权问题,不对任何资源负法律责任。 |
4、 | 如有损害你的权益,请联系275551777@qq.com及时删除。 |