Java通过JNA方式调用DLL(动态链接库)

1. JNA简单介绍

先说JNI(Java Native Interface)吧,有过不同语言间通信经历的一般都知道,它允许Java代码和其他语言(尤其C/C++)写的代码进行交互,只要遵守调用约定即可。首先看下JNI调用C/C++的过程,注意写程序时自下而上,调用时自上而下。

java调用soap java调用so动态库函数_数据类型

 

可见步骤非常的多,很麻烦,使用JNI调用.dll/.so共享库都能体会到这个痛苦的过程。如果已有一个编译好的.dll/.so文件,如果使用JNI技 术调用,我们首先需要使用C语言另外写一个.dll/.so共享库,使用SUN规定的数据结构替代C语言的数据结构,调用已有的 dll/so中公布的函 数。然后再在Java中载入这个库dll/so,最后编写Java native函数作为链接库中函数的代理。经过这些繁琐的步骤才能在Java中调用 本地代码。因此,很少有Java程序员愿意编写调用dll/.so库中原生函数的java程序。这也使Java语言在客户端上乏善可陈,可以说JNI是 Java的一大弱点!

JNA(Java Native Access)是一个开源的Java框架,是Sun公司推出的一种调用本地方法的技术,是建立在经典的JNI基础之上的一个框架。之所以说它是JNI的替 代者,是因为JNA大大简化了调用本地方法的过程,使用很方便,基本上不需要脱离Java环境加粗样式就可以完成。

如果要和上图做个比较,那么JNA调用C/C++的过程大致如下:

java调用soap java调用so动态库函数_java调用soap_02

 

 

可以看到步骤减少了很多,最重要的是我们不需要重写我们的动态链接库文件,而是有直接调用的API,大大简化了我们的工作量。
JNA只需要我们写Java代码而不用写JNI或本地代码。功能相对于Windows的Platform/Invoke和Python的ctypes。

2. JNA技术原理

JNA使用一个小型的JNI库插桩程序来动态调用本地代码。开发者使用Java接口描述目标本地库的功能和结构,这使得它很容易利用本机平台的功能,而不会产生多平台配置和生成JNI代码的高开销。这样的性能、准确性和易用性显然受到很大的重视。
此外,JNA包括一个已与许多本地函数映射的平台库,以及一组简化本地访问的公用接口。

注意:JNA是建立在JNI技术基础之上的一个Java类库,它使您可以方便地使用java直接访问动态链接库中的函数。

原来使用JNI,你必须手工用C写一个动态链接库,在C语言中映射Java的数据类型。
JNA中,它提供了一个动态的C语言编写的转发器,可以自动实现Java和C的数据类型映射,你不再需要编写C动态链接库。
也许这也意味着,使用JNA技术比使用JNI技术调用动态链接库会有些微的性能损失。但总体影响不大,因为JNA也避免了JNI的一些平台配置的开销。

3. JNA简单使用

JNA把一个.dll/.so文件看做是一个Java接口,下面以一个简单的实例来说明怎么使用。

当然要从最经典的HelloWorld开始,我们调用C的printf函数打印出“HelloWorld”(官方的例子),前提是已将jar包加入你的classpath。

package com.sun.jna.examples;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
/** Simple example of JNA interface mapping and usage. */
public class HelloWorld {
    // This is the standard, stable way of mapping, which supports extensive
    // customization and mapping of Java to native types.
    public interface CLibrary extends Library {
        CLibrary INSTANCE = (CLibrary)Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),CLibrary.class);
        void printf(String format, Object... args);
    }
    public static void main(String[] args){
        CLibrary.INSTANCE.printf("Hello, World\n");
        for (int i=0;i < args.length;i++) {
            CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
        }
    }
}

运行程序 执行结果

(1)需要定义一个接口,继承自Library 或StdCallLibrary默认的是继承Library ,如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary,比如众所周知的kernel32库。比如上例中的接口定义:

public interface CLibrary extends Library {}

(2)接口内部定义 接口内部需要一个公共静态常量:INSTANCE,通过这个常量,就可以获得这个接口的实例,从而使用接口的方法,也就是调用外部dll/so的函数。

CLibrary INSTANCE = (CLibrary)Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),CLibrary.class);
接口中只需要定义你要用到的函数或者公共变量,不需要的可以不定义,如上例只定义printf函数:void printf(String format, Object... args);
注意参数和返回值的类型,应该和链接库中的函数类型保持一致。

(3)调用链接库中的函数定义好接口后,就可以使用接口中的函数即相应dll/so中的函数了,前面说过调用方法就是通过接口中的实例进行调用,非常简单,如上例中:

CLibrary.INSTANCE.printf("Hello, World\n");for (int i=0;i < args.length;i++) {CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);}

这就是JNA使用的简单例子,可能有人认为这个例子太简单了,因为使用的是系统自带的动态链接库,应该还给出一个自己实现的库函数例子

4. 使用Visual Studio创建一个自定义dll


创建动态链接库

java调用soap java调用so动态库函数_Java_03

 

 

java调用soap java调用so动态库函数_Java_04

 

 添加一个头文件

java调用soap java调用so动态库函数_java调用soap_05

 

 

java调用soap java调用so动态库函数_java调用soap_06

 

 编写两个接口和实践

java调用soap java调用so动态库函数_java调用soap_07

 

 

#include "pch.h"
extern "C" _declspec(dllexport) int add(int a, int b);
int add(int a, int b)
{return a + b;
}extern "C" _declspec(dllexport) int substract(int a, int b);
int substract(int a, int b) {return a - b;
}

点击调试按钮 在这里请注意 java的jdk版本是32位还是64位 需要和生成的dll文件一样

java调用soap java调用so动态库函数_Java_08

java调用soap java调用so动态库函数_java调用soap_09

 

 然后运行成功会控制台提示成功 然后去项目下拿到生成的dll文件

java调用soap java调用so动态库函数_数据类型_10

4. Java通过JNA方式调用DLL文件

pom文件依赖

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>4.5.2</version>
</dependency>
package XXX;
import com.sun.jna.Library;
import com.sun.jna.Native;
public class TestDll {
    public interface DLibrary extends Library {
        //此处我的jdk版本为64位,故加载64位的Dll
        DLibrary INSTANCE = (DLibrary) Native.loadLibrary("D:\\dll\\source\\repos\\Dll1\\x64\\Debug\\Dll1.dll",DLibrary.class);
        //Dll2x64中定义的函数
        int add(int a,int b);
        int substract(int a, int b);
    }
    public static void main(String[] args) {
        int add = DLibrary.INSTANCE.add(3,4);
        int substract = DLibrary.INSTANCE.substract(3,4);
        System.out.println("a+b="+add);
        System.out.println("a-b="+substract);
    }
}

运行mian方法返回结果

java调用soap java调用so动态库函数_动态链接库_11

 

 到此java调用自定方法的dll文件完成 需要注意java定义接口的方法一定要和dll文件定义的方法名一样
再就是注意自己的jdk和dll文件生成的是32位还是64位 需要调用的DLL(注意JDK版本位数要与DLL一致)

java调用soap java调用so动态库函数_Java_12

4…Java和C的数据类型对照表

Java中的数据类型和C中的数据类型声明关键字有所不同,在java代码中需要使用Java类型来代替C类型,下表为Java类型和C类型对照表

java调用soap java调用so动态库函数_java调用soap_13