news 2026/10/30 6:49:09

【A11 PC信息】PC资产信息提取主程序解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【A11 PC信息】PC资产信息提取主程序解析

这是一个Rust 编写的 Windows GUI 小程序,作用是:自动采集本机设备信息 → 弹窗让用户选择保存位置 → 导出为 Excel 盘点表。下面按结构逐部分拆解。


一、整体功能流程

启动 → 隐藏控制台(GUI 子系统) │ ├─ 1. terminal::detect() 采集本机硬件/设备信息 ├─ 2. 获取北京日期 + 桌面路径 构造默认文件名(设备信息登记表YYYYMMDD.xlsx) ├─ 3. ask_save_path() 弹 Windows 原生"另存为"对话框 └─ 4. asset::save(info, path) 若用户确认 → 生成 xlsx 文件

任一步出错都不会崩溃,而是写入%TEMP%\pc_ledger_error.log。


二、顶部属性与导入

#![windows_subsystem = "windows"]

crate 级属性,告诉链接器使用Windows GUI 子系统。效果是双击 exe 时不会弹出黑色控制台窗口——桌面小工具的标准做法。代价是println!等 stdout 输出不显示,因此代码里所有错误都改用文件日志来记录。

依赖的 crate

导入用途
bridge::time::BeijingDateTime自建库,取北京时间
windows::core::{w, HSTRING}w!宏生成宽字符串字面量PCWSTR;HSTRING是 Windows 堆分配 UTF-16 字符串
windows::Win32::System::ComCOM 初始化/销毁、创建对象、释放 COM 内存
windows::Win32::UI::Shell文件保存对话框、FOLDERID_Desktop已知文件夹、SIGDN_FILESYSPATH
mod asset; mod terminal;两个业务模块:Excel 写入、终端信息采集

三、main()主流程

letinfo=terminal::Terminal::detect();// ①采集letnow=time::BeijingDateTime::now();// ②获取北京日期let(year,month,day)=now.ymd();letdate_str=format!("{:04}{:02}{:02}",year,month,day);// 例:20250115letdesktop=desktop_path().unwrap_or_else(||{// ③桌面路径,失败回退"."write_error_log("获取桌面路径失败,退回到当前目录");".".to_string()});letdefault_path=format!("{}\\设备信息登记表{}.xlsx",desktop,date_str);ifletSome(save_path)=ask_save_path(&default_path){// ④弹窗ifletErr(e)=asset::save(&info,&save_path){// ⑤落盘write_error_log(&format!("Excel 导出失败: {}",e));}}

设计要点

  • 默认文件名中嵌入日期,避免每日盘点覆盖上次结果。
  • 桌面路径获取失败时降级到当前目录,程序仍能运行。
  • 用户点"取消"返回None,什么都不做直接退出(无提示,也是无窗口程序的常见风格)。

四、write_error_log()— 错误日志

fnwrite_error_log(msg:&str){letlog_path=std::env::temp_dir().join("pc_ledger_error.log");let_=std::fs::write(log_path,msg);}
  • 写入%TEMP%\pc_ledger_error.log,避开当前工作目录(双击运行时工作目录可能是System32或不可写的位置)。
  • std::fs::write是覆盖写,而非追加——即只保留最后一次错误,简单但也意味着历史错误会丢。
  • 忽略返回值的let _ =,防止日志本身出错时再引发 panic。

五、desktop_path()— 取桌面路径(Win32)

