JNA 理论详解 1

JNA 简述

  • JNA 全称 Java Native Access (Java 本地访问),JNA 提供一组 Java 工具类用于在运行期间动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。
  • 开发人员只要在一个Java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。
  • JNA全称Java Native Access,是一个建立在经典的JNI(Java Native Interface-Java本地接口)技术之上的Java开源框架
  • Github托管地址:https://github.com/java-native-access/jna
  • JNA 包下载地址
  1. https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna/4.0.0/jna-4.0.0.jar

  2. https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna-platform/4.0.0/jna-platform-4.0.0.jar

C语言 与 Java

  • Windows底层主要使用C++编写以及还有C语言加汇编语言,Linux主要以C语言为主
  • dll 和 so 是 C函数的集合和容器,这与Java中的接口概念吻合,所以JNA把dll文件和so文件看成一个个接口。在JNA中定义一个接口就是相当于了定义一个DLL/SO文件的描述文件,该接口代表了动态链接库中发布的所有函数。而且,对于程序不需要的函数,可以不在接口中声明。
  • JNA 定义的接口一般继承 com.sun.jna.Library 接口,如果dll文件中的函数是以stdcall方式输出函数,那么该接口就应该继承com.sun.jna.win32.StdCallLibrary接口。
  • JNA难点在于学Java的人完全不知道电脑系统底层到底有哪些库?库叫什么名字?库中有哪些函数?函数是什么含义?等等

Java和C数据类型对照表

Java 类型

类型

原生表现

 

 boolean

 int

 32位整数(可定制)

 

 byte

 char 

 8位整数

 

 char

 wchar_t

 平台依赖

 

 short

 short

 16位整数

 

 int

 int

 32位整数

 

 long

long long, __int64

 64位整数

 

 float

 float

 32位浮点数

 

 double

 double

 64位浮点数

 

 Buffer/Pointer

 pointer

 平台依赖(3264位指针)

 

 <T>[] (基本类型的数组)

 pointer/array

3264位指针(参数/返回值)

邻接内存(结构体成员)

 

 String

 char*

/0结束的数组 (native encoding or jna.encoding)

 

 WString

 wchar_t*

 /0结束的数组(unicode)

 

 String[]

 char**

 /0结束的数组的数组

 

 WString[]

 wchar_t**

 /0结束的宽字符数组的数组

 

 Structure

 struct*/struct

指向结构体的指针(参数或返回值) (或者明确指定是结构体指针)结构体(结构体的成员) (或者明确指定是结构体)

 

 Union

union 

 等同于结构体

 

 Structure[]

 struct[]

 结构体的数组,邻接内存

 

 Callback

 <T> (*fp)()

 Java函数指针或原生函数指针

 

 NativeMapped

 varies

 依赖于定义

 

 NativeLong

 long

 平台依赖(3264位整数)

 

 PointerType

 pointer

 Pointer相同

入门示例

JNA 理论详解 1

示例代码

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]);
        }
    }
}
  • 运行程序,没带参数时只打印出“Hello, World”,带了参数时,则会打印出所有的参数。
  • 没有写一行 C 代码,就直接在 Java 中调用了系统动态链接库中的函数!

程序说明

自定义接口

  • public interface CLibrary extends Library {  .....
  • 自定义一个接口,继承Library 或 StdCallLibrary。默认的是继承Library ,如果动态链接库里的函数是以stdcall方式输出的,那么就继承StdCallLibrary,比如众所周知的kernel32库

接口内部定义

  • Clibrary INSTANCE = (Clibrary) Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),  CLibrary.class);
  • 接口内部需要一个公共静态常量:INSTANCE,通过这个常量,就可以获得这个接口的实例,从而使用接口的方法,也就是调用外部 dll/so 的函数
  • INSTANCE 常量通过 Native.loadLibrary() API函数获得,该函数有2个参数:
  1. 第一个参数是动态链接库dll/so的名称,但不带.dll或.so这样的后缀,这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。搜索动态链接库路径的顺序是:先从当前类的当前文件夹找,如果没有找到,再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,如果找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。比如上例中printf函数在Windows平台下所在的dll库名称是"msvcrt",而在其它平台如Linux下的so库名称是"c"。
  2. 第二个参数是自定义接口的Class类型。JNA通过这个Class类型,根据指定的.dll/.so文件,动态创建接口的实例。该实例由JNA通过反射自动生成。
  • void printf(String format, Object... args);  接口中只需要定义要用到的函数或者公共变量,不需要的可以不定义。注意参数和返回值类型,应该和系统链接库中的函数类型保持一致。

调用链接库函数

  • 定义好接口后,就可以使用接口中的方法即相应dll/so中的函数了,通过接口中的实例即可进行调用
  • 如:CLibrary.INSTANCE.printf("Hello, World\n");  即可调用系统中的printf函数