mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-29 15:19:12 +08:00
190 lines
11 KiB
Markdown
190 lines
11 KiB
Markdown
# AtomUI.Native 架构
|
||
|
||
## 定位
|
||
|
||
`AtomUI.Native` 是 AtomUI 的内部底层 OS 能力封装层。它按“是否属于操作系统、窗口系统或原生运行时
|
||
调用”划定边界,而不是按 `Window`、`Dialog`、主题、控件等上层概念划定边界。只要实现需要直接接触
|
||
P/Invoke、原生 handle、原生结构体、平台协议对象、native hook 或平台错误码,就应优先收敛到
|
||
`AtomUI.Native`。
|
||
|
||
窗口相关能力可以进入 `AtomUI.Native`。Win32、DWM、Objective-C runtime、Xlib/XCB、Wayland protocol
|
||
等窗口系统原生调用,本质上是底层 OS/backend 能力,不应因为服务于 Window/Dialog 就回到控件层。
|
||
|
||
`AtomUI.Native` 不决定控件策略、主题策略或用户可观察行为。上层库负责选择何时启用某个原生能力,
|
||
并承担 Avalonia 控件生命周期、事件订阅、属性同步和模板状态。Native 层提供能力,不拥有业务状态。
|
||
|
||
Avalonia drawn decorations、`TopLevelHost`、resize grip layer 等 Desktop 控件私有结构发现通常保留在
|
||
`AtomUI.Desktop.Controls`。只有当实现必须解析原生协议对象、调用原生 API,或把 P/Invoke/协议细节
|
||
压回 Native 边界时,才应进入 `AtomUI.Native`。
|
||
|
||
```text
|
||
AtomUI.Desktop.Controls
|
||
- 选择 Window chrome manager
|
||
- 管理 Window 事件、属性和模板生命周期
|
||
- 决定何时启用某个原生能力
|
||
|
|
||
v
|
||
AtomUI.Native
|
||
- 校验原生 handle / 参数
|
||
- 调用 Win32、Objective-C、Xlib/XCB 或 NWayland
|
||
- 封装原生结构体、常量、消息和协议对象
|
||
- 管理原生资源、hook 和错误边界
|
||
```
|
||
|
||
所有成员目前都是 `internal`,通过 `InternalsVisibleTo` 向 `AtomUI.Desktop.Controls` 和目标程序集名称
|
||
`AtomUI.Mobile.Controls` 开放。当前仓库没有 Mobile 实现消费者;这项预留只定义未来可见性边界,不是 Mobile 源码、
|
||
平台验证或发布证据。它是实现包,不是公共 Native SDK。
|
||
|
||
### 能力与策略分界
|
||
|
||
| 层级 | 可以做 | 不应该做 |
|
||
|---|---|---|
|
||
| `AtomUI.Native` | 封装 OS/backend 原生调用、窗口系统底层能力、消息结构体、hook/façade、句柄校验、原生资源释放 | 决定窗口主题、Dialog 行为、控件状态机、Token、默认平台策略 |
|
||
| `AtomUI.Core` | AtomUI 基础设施、主题/Token/语言、资源、动画、应用启动默认选项;必要时调用 `AtomUI.Native` 获取底层 OS 能力 | 直接散落 P/Invoke、原生结构体、平台协议细节,或依赖具体控件生命周期 |
|
||
| `AtomUI.Desktop.Controls` | Window/Dialog/Popup 等桌面控件语义、生命周期、何时调用 Native 能力 | 直接维护 P/Invoke、原生结构体或重复实现 OS 协议 |
|
||
| `AtomUI.Mobile.Controls`(目标) | Viewport、Gesture、Overlay、Theme、owner 生命周期、平台默认策略,以及何时调用 Native 能力 | 直接维护 P/Invoke、原生 handle/结构体、不可释放 hook,或把 OS 类型暴露到 Public Control API |
|
||
|
||
`AtomUI.Core` 可以依赖 `AtomUI.Native`。`Core` 是 AtomUI 的基础设施层,不等于必须排除所有 Window
|
||
或 OS 概念;判断标准是能力本身是否属于底层 OS/native 操作。例如安全打开文件、解析真实路径、读取系统
|
||
窗口 metric、封装窗口系统原生调用,都可以由 `Core` 通过 `AtomUI.Native` 使用。相反,Dialog 的默认尺寸、
|
||
最大/最小尺寸策略、控件状态同步和模板生命周期仍属于上层控件库。
|
||
|
||
## 当前依赖
|
||
|
||
| 依赖 | 用途 | 影响 |
|
||
|---|---|---|
|
||
| `Avalonia` | `Window`、`WindowBase`、geometry 和 platform handle | AtomUI 基础依赖 |
|
||
| `NWayland` | `WlSurface`、`WlCompositor`、`WlRegion` | 会进入所有引用 Native 的依赖图 |
|
||
|
||
`AtomUI.Native` 当前与 `Avalonia.Wayland` 共享 NWayland 类型。具体版本以集中包管理和最终 restore
|
||
依赖图为准;升级任一依赖时必须同步核对类型版本和跨包 ABI,不能把当前偶然一致视为兼容保证。
|
||
|
||
上层项目是否引用 `AtomUI.Native` 取决于是否存在真实底层 OS/native 调用点。不能为了让 `Core` 看起来
|
||
“纯净”而把 P/Invoke、native struct 或平台错误处理留在 `Core`;也不能因为某个 native 能力最终服务于
|
||
Window/Dialog,就把底层 OS 细节放回 `AtomUI.Desktop.Controls`。
|
||
|
||
未来 Mobile adapter 同样遵循这条边界:优先使用 Avalonia 公共 API;只有 Safe Area、system bar、input pane、返回手势、
|
||
haptic 或生命周期能力确实需要原生调用时才进入 Native。Native 负责底层资源和确定释放,Mobile Runtime 负责
|
||
`TopLevel` owner、订阅、snapshot 合并、取消和产品行为。
|
||
|
||
## 目录和职责
|
||
|
||
```text
|
||
src/AtomUI.Native/
|
||
├── AtomUI.Native.csproj
|
||
├── NativeWindowSizing.cs
|
||
├── WindowExtensions.cs
|
||
├── FileSystem/
|
||
│ └── NativeFileSystem.cs
|
||
├── Windows/
|
||
│ ├── WindowsCsdSizingHook.cs
|
||
│ ├── WindowUtils.Interop.cs
|
||
│ └── WindowUtils.Windows.cs
|
||
├── MacOS/
|
||
│ ├── WindowUtils.Interop.cs
|
||
│ └── WindowUtils.MacOS.cs
|
||
└── Linux/
|
||
├── WindowUtils.Interop.cs
|
||
├── WindowUtils.Linux.cs
|
||
├── XcbConnectionHolder.cs
|
||
├── WaylandWindowReflectionExtensions.cs
|
||
└── WaylandWindowUtils.cs
|
||
```
|
||
|
||
### Windows
|
||
|
||
- `SetWindowIgnoreMouseEventsWindows()` 修改 `WS_EX_TRANSPARENT/WS_EX_LAYERED`。
|
||
- `WindowsCsdSizingHook` 封装 opt-in 的 `WM_GETMINMAXINFO` track size 修正和
|
||
`WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE` live resize 信号。
|
||
- Windows 目录可以封装 DWM 属性、系统 metric、Win32 消息结构体和受限的可释放 hook。
|
||
- Windows 目录不决定 live resize 策略、合成后端策略、CSD/SSD 策略或控件行为。
|
||
- 窗口装饰与 resize hit-test 由 Avalonia CSD 管理;AtomUI 不处理 `WM_NCCALCSIZE` 或
|
||
`WM_NCHITTEST`,不返回 resize hit-test。
|
||
- 标题栏按钮通过 Avalonia 公共 `WindowDecorationProperties.ElementRole` 接入原生行为,
|
||
不需要 AtomUI 自定义 caption WndProc hook。
|
||
- 允许 Native 层提供窄范围、显式 opt-in、可释放的 sizing/message helper,例如只处理
|
||
`WM_GETMINMAXINFO` 或 live resize 生命周期信号;这类 helper 不能实现第二套 window chrome。
|
||
|
||
### macOS
|
||
|
||
- Objective-C runtime 调用 `NSWindow.ignoresMouseEvents`。
|
||
- 查询和重排 standard window buttons。
|
||
- x64 的 32-byte `NSRect` 返回走 `objc_msgSend_stret`,其他架构走 `objc_msgSend`。
|
||
|
||
### Linux/X11
|
||
|
||
- Xlib 设置 map 前初始 geometry 和 `WM_NORMAL_HINTS`。
|
||
- `_GTK_FRAME_EXTENTS` 告知 Mutter/KWin CSD 阴影范围。
|
||
- 进程级独立 XCB 连接复用 SHAPE input-region 请求。
|
||
|
||
X11 能力必须以 `IPlatformHandle.HandleDescriptor == "XID"` 为前置条件,不能只判断 Linux OS。
|
||
|
||
### Linux/Wayland
|
||
|
||
`WaylandWindowUtils` 只接受已经解析出的 NWayland `WlSurface/WlCompositor`,创建 region 并调用
|
||
`SetInputRegion()`。Avalonia 私有字段解析集中在 `WaylandWindowReflectionExtensions`,不能扩散到
|
||
`WaylandWindowUtils` 或上层业务控件。
|
||
|
||
但 Avalonia Wayland 后端的真实 surface 属于专用 worker。当前调用方从 UI 线程越过
|
||
`WXdgTopLevelProxy` 直接调用真实 `WlSurface`,不符合上游线程和重连契约。`DynamicDependency` 只保证
|
||
reflection metadata 不被裁剪,不能解决该问题。正确边界应由 Avalonia persistent surface/proxy 提供
|
||
input-region 方法,并通过 `WaylandWorkerClient.PostWithCommit` 下发。
|
||
|
||
### FileSystem
|
||
|
||
- `NativeFileSystem` 封装跨平台文件系统原生调用,例如 no-follow 打开文件和解析目录 canonical path。
|
||
- 文件系统能力可以被 `AtomUI.Core` 使用;判断标准是是否需要 P/Invoke、平台错误码、native handle 或
|
||
OS-specific flag,而不是调用方是否位于 Core。
|
||
- Native 层只负责底层安全打开、路径解析和错误转换,不决定 ThemeDefinition 的加载策略、搜索路径或
|
||
用户目录信任模型。
|
||
|
||
## 设计约束
|
||
|
||
1. 平台判断和后端判断是两层条件。Linux 不等于 X11,非 X11 也不等于 Wayland。
|
||
2. 只有 Avalonia 公共 API 无法表达、且涉及原生调用或原生协议细节的能力才进入 Native。
|
||
3. Window 相关 native 能力可以进入 Native;限制点是不能在 Native 层拥有 Window/Dialog 控件策略。
|
||
4. Native 层可以持有原生资源或可释放 hook,但 owner 必须清晰,上层必须有明确释放点。
|
||
5. Native 层不修改 Avalonia control properties,不决定 resize band、主题 token 或 Dialog/Window 行为。
|
||
6. Native 层不订阅控件业务事件;需要事件驱动时,上层负责订阅 Avalonia 事件并调用 Native 能力。
|
||
7. 原生调用失败不能让 Window manager 的更新队列永久卡死;异步/延迟状态更新需要 `try/finally` 恢复标志。
|
||
8. AOT 要同时满足 metadata 保留和真实 publish;`DynamicDependency`、普通 build、源码字符串测试分别只能
|
||
覆盖一部分风险。
|
||
|
||
## 已知结构问题
|
||
|
||
### 平台依赖扩散
|
||
|
||
`AtomUI.Native` 当前只面向桌面控件等需要原生能力的上层库开放。`AtomUI.Desktop.Controls` 直接引用
|
||
`Avalonia.Desktop` 和 `Avalonia.Wayland`,Browser 当前通过 WASM 内部 item 过滤部分 desktop assembly,
|
||
这只是构建补偿,不是清晰的依赖边界。
|
||
|
||
长期建议把 desktop bootstrap 与具体后端拆成可选 assembly,例如 X11、Wayland、Win32、macOS backend
|
||
包;Browser 只引用平台无关 controls,不应先引入桌面包再从 bundle 中删除 assembly。
|
||
|
||
### API 命名和校验
|
||
|
||
`WindowExtensions` 只承担内部跨平台路由,具体实现保留在 `Windows`、`MacOS` 和 `Linux` 目录。
|
||
只有 Avalonia 公共 API 无法表达的能力才进入 Native;平台实现应统一校验 handle descriptor、零句柄、
|
||
尺寸范围和平台。是否继续拆分文件取决于资源所有权和生命周期复杂度,不为了形式上的分层搬移代码。
|
||
|
||
### 测试层级
|
||
|
||
当前 Native 相关测试主要读取源码并断言字符串。这能防止某些旧代码回归,但不能验证 ABI、线程、资源释放、
|
||
协议顺序或真实 NativeAOT。至少需要:
|
||
|
||
- 纯计算和参数校验单元测试。
|
||
- 可替换 native façade 的调用顺序/错误路径测试。
|
||
- X11/Wayland 集成测试或最小 smoke app。
|
||
- Linux/Windows/macOS 的 NativeAOT publish matrix。
|
||
|
||
## 依赖升级检查
|
||
|
||
Native 架构结论以 AtomUI 的平台边界、公开契约和验证矩阵为准。升级 Avalonia 或原生协议依赖时,至少重新验证:
|
||
|
||
- Wayland proxy、worker 和 persistent surface 的线程所有权与 commit 顺序。
|
||
- CSD/SSD 协商、shadow extents 和 window geometry 的状态传播。
|
||
- X11 handle descriptor、drawn decorations gating 和 input-region 行为。
|
||
- Win32/macOS 平台能力仍由公开 API 或隔离的 Native 边界承担。
|
||
|
||
外部框架的类型名可以作为调查入口,但文件路径、源码行号和某次源码快照都不是 AtomUI 的架构契约。
|