nttr-tech/winui4k

GitHub: nttr-tech/winui4k

该项目通过 JVM FFI 直接调用 WinRT ABI,让开发者能使用纯 Kotlin 或 Java 构建原生 Windows UI 应用。

Stars: 32 | Forks: 0

# 适用于 Kotlin & Java 的 WinUI (winui4k) [![Build](https://static.pigsec.cn/wp-content/uploads/repos/cas/3b/3b39c55110f11a97d10cda63d1c1193c1b78ee3e26d559adaea16b191fc65e8a.svg)](https://github.com/nttr-tech/winui4k/actions/workflows/build.yml) [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE.txt) ![Kotlin](https://img.shields.io/badge/kotlin-2.4.0-7F52FF.svg?logo=kotlin) ![Java](https://img.shields.io/badge/java-8%2B-orange.svg) English | [日本語](README.ja.md) **WinUI** 是 Microsoft 推广的作为 Windows 11 世代标准的 UI 框架。 借助 **WinUI4K**,你可以使用纯 Kotlin 或 Java 构建原生 Windows 应用 —— 无需桥接 DLL,无需 C#,也无需 Visual Studio。 WinUI4K 直接通过 Java FFI(Panama / JNA / JNR)调用 WinRT ABI(二进制层面的调用约定),因此不需要打包原生 DLL 来桥接语言和运行时。 该库由 [NTT Resonant Technology](https://nttr-tech.co.jp/) 进行原型开发,部分目的是为了将其应用于“[Remote TestKit](https://appkitbox.com/)”的 PC 客户端,该服务允许你通过互联网租用真实的智能手机。 它基于 Apache License 2.0 发布,可自由用于商业和非商业目的。 下面的截图看起来可能像是 Microsoft 的 WinUI 3 Gallery,但它是完全用 Kotlin 编写的内置 [Gallery 应用](winui4k-sample-gallery)。 ![winui4k Gallery](https://raw.githubusercontent.com/nttr-tech/winui4k/master/doc/images/home.png) ## 目录 - [背景](#background) - [示例](#example) - [功能](#features) - [与传统 WinUI 开发的对比](#comparison-with-conventional-winui-development) - [适用场景与局限性](#suitable-use-cases-and-limitations) - [快速开始](#quick-start) - [示例应用](#sample-apps) - [模块](#modules) - [架构](#architecture) - [Windows App SDK Runtime 的自动配置](#automatic-setup-of-the-windows-app-sdk-runtime) - [系统属性](#system-properties) - [贡献](#contributing) - [许可证](#license) - [参考资料](#references) ## 背景 Microsoft 将 WinUI 定位为未来 Windows 应用 UI 的核心,并且一些标准的 Windows 应用已经迁移到了 WinUI。 然而,WinUI 官方支持的开发语言是 C++ 和 C#,到目前为止,几乎没有任何实用的方法可以在广泛应用于业务系统的 Java 或 Kotlin 中使用它。 因此,拥有 Java 或 Kotlin 资产的团队如果想采用原生 Windows UI,要么必须用 C# 重写所有内容,要么改用 Electron 等使用 Web 技术构建桌面应用的其他方法。 Compose for Desktop 是另一种选择,但由于它是一种带有自身渲染引擎的非原生方法,因此无法按原样提供操作系统标准的外观和辅助功能。 winui4k 的诞生正是为了填补这一空白,通过 JVM FFI 直接调用 WinUI。 ## 示例 你可以用类似于 Java Swing 的感觉来构建 WinUI 应用。 ``` WinUiUtilities.invokeLater { val frame = WFrame(title = "WinUI4K") val nameField = WTextField(placeholder = "Name") val greetButton = WButton("Greet") greetButton.addActionListener { greetButton.text = "Hello, ${nameField.text.ifBlank { "world" }}!" } frame.add(nameField) frame.add(greetButton) frame.isVisible = true } ``` 这个简短的代码片段会打开一个原生的 Windows 窗口,按下按钮会改变其标签。 ## 功能 - **60 多个控件**:从 Button / TextBox 到 NavigationView、TeachingTip、AppNotification 和 AppWindow —— 全部封装为 `W*` 类。你可以在 Gallery 中试用它们。 - **支持在 Java 8 上运行**:FFI 后端是可插拔的。内置了三种实现:Panama(Java 22+,默认)、JNA(Java 8+)和 JNR(Java 8+)。 - **协程支持**:`Dispatchers.WinUi`(winui4k-extension-coroutines)可分发到 UI 线程,且 `delay` 在原生计时器 (DispatcherQueueTimer) 上运行。 - **WebView2 支持**:`WWebView` 允许你在应用中嵌入基于 Microsoft Edge 的浏览器控件。 - **辅助功能**:因为直接按原样使用了操作系统标准控件,屏幕阅读器等辅助技术开箱即用。 - **在真实窗口上进行 E2E 测试**:测试会启动实际的 WinUI 窗口进行验证,并且 CI 会在 JDK 8 / 9 / 22 / 25 上运行它们。 - **无桥接 DLL**:一切 —— 对象创建(`RoGetActivationFactory`)、WinUI 的字符串类型 HSTRING、通过函数表(vtable)进行的方法调用、将 Kotlin 对象公开为 COM 对象的 upcall 以及 COM 聚合 —— 都纯粹使用 JVM FFI 实现([架构](#architecture))。 - **零猜测的 ABI 常量**:COM 调用所需的标识符(IID)和 vtable 位置均通过机器从 Windows 类型信息文件中自动提取,不包含任何手动编写的猜测。 ## 与传统 WinUI 开发的对比 | 方面 | 传统的 WinUI 开发 | winui4k | |---|---|---| | 语言 | C# / C++ 加上 XAML 标记 | Kotlin / Java(UI 也使用代码编写) | | IDE | 通常为 Visual Studio | 任意编辑器 + JDK | | 额外的运行时/SDK | .NET SDK,Windows App SDK runtime | 仅需 Windows App SDK runtime(如缺失,启动时自动安装) | | 自定义桥接 DLL | 有时需要 | 不需要 | | 现有的 JVM 资产 | 必须用 C# 重写 | 可直接使用 | 上表是与典型开发方法的比较;可能并不适用于所有配置。 ## 适用场景与局限性 当你想要实现以下需求时,winui4k 是一个很好的选择: - 将使用 Swing 或 JavaFX 构建的 Windows 业务应用 UI 现代化,升级为操作系统原生的 Fluent Design(Windows 11 的标准设计语言) - 在必须基于 Java 8 运行的环境中使用原生 Windows UI - 避免像 Electron 这种捆绑了浏览器引擎的方法所带来的较大的分发包体积和内存占用 - 获得操作系统标准控件的外观和辅助功能,而不是自定义渲染的控件 另一方面,存在以下局限性: - **仅支持 Windows。** WinUI 本身仅限 Windows;如果你需要包括 macOS 或 Linux 在内的跨平台支持,请考虑使用 Compose Multiplatform 等替代方案。 - **COM 引用与 GC 同步释放**,因此时机是不确定的。如果你的使用场景在创建和销毁 UI 元素时频率很高,同时又需要严格控制原生侧的释放时机,你的设计必须考虑到这一点([架构](#architecture))。 - **跨越语言边界的循环引用无法自动回收。** 不再需要的事件监听器必须使用相应的 remove 方法显式移除。 ## 快速开始 你只需要 **JDK 25** (x64) —— 无需 Visual Studio、C++ 构建工具或 .NET SDK。 从 [Eclipse Temurin](https://adoptium.net/) 或类似来源获取它,并将其添加到你的 PATH 中。 ``` git clone https://github.com/nttr-tech/winui4k.git cd winui4k .\gradlew run ``` 这会启动 Gallery 应用。 即使未安装 WinUI 的执行基础(Windows App SDK runtime),它也会在启动时自动设置([详情](#automatic-setup-of-the-windows-app-sdk-runtime))。 仅构建此仓库需要 JDK 25;库本身可在 Java 8 或更高版本上运行。 你可以使用 `.\gradlew :winui4k-sample-gallery:runJna` 验证在 Java 8 + JNA 上的运行情况,使用 `.\gradlew :winui4k-sample-gallery:runJnr` 验证在 Java 8 + JNR 上的运行情况。 支持的环境为 Windows 11 x64(预计在 Windows 10 1809 或更高版本上也能正常工作)。 ## 示例应用 除了 Gallery 之外,还内置了几个更接近真实应用的示例。 它们的存在都是为了证明可以使用 WinUI4K 构建真实的应用程序。 | 示例 | 描述 | 运行命令 | |---|---|---| | [Gallery](winui4k-sample-gallery) | 按类别展示 60 多个控件的演示应用(采用 WinUI 3 Gallery 风格) | `.\gradlew run` | | [Filer](winui4k-sample-filer) | 具有标签页、详细信息/图标视图切换、面包屑导航、侧边栏和过滤功能的 Fluent Design 文件管理器 | `.\gradlew :winui4k-sample-filer:run` | | [Notes](winui4k-sample-notes) | 简单的记事本应用 | `.\gradlew :winui4k-sample-notes:run` | | [Form with MigLayout](winui4k-sample-form-with-miglayout) | 使用 MigLayout 布局库的输入表单 | `.\gradlew :winui4k-sample-form-with-miglayout:run` | ## 模块 | 模块 | 描述 | |---|---| | `winui4k` | 核心。公共 API(`W*` 类)以及内部的 COM / WinRT / WinUI 层 | | `winui4k-ffi-panama` | Panama (`java.lang.foreign`) FFI 后端。Java 22+ | | `winui4k-ffi-jna` | JNA FFI 后端。Java 8+(仅限 x64) | | `winui4k-ffi-jnr` | JNR (jffi) FFI 后端。Java 8+(x86 / x64 / arm64) | | `winui4k-extension-coroutines` | `Dispatchers.WinUi`(kotlinx-coroutines-swing 的 WinUI 对应实现) | | `winui4k-extension-miglayout` | 使用 MigLayout 布局库布局 `W*` 控件的适配器 | | `winui4k-all` | 引用上述所有内容的聚合模块(不包括示例) | | `winui4k-sample-gallery` | 展示所有控件的演示应用 | | `winui4k-sample-filer` | Fluent Design 文件管理器示例 | | `winui4k-sample-notes` | 记事本应用示例 | | `winui4k-sample-form-with-miglayout` | 使用 MigLayout 的输入表单示例 | ## 架构 winui4k 通过 Java FFI 直接操作 **COM**(Windows 定义的用于跨语言调用对象的二进制层面约定),并在作为 COM 演进的 **WinRT**(Windows 的现代 API 基础)之上调用 WinUI。 对 COM 对象的引用是一个指向结构体的指针,该结构体的第一个成员是一个指向函数指针数组(**vtable**)的指针。 沿着 `pointer → vtable → vtable[slot]` 访问即可到达方法的实现 —— 这与 C++ 虚函数调用的机制相同。 WinUI 的 Button 和 Window 就以此形式存在于进程中,而 winui4k 使用 FFI 直接组装这些调用。 各层仅进行单向依赖。 | 层级 | 包 | 角色 | |---|---|---| | 公共 API | `com.appkitbox.winui4k` | `W*` 类(`WFrame` / `WButton` / ...) | | WinUI | `internal.winui` | `*Interop` ABI 常量,Dispatcher | | WinRT | `internal.winrt` | HSTRING,`KComObject`(将 Kotlin 实现作为 COM 对象公开的 upcall stub),Activation | | COM | `internal.com` | `ComPtr`,Guid,将 HRESULT(COM 调用结果代码)转换为异常 | | FFI SPI | `internal.ffi.api` | 与后端无关的 FFI 核心词汇。实现通过 ServiceLoader 发现 | FFI 后端被分离到各自的模块中,并在运行时通过 ServiceLoader 进行发现和选择。 优先顺序为 Panama(Java 22+ 默认)> JNA(Java 8+,仅限 x64)> JNR(Java 8+,x86 / x64 / arm64),你也可以使用 `-Dwinui4k.ffi` 显式指定。 核心模块针对 Java 8,仅 Panama 模块引用了 `java.lang.foreign`。 ### 桥接 COM 引用与 GC COM 通过引用计数(`AddRef` / `Release`)管理生命周期,而 JVM 通过带有 tracing GC 的可达性来确定生命周期。 winui4k 使用与 Microsoft 官方的 C# 互操作运行时 **CsWinRT** 相同的设计来弥合这种不匹配。 每个 `W*` 包装器都恰好持有一个 COM 引用计数,当 GC 检测到包装器已变得不可达时,就会调用 `Release`(在 Java 9+ 上通过 `java.lang.ref.Cleaner` 实现,或者在 Java 8 上通过基于 `PhantomReference` 的等效自制机制实现)。 COM 有一种称为 **apartments** 的线程约定:WinUI 对象绑定到 UI 线程,并且 `Release` 也必须从该线程调用。 因此,由 GC 触发的释放操作会被汇集到 UI 线程的消息循环中执行。 请注意,跨越语言边界(原生侧 → Kotlin 实现的事件处理器 → Kotlin 对象 → 指回原生的 COM 引用)的循环引用无法自动回收。 CsWinRT 通过 .NET GC 与 WinRT 运行时之间的相互引用图查询来解决这个问题,但 JVM 的 GC 没有对应的扩展点。 ### 已知局限 - 假定为单一 UI 线程,且 `W*` API 在约定上仅能在此线程上使用。 - Window 和 Shell 包装器(`WFrame`、`WAppWindow` 等)被排除在自动释放之外,并会无限期持有其引用。 - 错误处理仅限于将 HRESULT 转换为异常。 IID(接口标识符)和 vtable 槽号不是手动编写的猜测 —— 它们是使用 `tools/dump_winmd.py` 从 Windows 类型信息文件 中通过机器提取的。 ## Windows App SDK Runtime 的自动配置 WinUI4K 会在应用启动时自动配置 WinUI 的执行基础 —— Windows App SDK Runtime。 ### 引导 DLL 初始化 Windows App SDK 所需的引导 DLL(`Microsoft.WindowsAppRuntime.Bootstrap.dll`)被嵌入在 winui4k JAR 中(涵盖 x86 / x64 / arm64 架构)。 在首次调用 `WinUiUtilities` 时,与正在运行的 PC 架构相匹配的 DLL 会自动解压到临时目录,并在进程退出时删除。 ### 运行时安装 需要 Windows App SDK 2.2 运行时。如果尚未安装,将按顺序执行以下步骤: 1. **自动执行安装程序**:如果当前目录(或由 `winui4k.installer.dir` 指定的目录)中存在 `WindowsAppRuntimeInstall-x64.exe` 等安装程序,它将使用 `--quiet` 选项静默运行,随后应用将正常启动 2. **安装对话框**:如果未找到安装程序,将显示 Microsoft 的对话框,提示用户下载运行时 可以使用以下命令下载安装程序: ``` .\gradlew :winui4k:downloadInstallers ``` 三个安装程序 —— x86 / x64 / arm64 —— 将被下载到 `winui4k/installer/`(每个约 104 MB)。 如果在分发应用时捆绑了与目标架构匹配的安装程序,运行时将会在最终用户的机器上自动安装。 要手动安装,请从 https://aka.ms/windowsappsdk 运行 `WindowsAppRuntimeInstall-x64.exe`。 ## 系统属性 | 属性 | 值 | 描述 | |---|---|---| | `winui4k.ffi` | `panama` / `jna` / `jnr` | 显式选择 FFI 后端。默认使用可用且优先级最高的后端 | | `winui4k.lifetime` | `cleaner` / `phantom` | 显式选择 COM 引用清理机制。默认根据 Java 版本自动选择 | | `winui4k.gcThreshold` | 整数(引用计数) | 每当活跃的原生引用数量超过阈值时请求 `System.gc()`。默认禁用 | | `winui4k.bootstrap.dll` | 路径 | 显式指定要使用的引导 DLL,以代替 JAR 中嵌入的 DLL | | `winui4k.installer.dir` | 目录 | 运行时安装程序的搜索位置。支持绝对路径和相对路径(默认为当前目录) | ## 许可证 [Apache License 2.0](LICENSE.txt)。可自由用于商业和非商业目的。 ## 参考资料 - [WinUI 开发文档](https://learn.microsoft.com/en-us/windows/apps/winui/winui3/) - [Fluent Design System](https://fluent2.microsoft.design/) - [Windows 应用的设计基础](https://learn.microsoft.com/en-us/windows/apps/design/) - [WinUI 仓库](https://github.com/microsoft/microsoft-ui-xaml) - [WinUI Gallery 仓库](https://github.com/microsoft/WinUI-Gallery) - [Windows App SDK 仓库](https://github.com/microsoft/WindowsAppSDK) - [nuget](https://www.nuget.org/packages/Microsoft.WindowsAppSDK) - 额外的 WinUI 组件 - [TableView 仓库](https://github.com/w-ahmad/WinUI.TableView) - [Community Controls 仓库](https://github.com/CommunityToolkit/Windows)
标签:JS文件枚举, Kotlin, Windows UI, WinRT, 后台面板检测, 桌面应用, 跨语言调用