目录
本文参考网址:https://blog.csdn.net/gwd1154978352/article/details/55097376
本文基于 win10 的 64 位操作系统
JNA (Java 本地访问)概述
1、JNA 全称 Java Native Access (Java 本地访问),JNA 提供一组 Java 工具类用于在运行期间动态访问系统本地库(native library:如Window的dll)而不需要编写任何Native/JNI代码。
2、开发人员只要在一个Java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射。
3、JNA全称Java Native Access,是一个建立在经典的JNI(Java Native Interface-Java本地接口)技术之上的Java开源框架
4、Github托管地址:https://github.com/java-native-access/jna
5、JNA 开发包下载地址
6、官方入门示例:https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md
6、通俗的说就是使用 JNA 可以更方便的调用 windows/Linux 系统底层的函数(方法)
C 语言 与 Java
1、Windows 底层主要使用 C++ 编写以及还有 C 语言加汇编语言,Linux 主要以 C 语言为主
2、dll 和 so 是 C 函数的集合和容器,这与 Java 中的接口概念吻合,所以 JNA 把 dll 文件和 so 文件看成一个个接口。在 JNA 中定义一个接口就是相当于了定义一个 DLL/SO 文件的描述文件,该接口代表了动态链接库中发布的所有函数。而且,对于程序不需要的函数,可以不在接口中声明。
3、JNA 定义的接口一般继承 com.sun.jna.Library 接口,如果dll文件中的函数是以stdcall方式输出函数,那么该接口就应该继承com.sun.jna.win32.StdCallLibrary接口。
4、JNA 难点在于学 Java 的人完全不知道电脑系统底层到底有哪些库?库叫什么名字?库中有哪些函数?函数是什么含义?等等
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相同 |
JNA 入门示例
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、运行程序,没带参数时只打印出“Hello, World”,带了参数时,则会打印出所有的参数。
2、没有写一行 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函数
msvcrt.dll 运行库
1、msvcrt.dll 是微软在windows操作系统中提供的C语言运行库执行文件(Microsoft Visual C Runtime Library)
2、msvcrt.dll 中提供了printf,malloc,strcpy 等 C语言库函数的具体运行实现
printf 打印方法
1、第一步自定义接口继承Library,接口名称建议与库名称一致,如 Msvcrt。如果dll库文件中的函数是以stdcall方式输出,那么自定义接口就应该继承com.sun.jna.win32.StdCallLibrary接口。
2、使用 Native 加载链接库,msvcrt的后缀名.dll不要写,想要使用msvcrt.dll库中哪些方法,就在自定义接口声明它即可
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
/**
* Created by Administrator on 2018/6/27 0027.
*/
public interface Msvcrt extends Library {
Msvcrt INSTANCE = (Msvcrt) Native.loadLibrary(
(Platform.isWindows() ? "msvcrt" : "c"),
Msvcrt.class
);
void printf(String format, Object... args);
}
3、第二步则是常见自定义接口的实例调用接口中的方法,也是msvcrt库中的函数
/**
* 测试msvcrt.dll的printf方法
*/
@Test
public void test1() {
Msvcrt msvcrt = Msvcrt.INSTANCE;
msvcrt.printf("Hello JNA");
}
kernel32 动态链接库
1、kernel32.dll 是Windows中非常重要的32位动态链接库文件,属于内核级文件。
2、kernel32.dll 控制着系统的内存管理、数据的输入输出操作和中断处理,当Windows启动时,kernel32.dll就驻留在内存中特定的写保护区域,使别的程序无法占用这个内存区域。
3、kernel32.dll库中有GetLogicalDriveStringsA、GetSystemDirectoryA等方法
GetLogicalDriveStringsA
1、获取本地系统的系统盘符
2、自定义接口,然后指明动态链接库名称,同时声明方法
import com.sun.jna.Library;
import com.sun.jna.Native;
/**
* Created by Administrator on 2018/6/27 0027.
*/
public interface Kernel32 extends Library {
Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
/**
* 【获取本地系统逻辑盘符】
* @param length
* @param buffer
* @return
*/
int GetLogicalDriveStringsA(int length,byte[] buffer);
}
/**
* 测试kernel32.dll的GetLogicalDriveStringsA方法
* 【获取本地系统逻辑盘符】
*/
@Test
public void test2() {
Kernel32 kernel32 = Kernel32.INSTANCE;
byte[] buffer2 = new byte[64];
/**
* GetLogicalDriveStringsA方法会把结果缓存到buffer2字节数组中
* logicalDriveStringsSize:方法返回值其实是结果的大小
*/
int logicalDriveStringsSize = kernel32.GetLogicalDriveStringsA(buffer2.length / 2, buffer2);
StringBuilder stringBuilder = new StringBuilder(logicalDriveStringsSize);
for (byte bt : buffer2) {
stringBuilder.append((char) bt);
}
String logicalDriveStrings = stringBuilder.toString().trim();
System.out.println(logicalDriveStrings);
}
GetSystemDirectoryA
import com.sun.jna.Library;
import com.sun.jna.Native;
/**
* Created by Administrator on 2018/6/27 0027.
*/
public interface Kernel32 extends Library {
Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
/**
* 【获取本地系统逻辑盘符】
* @param length
* @param buffer
* @return
*/
int GetLogicalDriveStringsA(int length,byte[] buffer);
/**
* 【获取系统目录】
* @param buffer
* @param size
* @return
*/
int GetSystemDirectoryA(byte[] buffer,int size);
}
/**
* 测试kernel32.dll的GetSystemDirectoryA方法
* 【获取系统目录】
*/
@Test
public void test3() {
Kernel32 kernel32 = Kernel32.INSTANCE;
byte[] buffer = new byte[50];
/**
* 同理此方法也是将结果缓存到了字节数组中,数组大小自己设置即可
* 返回值systemDirectoryStrSize仍然是结果的长度大小
*/
int systemDirectoryStrSize = kernel32.GetSystemDirectoryA(buffer, 50);
StringBuilder stringBuilder = new StringBuilder(systemDirectoryStrSize);
for (byte bt : buffer) {
stringBuilder.append((char) bt);
}
String systemDirectoryStr = stringBuilder.toString().trim();
System.out.println(systemDirectoryStr);
}