JNA调用Win32API
最近想做一个桌面管理工具,不可避免地需要用到很多Win32 API。那么,就从显示一个MessageBox开始吧。
最近想做一个桌面管理工具,不可避免地需要用到很多Win32 API。鉴于我暂时懒得再去学C#,我需要在Kotlin/JVM下使用Win32 API的办法。传统来说JVM为我们提供了JNI,但JNI使用其实相对繁琐,需要生成jni header文件,进行C/C++开发,我们这个需求用JNA就能实现,不需要写任何C/C++代码。 那么,就从显示一个MessageBox开始吧。
JNA显示MessageBox
首先引入JNA的依赖:
implementation("net.java.dev.jna:jna:5.14.0")
接下来检查MessageBox方法的定义:
int MessageBox(
[in, optional] HWND hWnd,
[in, optional] LPCTSTR lpText,
[in, optional] LPCTSTR lpCaption,
[in] UINT uType
);
然后写一个Kotlin接口,定义这个方法即可:
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.win32.W32APIOptions
interface User32Extend : Library {
companion object {
val instance: User32Extend = Native.load("user32", User32Extend::class.java, W32APIOptions.DEFAULT_OPTIONS);
}
fun MessageBox(hWnd: Long?, lpText: String, lpCaption: String, uType: Long): Int
}
这里的类型hWnd
本质上是一个指针,Kotlin中我们可以直接定义为Long(JNA官方实现中是定义了两个类去包这个值,其实没必要)。uType
原始定义是UInt类型,但Kotlin中不能使用UInt否则找不到方法,这可能是因为UInt是一个Kotlin内联类,JNA并不支持,所以这里我们用Int或Long都可以。
接下来使用我们封装的方法
User32Extend.instance.MessageBox(null, "Hello World", "你好,世界!", 64)
MessageBox函数会阻塞当前线程,如果在主线程使用,在用户确认这个对话框前整个界面是完全卡住的,这不符合开发规范,用户体验也不好。因此,我们需要切换到后台线程再调用这个函数,以一段Compose代码为例:
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch(Dispatchers.IO) {
User32Extend.instance.MessageBox(null, "Hello World", "你好,世界!", 64)
}
}) {
Text(text)
}
尽管现在对话框成功弹出了,点击也没有什么问题,还有一个小的体验问题,就是新弹出的窗口和我们本来的窗口没有什么关系,而我们知道很多软件中的交互是在确认前用户无法操作主窗口的。 MessageBox函数有四个参数,前面我们用上了三个,现在只要把主窗口的句柄传到第一个参数即可。
@Composable
@Preview
fun App(pointer: Long? = null) {
var text by remember { mutableStateOf("Hello, World!") }
MaterialTheme {
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch(Dispatchers.IO) {
User32Extend.instance.MessageBox(null, "Hello World", "你好,世界!", 64)
}
}) {
Text(text)
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication) {
App(pointer = window.windowHandle)
}
}
封装Messagebox
这个API果然还是好难用,我们按照java世界的规矩,简单封装下使其更易用。
最后修改于 2024-08-04