
windows-window面向 HWND 托管场景的最小化窗口创建与消息循环实战指南【免费下载链接】windows-rsRust for Windows项目地址: https://gitcode.com/GitHub_Trending/wi/windows-rswindows-window是 Rust for Windows 仓库本仓库crates/libs/window中一个刻意保持小巧、编译迅速的 crate专门为需要一个顶层窗口与消息循环来托管 swap chain、WebView2、Direct2D/Direct3D 渲染器的桌面场景而设计。它把窗口类注册、DPI 设置、CreateWindowExW、消息分发与销毁等 Win32 细节全部封装为私有实现向应用只暴露安全的Window类型、两个可选回调与四种消息循环原语。读完本文你将掌握它的适用边界、创建与所有权模型、四种消息循环的取舍以及如何把它与 WebView2、交换链等宿主 API 做 resize 联动。何时使用 windows-windowwindows-window解决的问题非常具体为一个能接收原始HWND的 APIswap chain、WebView2 controller、Direct2D/Direct3D 渲染器等提供一个最小的顶层窗口与消息循环。仓库文档docs/crates/windows-window.md明确给出了三条判断标准需要一个top-level 窗口 消息循环来托管其他 API希望避免依赖完整的windowscrate也无需为基本的窗口创建手工编写windows-bindgen构建脚本接受只做窗口不做 UI 框架的分工边界。它不是通用 UI 工具包。菜单、控件、输入策略、多窗口协调以及特化消息处理仍是应用自身的责任见 window/readme.md 与文档 The crate is not a general UI toolkit 的边界说明。从源码结构看整个 crate 只包含一个手写的src/window.rs和一份由tool_bindings生成的极薄 Win32 绑定src/bindings.rs依赖仅windows-core见 Cargo.toml这正是它小而快的根基。前置条件与线程/所有权约束在使用前需要明确三条硬性约束文档 Prerequisites 章节面向 Windows 桌面应用必须在拥有消息队列的线程上创建并驱动窗口——即 UI 线程模型跨线程使用窗口句柄或消息循环不在本 crate 的职责内。Window::hwnd()借用即用接收该句柄的互操作代码不得在Window生命周期之外保留句柄文档原话Interop code receivingWindow::hwnd()must not retain the handle beyond theWindowlifetime。原因在源码中可以印证Window(HWND)的Drop实现会调用DestroyWindow并释放GWLP_USERDATA中装箱的State见 window.rs句柄过期后即成为悬垂句柄。DPI 策略在首个窗口类注册时被设定register_class()通过OnceLock保证进程内只注册一次窗口类并在注册时调用SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)将进程 DPI 感知设为per-monitor v2window.rs。因此如果应用需要不同的进程 DPI 策略必须在创建任何Window之前设置否则注册后的设定无法被覆盖。依赖与最小示例可参考 window/readme.md其中给出了完整的Cargo.toml依赖引入方式与创建窗口 → 打印句柄 → 运行消息循环的起步代码。第一条工作流托管随窗口缩放的内容绝大多数集成场景遵循同一条六步工作流文档 First workflow 章节仓库源码与其逐一对应把共享的渲染器/控制器状态放到RcRefCell_或其它 UI 线程所有者后面——因为回调以闭包形式被装箱到State中需要在多回调间共享可变状态。用on_resize闭包接收新尺寸并更新托管内容。on_resize回调收到的宽高是客户区物理像素wndproc中从WM_SIZE的lparam低位 16 位解析出 width、高位 16 位解析出 height见 window.rs。create()创建窗口后用client_size()获取初始客户区尺寸作为初始 swap chain 或子内容的尺寸。把hwnd()传给托管 API。按需选择run()事件驱动或run_with()渲染循环。在Window之前释放托管资源——如果宿主 API 要求父句柄在资源存活期间保持有效。on_resize还会处理 builder 安装完状态之后到达的初始 resize 消息所以先建窗口、再按回调/查询结果初始化内容是安全的顺序。这一流程在 WebView2 样例中得到了完整实现crates/samples/webview/samples/src/lib.rs 中on_resize闭包克隆RcRefCellOptionController在回调里调用controller.set_bounds(0, 0, width, height)创建后用window.client_size()得到初始尺寸并再次set_bounds——这正是文档 Interop and common options 中 Forward resize callbacks toController::set_boundsforwindows-webview 的样板代码。窗口创建与所有权模型Window::new(title)返回WindowBuilderwindow.rs可用链式方法定制Builder 方法默认值说明size(width, height)CW_USEDEFAULT初始外层窗口尺寸含非客户区边框像素style(u32)WS_OVERLAPPEDWINDOW用原始WS_*值整体替换默认样式ex_style(u32)0无扩展样式用原始WS_EX_*值替换默认扩展样式on_message(fn)无消息回调见下文on_resize(fn)无客户区 resize 回调WM_SIZE专用需要注意style/ex_style是替换而非叠加源码注释 Sets the window style (WS_*). Defaults toWS_OVERLAPPEDWINDOW传入原始值即可。on_message 与 on_resize 的分工on_message接收(hwnd, message, wparam, lparam)返回Optionisize返回Some(result)表示应用已完整处理该消息返回None则回退到 crate 的内置处理与DefWindowProcW类型定义见 window.rs。on_resize是WM_SIZE的聚焦替代。若两者同时安装且on_message处理了WM_SIZEresize 回调不会执行——因为wndproc中只有handled.is_none()时才会走到 resize 分支window.rs。create、client_size 与销毁create()内部依次执行注册窗口类 →CreateWindowExW宽字符标题经 UTF-16 编码→ 把装箱的State写入GWLP_USERDATA→ShowWindow(SW_SHOWNORMAL)创建失败时返回Error::from_thread()提取的错误window.rs。Window::client_size()调用GetClientRect失败时返回(0, 0)window.rs。销毁策略Drop会先IsWindow校验句柄仍有效再DestroyWindow防止窗口已被同步销毁时二次销毁而未处理的WM_DESTROY会自动PostQuitMessage(0)window.rs。这意味着关闭本 crate 创建的任意窗口就会结束该线程的消息循环——拥有多个顶层窗口的应用必须把这一点纳入设计例如自行拦截WM_DESTROY。四种消息循环的取舍文档用一张表概括了四种原语此处结合源码逐条展开API适用场景行为run()事件驱动更新UI、WebView 等阻塞在GetMessageW直到收到 quit 消息每取到一条消息执行TranslateMessageDispatchMessageWwindow.rsrun_with(render)可能连续需要多帧动画/渲染循环有动画需求时用PeekMessageW排空消息再调render否则GetMessageW阻塞等待收到WM_QUIT即返回Ok(())pump()等待权在外部操作如异步回调、自定义 wait用PeekMessageW(PM_REMOVE)只分发当前已排队的消息从不阻塞消费到WM_QUIT返回false否则返回truequit()应用状态需要终止循环向当前线程投递WM_QUITPostQuitMessage(0)run_with的返回值协议是渲染循环的关键window.rsrender闭包返回Resultbool——Ok(true)请求立即渲染下一帧保持animating状态继续PeekMessageW排空Ok(false)阻塞等待下一条消息再渲染适用于空闲或被遮挡状态避免忙等Err(_)传播错误并退出循环。这让渲染器可以在动画中与空闲/被遮挡两种状态间无缝切换而不必忙等 CPU。create_window样例给出了最小用法run_with(|| { println!(rendering); Ok(false) })?create_window.rs。pump的注意事项返回false后调用方应停止外层循环而在没有其它等待机制的情况下反复调用pump会空转 CPU文档明确警示 Repeatedly callingpumpwithout another wait mechanism spins the CPU。消息分发、重入与 panic 语义这是该 crate 在安全模型上最微妙的部分文档与源码注释一一对应重入reentrancy消息分发是可重入的——处理器内调用某个 Win32 API 可能在第一个回调返回前就派生出另一条消息。wndproc在调用任一回调前会先用take()把两个处理器都从State中摘出嵌套消息因此走默认处理而不会重新进入闭包、也不会二次借用闭包捕获的RefCellwindow.rs 注释原文a handler that calls SetWindowPos ... falls through to default processing rather than aliasing a handler that is already running。嵌套WM_SIZE不触发on_resize因为处理器已被摘出同步操作引发的嵌套 resize 消息不会回调on_resize。需要的话应在该同步操作内部直接应用状态更新。panic 不跨extern system边界wndproc是extern system窗口过程处理器调用没有catch_unwind包裹。从回调逃逸的 panic 会展开到这个 extern 边界并直接中止进程而不会穿过 Win32 帧window.rs 注释明确 This is intentional。需要恢复能力时要么通过捕获的应用状态返回错误要么在闭包内部自行catch_unwind。不要在消息处理器中做长时间阻塞工作它会阻塞同一 UI 线程上的绘制、输入、定时器及其它组件。window_message样例展示了on_message处理WM_PAINT、WM_LBUTTONDOWN、WM_KEYDOWN的完整写法包括用ValidateRect校验绘制区域、从lparam高低位解析鼠标坐标、把wparam当作虚拟键码window_message.rs。互操作与常见选项hwnd()只用于接受借用父句柄/目标句柄的 API需要长期持有时由应用自己负责生命周期对齐。创建后用client_size()设置初始 swap chain 或子内容尺寸参见第一条工作流。把 resize 回调转发给宿主WebView2 走Controller::set_bounds见 webview 样例swap chain 场景则调整对应缓冲。DirectComposition 宿主可能需要扩展样式如WS_EX_NOREDIRECTIONBITMAP从消费方的绑定中取得常量把其原始值传给ex_styledcomp样例即属于此模式。原始输入、绘制、键盘/鼠标、DPI、位置等行为都可通过on_message自行实现。crate有意不投影消息级的参数类型保持极小表面——这也是它能做到不依赖完整windowscrate 的原因之一。推荐的样例学习路径文档给出了七组样例本文补充其仓库内真实路径样例学习重点路径create_window基本创建 run_withcreate_window.rswindow_message绘制、鼠标、键盘消息window_message.rsstandaloneswap chain 托管与 resize 流程crates/samples/canvas/standalonedirect2d仅在可见时渲染crates/samples/windows/direct2ddirect3d12把 swap chain 绑定到句柄crates/samples/windows/direct3d12dcomp合成、自定义样式与 DPIcrates/samples/windows/dcompwebviewController 生命周期与 resize 流程crates/samples/webview/samples内部实现给贡献者的源码导读windows-window的体积优势来自明确的分层文档 Internal documentation 章节源码全部可验证绑定层src/bindings.rs由tool_bindings依据 crates/tools/bindings/src/window.txt 生成——该文件以--out crates/libs/window/src/bindings.rs --flat --minimal --dead-code生成并通过--filter精确列出 36 个符号CreateWindowExW、RegisterClassW、GetMessageW、PeekMessageW、WM_SIZE等涵盖类注册、创建、DPI、销毁与消息分发所需的最小 Win32 平面。生成结果见 bindings.rs。手写层window.rs只依赖windows-coreCargo.toml 中仅此一个依赖对外通过 lib.rs 导出Window、WindowBuilder、run、run_with、pump、quit并转发windows_core::Result。状态与生命周期进程内懒注册一个共享窗口类OnceLockCreateWindowExW之后把包含可选 message/resize 处理器的装箱State存入GWLP_USERDATAwndproc在WM_NCDESTROY时取出并释放StateWindow::drop先检查IsWindow再DestroyWindow见 window.rs。回调后的指针重读wndproc在回调返回后再次读取GWLP_USERDATA因为同步处理可能已销毁窗口并释放了State仅当状态仍存在时才把摘出的处理器装回去window.rs。改动分发行为时必须保持这一顺序。crate 刻意不做三件事在wndproc中捕获 panic、为消息提供特化包装、协调多个顶层窗口。这些边界保证了它足够小、所有权规则足够显式也决定了它小而专的定位把窗口这块最基础的拼图交给它把 UI 框架和业务逻辑留给你自己。【免费下载链接】windows-rsRust for Windows项目地址: https://gitcode.com/GitHub_Trending/wi/windows-rs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考