unsafe{letpath_ptr=SHGetKnownFolderPath(&FOLDERID_Desktop,KF_FLAG_DEFAULT,None).ok()?;ifpath_ptr.is_null(){returnNone;}letpath_result=path_ptr.to_string();// ①先转 StringCoTaskMemFree(Some(path_ptr.0as*const_));// ②无条件释放letpath_string=path_result.ok()?;// ③再判成败ifpath_string.is_empty(){None}else{Some(path_string)}}

关键点 1:用SHGetKnownFolderPath而不是%USERPROFILE%\Desktop

  • 用户可能把桌面重定向到 OneDrive、D 盘等,硬编码路径不可靠。
  • SHGetKnownFolderPath返回CoTaskMem 分配的宽字符串指针PWSTR,必须手动CoTaskMemFree释放,否则泄漏。

关键点 2:转换 → 释放 → 判结果的顺序

  • pwstr.to_string()可能因为字符串内非法 UTF-16(如孤立代理项)而返回Err。
  • 如果写成path_ptr.to_string().ok()?,遇到错误会提前return,跳过CoTaskMemFree→ 内存泄漏。
  • 因此代码把"释放"放到转换和结果判定之间,保证无论如何都释放。这是很细心的正确写法。

关键点 3:path_ptr.0 as *const _

  • PWSTR是pub struct PWSTR(pub *mut u16)的元组结构体。
  • CoTaskMemFree签名是Option<*const c_void>,所以取.0再 cast。
  • Some(...)而不是裸指针,因为 API 定义如此。

六、ask_save_path()— COM 生命周期管理

unsafe{ifCoInitializeEx(None,COINIT_APARTMENTTHREADED).is_err(){...;returnNone;}letresult=show_dialog(default_path);CoUninitialize();result}
  • COINIT_APARTMENTTHREADED=STA(单线程套间)。文件对话框、Shell 对象都要求 STA 模式,用 MTA 会失败或行为怪异。
  • 严格初始化 / 反初始化成对。注意:如果show_dialog内部发生 panic,CoUninitialize不会执行——但 Rust 默认panic=abort在windowscrate +windows_subsystem下通常也不会跨 FFI 回溯,所以这里可接受,但严谨写法可用 RAII guard(struct ComScope+Drop)自动反初始化。

七、show_dialog()— 真正的对话框实现

letdialog:IFileSaveDialog=CoCreateInstance(&FileSaveDialog,None,CLSCTX_INPROC_SERVER).ok()?;
  • CoCreateInstance创建IFileSaveDialog接口实例。FileSaveDialog是 CLSID,CLSCTX_INPROC_SERVER表示进程内 DLL。
  • 这是 Vista+ 的 COM 文件对话框(比旧的GetSaveFileNameW灵活,支持自定义控件、过滤、扩展名)。

7.1 设置对话框属性

let_=dialog.SetTitle(&HSTRING::from("保存资产盘点表"));letfilter=COMDLG_FILTERSPEC{pszName:w!("Excel 文件 (*.xlsx)"),// 显示给用户的名字pszSpec:w!("*.xlsx"),// 实际过滤模式};let_=dialog.SetFileTypes(&[filter]);let_=dialog.SetDefaultExtension(w!("xlsx"));
  • w!宏用于编译期常量宽字符串;HSTRING::from用于动态构造(SetTitle需要HSTRING)。
  • COMDLG_FILTERSPEC的字段是PCWSTR,所以用w!。
  • 所有Set*调用都用let _ =忽略错误——属性设置失败不致命,对话框仍能显示。

7.2 预设默认文件名 & 目录

letpath_obj=std::path::Path::new(default_path);ifletSome(file_name)=path_obj.file_name(){let_=dialog.SetFileName(&HSTRING::from(file_name.to_string_lossy().as_ref()));}ifletSome(folder_path)=path_obj.parent(){ifletOk(folder_item)=SHCreateItemFromParsingName::<_,_,IShellItem>(&HSTRING::from(folder_path.to_string_lossy().as_ref()),None,){let_=dialog.SetFolder(&folder_item);}}
  • 用std::path::Path解析"C:\Users\x\Desktop\设备信息登记表20250115.xlsx",分别取出文件名、父目录。
  • SHCreateItemFromParsingName把文件系统路径转成IShellItem(Shell 命名空间对象),因为SetFolder要的是IShellItem。
  • 泛型<_, _, IShellItem>让编译器推断前两个参数类型、显式指定输出类型。
  • 若SetFolder失败(路径不存在等),对话框仍会打开,只是默认位置不是桌面。

7.3 显示模态对话框并取结果

ifdialog.Show(None).is_ok(){// None = 无 owner 窗口ifletOk(result_item)=dialog.GetResult(){// 拿到用户选择ifletOk(path_ptr)=result_item.GetDisplayName(SIGDN_FILESYSPATH){if!path_ptr.is_null(){letpath_result=path_ptr.to_string();CoTaskMemFree(Some(path_ptr.0as*const_));letpath_string=path_result.ok()?;ifpath_string.is_empty(){returnNone;}returnSome(path_string);}}}}None
  • Show(None):模态对话框,无 owner 窗口(因为程序无主窗口)。
  • SIGDN_FILESYSPATH:请求文件系统路径形式的显示名(相对的还有SIGDN_NORMALDISPLAY友好名、SIGDN_URL等)。
  • 同样采用转换 → 释放 → 判成败的顺序,原因同前。
  • 用户点取消时Show返回Err(HRESULT_FROM_WIN32(ERROR_CANCELLED)),这里统一走None分支。

八、安全性与内存管理要点汇总

场景处理
SHGetKnownFolderPath返回的PWSTRCoTaskMemFree释放,且放在转换后、判结果前
GetDisplayName返回的PWSTR同上
COM 初始化CoInitializeEx(STA)+CoUninitialize成对
STA 要求COINIT_APARTMENTTHREADED,文件对话框必须
指针空判is_null()检查后再to_string()
错误日志写%TEMP%,不依赖 cwd;let _ =忽略返回值

九、可以改进的地方(供参考)

  1. 错误日志用覆盖写:建议改OpenOptions::new().append(true).create(true),保留多次运行历史。
  2. 失败时无 UI 反馈:asset::save失败、COM 初始化失败,用户看不到任何提示,只写日志。可考虑MessageBoxW。
  3. COM 反初始化未 RAII 化:若show_dialogpanic,CoUninitialize会漏掉。可用带Drop的 guard。
  4. write_error_log中的let _ =是好的;但多错误时相互覆盖,可考虑追加时间戳。
  5. SIGDN_FILESYSPATH结果为空串检查:return None是对的,但空串理论上等价于取消,可以合并逻辑。
  6. filter 生命周期:COMDLG_FILTERSPEC数组传引用&[filter],栈上对象在SetFileTypes调用期间有效即可(Shell 内部复制),当前写法正确。
  7. 文件路径混用/和\:default_path用\\拼接(Windows 路径),后续Path::parent在 Windows 上能正确处理;但若桌面路径尾部已含\也无碍,只是可读性偏 Windows 风格。
  8. asset::save调用前建议检查扩展名:用户可能手输foo.xls,SetDefaultExtension只补全无扩展名情况。

十、一句话总结

这是一段结构清晰、内存管理严谨的 Win32+COM Rust 代码:利用#![windows_subsystem]做成无窗口的小工具,通过SHGetKnownFolderPath精确定位桌面、通过IFileSaveDialog提供原生保存体验,并非常细致地处理了COM 内存释放顺序与STA 线程模型两个最容易出错的点。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 6:48:22

2026-10-28 hetao1733837 的刷题记录

LGB3642 二叉树的遍历 原题链接&#xff1a;二叉树的遍历 分析 至今没有理解遍历顺序……诶……是不是说什么我建一个东西&#xff0c;每次输一下……类似于什么 lslsls 和 rsrsrs 分别为每一层的左儿子和右儿子……类似吧&#xff0c;那&#xff0c;还是看一下题解吧…… …

作者头像 李华
网站建设 2026/10/30 6:43:13

AI配音怎么制作商品详情页语音?从文案到成品的实用方法

AI配音可以用于商品详情页讲解、产品功能介绍和电商短视频旁白。对于不方便真人录音的商家或个人创作者&#xff0c;可以先整理商品卖点&#xff0c;再通过文字转语音工具生成讲解音频&#xff0c;最后检查发音、节奏和信息准确性。配朵朵是一款提供网页版、APP和微信小程序的A…

作者头像 李华
网站建设 2026/10/30 6:43:13

融合策略与生产化——让主题词“更稳、更干净、可落地”

目标&#xff1a;单一算法各有噪声&#xff0c;把多种结果交叉验证或合并&#xff0c;再接入停用词、词性过滤和批量处理&#xff0c;形成可用的主题词提取流程。 1. 交集与并集&#xff1a;用多算法投票降低噪声 有研究对比了 TF-IDF、TextRank、LDA 两两交集与并集的效果&a…

作者头像 李华
网站建设 2026/10/30 6:42:23

双目立体视觉入门——(13)三角测量与三维重建

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/30 6:41:28

跳出经验类比:第一性原理如何完成物种级颠覆 —— 从 SpaceX 航天革命到矩规评级的认知重构

马斯克对火箭发射技术的降维升级&#xff0c;不是对传统航天参数的小修小补&#xff0c;而是从底层逻辑到产业模式的全链条物种级颠覆&#xff0c;直接把航天从“国家级精密实验”拉平成了可流水线量产的工业级基础设施。核心技术降维&#xff1a;把航天级材料和工艺打穿到民用…

作者头像 李华