Java调用C/C++的开发库的几种常见手段梳理

背景

最近在做一个与硬件和第三方平台有关的项目,平台厂商扔过来一个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 TypeSizeJava TypeCommon Windows Types
char8-bit integerbyteBYTE, TCHAR
short16-bit integershortWORD
wchar_t16/32-bit charactercharTCHAR
int32-bit integerintDWORD
intboolean valuebooleanBOOL
long32/64-bit integerNativeLongLONG
long long64-bit integerlong__int64
float32-bit FPfloat
double64-bit FPdouble
char*C stringStringLPCSTR
void*pointerPointerLPVOID, 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();
}
中间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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值