驽马十驾 驽马十驾

驽马十驾,功在不舍

目录
Java调用C/C++的利器--JNA
/      

Java调用C/C++的利器--JNA

开篇

在我们 Java 开发中,有些时候会涉及到跨语言的调用,比如涉及到一些高效计算、图形渲染、加密和解密的时候会用到 C++ 编写的程序,大部分情况我们都是利用 JNI 来调用 C++ 的 dll 或者 so,其实随着技术发展,更简单和易用的 JNA 应用而生。

JNI 和 JNA 区别

JNI 的使用

Java 调用 C++ 编写的。dll/.so 文件,可以使用传统的 JNI 调用。

  • 使用 SUN 规定的数据结构替代 C 语言的数据结构,调用已有的 dll/so 中公布的函数。
  • 在 Java 中载入这个适配器 dll/so,再编写 Java Native 函数作为 dll/so 中函数的代理。

经过 2 个繁琐的步骤才能在 Java 中调用本地代码。

因此,很少有 Java 程序员愿意编写调用 dll/.so 库中的原生函数的 Java 程序。这也使 Java 语言在客户端上乏善可陈。可以说 JNI 是 Java 的一大弱点!

如果你想具体了解 JNI 如何使用,可以参考这篇博客:https://blog.csdn.net/jiangwei0910410003/article/details/17465085

我相信你看了后,会有砸键盘的冲动。

其实正是因为 JNI 的复杂,所以有了更加简单易用的 JNA

JNI 和 JNA 的区别

其区别主要有:

  • JNA 为 Java 程序提供一种以纯 Java 编写调用本地共享类库的简便方法,不需要接触 JNI 或者本地代码。JNA 功能同 Windows 的平台调用和 Python 中的 ctypes 相似。
  • JNA 允许你使用 Java 方法调用来直接调用本地方法。JNA 编写的 Java 方法调用接口看起来像本地接口。大多数调用不需要特殊处理和配置,不需要模板文件和代码生成。
  • JNA利用一小部分 JNI 库来实现动态调用本地方法。开发者使用一个 Java 接口来描述目标本地库的方法和结构。这使得利用本地代码的利用变得更为简单,并且不会带来多种配置和生成多个平台的 JNI 代码的开销。可以从这里找到技术细节
  • JNA把性能、正确性和易用性当作首要目标。除此以外,JNA 包含一个平台相关的类库,这个类库包含已经映射好的本地方法调用和许多便利本地方法调用的公用接口。
  • JNA 是一个成熟的类库,被上百个商业软件和开源软件所使用。

使用:核心内容

首先推荐这个网站:https://www.eshayne.com/jnaex/index.html

上述网站可以找到 C 中结构 和 Java JNA 中类的对应关系,可以借助查阅这个文档,实现类型映射构建对应实体类

废话不多说我们先来一个代码解解渴:

假如有一个 C 的接口如下所示:

int increase(int val)
{
	return val + 1;
}

其编译后的文件为 say.dll 或者 say.so

如果使用 JNA 需要先引入 pom.

<dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <version>4.5.1</version>
        </dependency>

然后构建一个接口:

import com.sun.jna.Library;
import com.sun.jna.Native;

/**
 * 测试JNA调用C的接口
 *
 * @author LiuChunfu
 * @date 2018/5/27
 */
public interface SayLibrary extends Library {

    /////////// 如下为接口 //////////
    /**
     * 根据C提供的接口构造的接口
     *
     * @param val
     * @return
     */
    int increase(int val);


    /////////// 如下为调用入口 ///////////
    /**
     * 需要将C编译的dll 或者so 文件放入Java运行目录。
     */
    SayLibrary LIBRARY = (SayLibrary) Native.loadLibrary("say", SayLibrary.class);
}

在使用的时候可以如下使用:

public static void main(String[] args) {
        // 通过接口的入口 LIBRARY进行调用
        int result = SayLibrary.LIBRARY.increase(1);
        System.out.println(result);
    }

需要说明的是:

  1. 如果你不知道 so 或者 dll 需要放在哪里,可以将其放在: src/main/resources 理论上就可以成功调用了。
  2. 同时你也可以通过代码  System.getProperty("java.library.path"); 输出的路径中选择任一路径来进行放置。

关于 dll 或者 so 文件存放路径可以参考:https://blog.csdn.net/daylight_1/article/details/70199452

总结

  1. 构建 1 个接口,在其中映射出正确的形参,参考地址再列出下:https://www.eshayne.com/jnaex/index.html
  2. 然后通过一个模板代码构建调用连接点:
/////////// 如下为调用入口 ///////////
    /**
     * 需要将C编译的dll 或者so 文件放入Java运行目录。
     */
    SayLibrary LIBRARY = (SayLibrary) Native.loadLibrary("say", SayLibrary.class);
  1. 通过接口中的连接点 LIBRARY 进行相应方法的调用。

其实 JNA 最难的一点是构建形参,特别是有指针、结构体等等包裹的时候,如果可能建议和 C 接口编写人沟通下,确定下,形参尽量简单。

骐骥一跃,不能十步。驽马十驾,功在不舍。