JNA 理论详解 1
- 本文参考网址:https://blog.****.net/gwd1154978352/article/details/55097376
- 本文基于win10的64位操作系统
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 包下载地址
- 官方入门示例:https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md
- 通俗的说就是使用JNA可以更方便的调用windows/Linux系统底层的函数(方法)
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 类型 |
C 类型 |
原生表现 |
|
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 |
平台依赖(32或64位指针) |
|
<T>[] (基本类型的数组) |
pointer/array |
32或64位指针(参数/返回值) 邻接内存(结构体成员) |
|
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 |
平台依赖(32或64位整数) |
|
PointerType |
pointer |
和Pointer相同 |
入门示例
示例代码
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个参数:
- 第一个参数是动态链接库dll/so的名称,但不带.dll或.so这样的后缀,这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。搜索动态链接库路径的顺序是:先从当前类的当前文件夹找,如果没有找到,再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,如果找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。比如上例中printf函数在Windows平台下所在的dll库名称是"msvcrt",而在其它平台如Linux下的so库名称是"c"。
- 第二个参数是自定义接口的Class类型。JNA通过这个Class类型,根据指定的.dll/.so文件,动态创建接口的实例。该实例由JNA通过反射自动生成。
- void printf(String format, Object... args); 接口中只需要定义要用到的函数或者公共变量,不需要的可以不定义。注意参数和返回值类型,应该和系统链接库中的函数类型保持一致。
调用链接库函数
- 定义好接口后,就可以使用接口中的方法即相应dll/so中的函数了,通过接口中的实例即可进行调用
- 如:CLibrary.INSTANCE.printf("Hello, World\n"); 即可调用系统中的printf函数