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