摘要
五一快乐吖!死肥宅正趁着五一这段时间,努力提升自己!
正文
一、背景
五一快乐吖!死肥宅正趁着五一这段时间,努力提升自己!
最近使用Java拦截Windows系统中一些默认事件时,发现了一些瓶颈。
我用Java操作浏览器、用Java最小化其他应用窗口,但是我发现这个操作,他都是异步的。
比如,写个程序,获取当前前置窗口,给他缩小。由于它是异步的,只是给操作系统发个通知你要缩小,但是否执行完,开发者不知道。实际上由于循环过快,就成了死循环一直获取到的是当前窗口,然后一直缩小当前窗口。最后把电脑卡死了。所以用Java写系统级的功能,并不好使。
经过我在C++/C#/Java/Go中的语言选型,以下排序分先后
| 开发语言 | 适用平台 | 适用场景 | 细节描述 |
|---|
| C++ | 跨平台 | 底层应用、上层应用 | - |
| C# | Windows | 底层应用、上层应用 | 也支持跨平台,但不如直接C++ |
| Java | 跨平台 | 上层应用 | 借助其他语言,如C/C++、C#可调用操作系统底层API |
| Go | 跨平台 | 上层应用 | 借助其他语言,如C/C++、C#可调用操作系统底层API。不过说替代C++,完全是扯淡,如果真要说作用,那就是为了分Java一杯羹。 |
二、动态链接库
2.1 概念
动态链接库是一种可以被多个程序共享的代码库,它的主要作用是复用代码、减少程序占用的磁盘和内存空间,以及支持独立更新。
动态链接库在不同操作系统上有不同的名称:
| 操作系统 | 动态链接库 | 文件扩展名 |
|---|
| Windows | 动态链接库(Dynamic-Link Library) | .dll |
| Linux | 共享对象(Shared Object) | .so |
| macOS | 动态库(Dynamic Library) | .dylib |
2.2 环境配置
开发IDE:Visual Studio 2022
该页面如果已安装,可以在 工具 - 获取工具和功能 选项中查找到
2.3 C++开发动态链接库
2.3.1 创建项目
2.3.2 编写代码
编写代码TestDLL.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| // TestDLL.cpp : 定义 DLL 的导出函数。
//
#include "pch.h"
#include "framework.h"
#include "TestDLL.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <string>
using namespace std;
// 这是导出变量的一个示例
TESTDLL_API int nTestDLL=0;
// 这是导出函数的一个示例。
TESTDLL_API int fnTestDLL(void)
{
return 0;
}
TESTDLL_API int add(int a, int b) {
int value = a + b;
//c++打印
cout << "C++打印: TestDLL add: " << value << endl;
//c打印
printf("C打印: TestDLL add: %d", value);
// 线程休眠5秒
std::this_thread::sleep_for(std::chrono::seconds(5));
return value;
}
TESTDLL_API void openBrowser(char* str) {
std::string cmd = "start " + std::string(str);
system(cmd.c_str());
}
// 这是已导出类的构造函数。
CTestDLL::CTestDLL()
{
return;
}
|
编写代码TestDLL.h头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| // 下列 ifdef 块是创建使从 DLL 导出更简单的
// 宏的标准方法。此 DLL 中的所有文件都是用命令行上定义的 TESTDLL_EXPORTS
// 符号编译的。在使用此 DLL 的
// 任何项目上不应定义此符号。这样,源文件中包含此文件的任何其他项目都会将
// TESTDLL_API 函数视为是从 DLL 导入的,而此 DLL 则将用此宏定义的
// 符号视为是被导出的。
#ifdef TESTDLL_EXPORTS
#define TESTDLL_API __declspec(dllexport)
#else
#define TESTDLL_API __declspec(dllimport)
#endif
// 此类是从 dll 导出的
class TESTDLL_API CTestDLL {
public:
CTestDLL(void);
// TODO: 在此处添加方法。
};
extern TESTDLL_API int nTestDLL;
TESTDLL_API int fnTestDLL(void);
// 声明
/*
当C++代码包含C函数或C库的头文件时,由于C++和C语言在函数名称的处理方式上有所不同,C++编译器会将这些C函数或库中的函数名称按照C++的方式进行重整,这会导致函数名称被修改并在链接时找不到函数。
通过在C++代码中使用extern "C"说明符来修饰C函数,可以告诉C++编译器对函数名进行C语言风格的链接,从而避免函数名被重整导致链接错误。
*/
extern "C" TESTDLL_API int add(int a, int b);
extern "C" TESTDLL_API void openBrowser(char* str);
|
2.3.3 注意事项
参考学习JNA的一天-自定义DLL以及被Java调用 - 简书
由此代码就写完了。注意几点事项
- 项目编码设置为UTF-8,不是UTF-8 BOM
- 编译系统相应版本。
1.) 设置编码
2.) 编译系统相应版本
生成-配置管理器
2.3.4 编译
三、Java调用动态链接库
3.1 JNA与JNI
JNA是基于JNI技术,实现的一个Java库!
JNI(Java Native Interface)是Java提供的一种与本地代码交互的技术。通过JNI,Java程序可以调用本地编写的C/C++等语言编写的代码,也可以让本地代码调用Java程序中的对象和方法。
JNA(Java Native Access)是一个Java库,它允许Java应用程序直接访问本地库(如动态链接库,DLL)中的功能,而无需编写任何本地代码。JNA通过Java反射和动态代理技术,将Java数据类型和本地C数据类型进行映射,实现Java和本地代码之间的互操作。
JNA(Java Native Access)和JNI(Java Native Interface)都是Java与本地代码交互的技术,但它们有一些区别。
难易程度
- JNI的使用相对较为复杂,需要编写较多的底层代码,如头文件、Java虚拟机调用等,需要掌握C/C++语言。
- JNA的使用相对简单,只需要定义接口、使用注解、调用动态库函数即可,无需编写底层代码。
性能
- JNI在性能方面较为优秀,因为它直接操作本地内存,调用本地代码时速度较快。
- JNA在性能方面相对较差,因为它使用Java对象来表示本地内存,需要进行频繁的对象转换和内存拷贝。
平台兼容性
- JNI需要编写本地代码,因此需要针对不同的操作系统和平台进行不同的实现。
- JNA则是基于Java虚拟机实现的,因此可以很好地跨平台运行,无需考虑本地代码的兼容性问题。
使用场景
- JNI主要用于需要对本地代码进行直接控制的场景,如操作系统级别的编程、性能敏感的算法等。
- JNA则主要用于简化Java与本地库交互的过程,如调用本地的API函数等。
总的来说,JNA相比JNI使用起来更加简单、方便,并且具有良好的跨平台性,但在性能方面稍逊于JNI,因此需要根据具体的需求选择合适的技术。
3.2 JNI使用
使用步骤分为三步
- 定义Java方法
- 编写底层(C/C++)方法
- 编译和链接:将本地代码编译为动态链接库或静态库,并将其链接到Java程序中。在Java程序中调用native方法时,动态链接库将被加载并执行本地代码。
一般JNI的方法,都会带有native关键字,如下。
1
| public native void hello();
|
实际手撕JNI,使用起来较为复杂,所以一般采用JNA库来实现功能
3.3 JNA使用
3.3.1 示例代码
添加maven依赖
1
2
3
4
5
6
7
8
9
10
| <dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.12.1</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.12.1</version>
</dependency>
|
以下是JNA的使用示例
1
2
3
4
5
6
7
8
| public interface TestDLL extends Library {
//加载dll,实例成对象。下面的方法,均为dll提供的方法
TestDLL instance = Native.load("C:\\Users\\meethigher\\Desktop\\DLL\\TestDLL\\x64\\Debug\\TestDLL.dll",TestDLL.class);
int add(int a,int b);
void openBrowser(String str);
}
|
进行测试
1
2
3
4
5
6
7
8
9
10
11
12
| @Slf4j
public class Main {
public static void main(String[] args) {
log.info("start");
int add = TestDLL.instance.add(1, 2);
log.info("end");
System.out.println(add);
for (int i = 0; i < 10; i++) {
TestDLL.instance.openBrowser(String.format("https://meethigher.top/%s", i));
}
}
}
|
3.3.1 数据类型对应关系
表格如下
| Java数据类型 | C/C++数据类型 |
|---|
| boolean | bool |
| byte | char |
| short | short |
| int | int |
| long | long long |
| float | float |
| double | double |
| char | char |
| String | const char* |
| byte[] | char* |
| short[] | short* |
| int[] | int* |
| long[] | long long* |
| float[] | float* |
| double[] | double* |
| char[] | char* |