20.4 MB → 1.72 MB:把一个 Windows 工具从 tkinter 重写成 Win32 原生控件
一个记录"你打开过哪些应用"的桌面小工具,Python 版功能最全但体积 20.4 MB、常驻 5-8 MB。这一轮用 Rust + Windows 公共控件重写了一个原生版:exe 1.72 MB、托盘常驻约 4.5 MB。重写不是翻译——列表虚拟化与"运行中自动隐藏"、图标的三种形态、项目管理扫描、以及把工具变成"可以被 Agent 调用的接口",每一步都是跟平台重新谈条件。
项目:joker1point/ExeTrace(MIT)。本文对应原生版 v1.1.0(2026-10-16 发布),环境:Windows 11。
这个工具解决的问题很小:帮你找回"刚才那个软件"在哪。它后台记录你打开过的每一个应用,随时可以一键把它钉到桌面。技术上没有任何高深的地方,它甚至不需要联网。
但它有一个持续了三周的矛盾:Python 版的界面用的是 tkinter——功能最全、迭代最快,代价是单文件 20.4 MB、后台常驻 5 到 8 MB。对于"想让它静静地待在托盘里"的工具型软件,这个体量有点违和:用户下载一个 20 MB 的 exe,只为了找一个 200 KB 的程序的位置。
于是有了这一轮重写:Rust + Windows 公共控件,不引入任何 UI 框架。目标写得很直白:exe 不超过 6 MB、前台内存不超过 40 MB、常驻不超过 15 MB。
一、为什么是"没有 UI 框架"
第一版尝试用的是 egui(Rust 生态里最顺手的立即模式 GUI)。它能跑,但在"小工具"这个定位下不划算:立即模式框架自带一套渲染与状态基线,实测体积 6.8 MB、内存约 120 MB——对"静静待在托盘"的诉求来说,那 120 MB 全是框架的固定开销,跟业务代码没关系。
第二版换掉了框架,直接用 Windows 自带的公共控件:列表、输入框、按钮、下拉框、状态栏。没有渲染引擎、没有布局系统,界面就是操作系统的那套控件。

最终的实测数字(本机 2026-10-16 实测):exe 1.72 MB、托盘常驻约 4.5 MB、窗口打开约 13 MB。三个目标全部达标,其中常驻内存只有 Python 版的六成,体积是十二分之一。
值得单独说一句:两个实现共用同一份 SQLite 历史库(schema 一致),所以这不是"新版本替换旧版本",而是"两个形态"——Python 版继续保留全部功能与最快的迭代速度;原生版给"想要更轻"的用户。唯一要提醒的是别同时常驻:两份记录会互相打架。
二、重写不是翻译:四个"长得像、做起来完全不同"的地方
功能清单可以照抄,实现路径全部要重谈。四个例子:
列表与"运行中自动隐藏"。 原生列表控件不会替你虚拟化数据,行数就是性能。而在功能上,这一版新增了一条反直觉的规则:正在运行的应用从列表里隐藏。理由是这个工具的使命是"找回还没找到的"——你已经打开的东西,不需要它来告诉你路径。实现上按"进程在跑"判定(以 exe 路径确认,路径读不出来时按同名兜底),对列表做 3 秒节流复核,状态栏标出隐藏数量。验收时的一笔账很能说明问题:近 30 天的记录 78 条 = 显示 58 + 隐藏 20,隐藏的那 20 条正是此刻开着的。

项目管理。 这是"列表复用"带来的意外收获:既然能记录应用,为什么不记录项目?于是加了一个扫描本机 git 仓库的入口——列表顶部切换"应用 / 项目",项目行支持双击打开文件夹、右键打开 GitHub 页、一键更新。两个细节值得记:打开 GitHub 页之前要从仓库配置里剥掉凭据(远程地址里可能带 token);路径扫描默认只覆盖文档、工作目录、桌面这几个根,深度有限制,避免对着整个磁盘做遍历。
图标的三种形态。 一个 exe 图标要出现在三个地方:资源管理器里的文件图标(编译进 PE 资源段)、窗口与托盘的运行时图标(从可执行文件里提取 HICON)、以及界面列表里的 PNG。最后一步的转换交给系统图形接口完成缩放、掩码与透明合成,而不是自己写像素处理——平台成熟的路径往往比"自己控制每一步"更省事。
交付形态的"隐形需求"。 单实例唤醒(双击第二次时让已有窗口浮起来)、全局热键、托盘、以及一个非 GUI 自检模式:打包成窗口子系统后没有控制台,自检结果只能写进文件再回读——验收要打在交付形态上,而不是开发形态的终端里。
三、把"能交给 Agent"做成一等接口
这一版最像"新产品"的部分,是数据出口。工具本身不做 AI 相关的事,但它把"数据和动作"都开了一个机器可读的口子,任何 Agent 都能直接拿它用:
| 面 | 命令 | 输出 |
|---|---|---|
| 读 | --export [路径] | 全量 JSON:应用列表 + 项目列表(schema 版本化) |
| 读 | --find <关键词> | stdout JSON;退出码 0 表示有结果、1 表示无结果 |
| 做 | --launch <路径> | 打开应用或文件夹 |
| 做 | --make-shortcut <路径> | 钉到桌面(成功后读回校验) |
| 交接 | --agent-guide | 生成一份随本机路径的接口说明文本,整段转发给 Agent 即可 |

这份接口里最容易忽略、也最值得写进文档的是一条契约:这是图形子系统的 exe,在 PowerShell 里直接以调用运算符执行时不会等它跑完——命令立刻返回,文件还没生成。正确的调用姿势是经由命令解释器执行、或者显式等待进程,并且以产物文件为准(标准输出可能晚到)。这条契约是从真实踩坑里写出来的:自动化脚本里遇到过"命令成功了但文件不存在"的假失败。
四、两个实现的边界怎么划

发布策略也按"两个形态"对齐:原生版走 README 的直达链接,下载按钮背后的"最新版"标签仍然留给 Python 版——不是为了保守,而是因为两个实现的功能集不是严格包含关系(原生版有"运行中隐藏""项目管理",Python 版有更完整的分类与统计显示)。标签指向哪一边,取决于用户更需要什么;把它写清楚,比"最新即最好"更诚实。
多步交互的功能照旧用真机录制动图来展示(搜索到钉桌面的完整流程),这也是这个项目一贯的交付标准:宣称的能力要有真实运行的画面。

五、关于验证:把它当"交付物"而不是"代码"来验
这一轮重写顺带把验证也收紧了三层:
- 自检成为命令:非 GUI 自检覆盖路径模型、数据库读写与去重、注册表回填扫描、图标提取、噪音过滤、窗口枚举等 33 项,源码形态与打包形态各跑一遍。
- 端到端走真实数据:本机实测 361 个应用、58 个项目,验证查询/启动的成败分支与交接文本的生成。
- 推送即门禁:仓库挂了工作流,每次 push 自动跑语法检查、非 GUI 自检、README 素材规范与对外材料扫描;原生版另有一个编译检查任务。绿了才谈发布。
20.4 MB 到 1.72 MB,缩的不只是体积——是"这个工具要不要活得像个系统组件"这个问题的答案。功能可以慢慢加,但一个常驻小工具的内存与体积,是它每天都要替你付的账。