Windows 桌面图标移动的 COM 实现
基于 rust + windows-rs 移动 Windows 桌面图标 在 Windows 桌面上移动图标,看起来只是个“改坐标”的操作,但背后实际上是 Shell View + COM 交互。 笔者基于 Rust + windows-rs,实现直接操作桌面 该实现在不依赖 UI 自动化或 项目地址:win-desktop-icon 很多人一开始会误以为桌面图标属于某个窗口句柄(HWND),但实际上桌面图标属于 Explorer 的 Shell View 层。 关键对象是: 其中 获取流程大致为: 这里有两个关键点: 也就是说,一个负责位置,一个负责语义。 桌面图标并不是用 HWND 或 ID 标识的,而是 都统一抽象为 PIDL。 枚举图标时,本质是: 这里得到的 真正的核心 API 是: 调用方式类似: 此处语义如下: 本质就是告诉 Explorer: “把这个 PIDL 对应的图标移动到这个坐标”。 这一块比 COM API 本身更容易出错。 来自 来自外部 buffer: 因此实现中引入了一个关键结构: 核心思想很简单: Drop 实现也因此变得安全: 这个设计避免了两类经典问题: 图标信息分为两部分: 直接由 Shell View 返回当前 UI 布局坐标。 通过 最终再转换为 Rust String,并手动释放: 这一部分本质是 Shell 统一的字符串返回机制。 顺便一提,笔者发现 IFolderView 的图标管理层,用于枚举、读取信息以及移动桌面图标位置。ListView 无文档接口的前提下,直接驱动 Explorer 的图标布局。 桌面图标的来源不是“窗口”,而是 Shell View
IShellWindowsIShellBrowserIShellViewIFolderViewIShellFolderIFolderView 才是“图标布局控制器”。let shell_windows: IShellWindows =
CoCreateInstance(&ShellWindows, None, CLSCTX_ALL)?;
let dispatch = shell_windows.FindWindowSW(... SWC_DESKTOP ...)?;
let service_provider: IServiceProvider = dispatch.cast()?;
let browser: IShellBrowser = service_provider.QueryService(&SID_STopLevelBrowser)?;
let shell_view: IShellView = browser.QueryActiveShellView()?;
let folder_view: IFolderView = shell_view.cast()?;
let shell_folder = folder_view.GetFolder()?;IFolderView 负责“布局操作”IShellFolder 负责“名字 / PIDL / 元数据” ITEMIDLIST 才是图标的真实身份
ITEMIDLIST(PIDL)。let enumerator = folder_view.Items(SVGIO_ALLVIEW)?;
while let Some(idlist) = next_item(&enumerator)? {
...
}ITEMIDLIST 是 COM 分配的内存,在 Rust 侧需要谨慎地管理生命周期。 移动图标:SelectAndPositionItems
SelectAndPositionItemsself.folder_view.SelectAndPositionItems(
1,
&(icon..as_ptr() as *const ITEMIDLIST),
Some(&POINT { x, y }),
SVSI_POSITIONITEM.0 as _,
)?; Rust 侧内存管理的关键问题
ITEMIDLIST 的来源有两种: 1. COM 分配
IEnumIDList / Shell API:CoTaskMemFree 2. Rust 内存映射
pub struct DesktopIcon<'desktop> {
inner: NonNull<ITEMIDLIST>,
mut_ref: Option<&'desktop mut [u8]>,
}mut_ref = None → COM 管理,需要释放mut_ref = Some(...) → Rust 管理,不可释放impl Drop for DesktopIcon<'_> {
fn drop(&mut self) {
if self.mut_ref.is_none() {
unsafe {
CoTaskMemFree(Some(self..as_ptr() as _));
}
}
}
} 获取图标信息:名字与坐标
坐标
folder_view.GetItemPosition(pidl) 名字
IShellFolder:GetDisplayNameOf -> STRRET -> StrRetToStrWCoTaskMemFree(Some(name_ptr.0 as _)); 小结
IFolderView 控制布局ITEMIDLIST 作为唯一标识SelectAndPositionItems 似乎无法批量移动多个图标,不过目前实现性能足矣。