查看: 132|回复: 0

PowerShell内联C#开发WinForms桌宠与CPU优化

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
项目形态与技术路线
Desktop Cat 是一个 Windows 桌面像素猫:在桌面上走动,包含发呆、走路、伸懒腰、跳舞、撒欢、摔倒等 12 组动画,支持 75%、100%、125%、150% 四档大小;好感度分 6 级,从“陌生”到“心之友”,上限 400;右键菜单 13 项,全中文。主程序 DesktopCat.exe 为 54,272 字节(约 53 KB),源码是一个 2,717 行的 pet.ps1。空闲时单核占用 0.4% 到 0.8%,内存约 60 MB;无网络请求,无注册表写入,数据只写在自己目录内,许可为 MIT。安装是解压后双击 exe,卸载是删除文件夹。

它最特别的地方不是界面,而是实现路径:单个 .ps1 文件内联一段 C# WinForms 代码,运行时用 Add-Type 现场编译。没有构建步骤,没有外部依赖。最初尝试 Electron,但在目标机器上出现 FATAL:platform_channel.cc(83) Check failed: .: 拒绝访问。 (0x5),进程无法启动,基本可判断被安全策略拦截,于是放弃。重新梳理需求后,目标被限定为四点:零依赖,不能让用户装 .NET SDK、Node 或 Python;不联网;不要构建步骤;单文件。Windows 上能同时满足这些条件的组合,基本只有系统自带的 PowerShell 5.1 和 .NET Framework,再配合 Add-Type 在运行时把 C# 编译到内存程序集。

这条路的收益是 Win10/Win11 开箱即跑,不需要管理员权限,也不需要安装任何环境。代价也明确:源码是 .ps1,IDE 断点调试基本用不上;每次启动都要重新编译内联 C#,同一次运行只编译一次;杀毒软件对“脚本里编译代码”的行为天然敏感,需要向用户解释。

Add-Type 内联编译 WinForms
核心调用只有一行参数,但有几个必须注意的点:
  1. Add-Type -TypeDefinition $csharpCode -ReferencedAssemblies System.Windows.Forms, System.Drawing, System.Core
复制代码
-ReferencedAssemblies 必须显式写上 System.Windows.Forms 和 System.Drawing,否则编译不过。C# 代码建议用 here-string 包起来;内联 C# 中不要出现与 here-string 结束符冲突的字符序列,否则 PowerShell 会提前截断,报错往往很难定位。编译只做一次,编译结果留在内存里,之后直接 New-Object 对应类型即可。Add-Type 编译失败时异常信息比较绕,实践上先用一段 10 行以内的最小 C# 跑通,再把代码逐步搬进去。

整个程序的骨架也因此很清晰:PowerShell 负责资源、配置和胶水逻辑,C# 负责窗口、绘制、动画和消息处理。

透明窗口与 WM_NCHITTEST 点击穿透
桌面宠物需要“看得见猫、看不见框”。WinForms 里较省事的方式是透明色:
  1. FormBorderStyle = FormBorderStyle.None
  2. TransparencyKey = Color.FromArgb(255, 1, 2, 3)
  3. SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true)
复制代码
这里用 (255,1,2,3) 这种颜色作为透明键,是为了避免和素材里的颜色撞车;一旦撞上,猫身上就会出现一个真实的洞。

更麻烦的是点击穿透。桌宠有两个互相矛盾的需求:猫身上的点击要能被它收到,用于点头涨好感度;猫外面的区域必须完全穿透,不能挡住桌面图标点击和文件拖拽。实现方式是处理 WM_NCHITTEST(0x0084):命中测试时读取该像素的 alpha 值,低于阈值就返回 HTTRANSPARENT(-1),让系统把点击交给下层窗口;alpha 足够高则返回正常值,这次点击算在猫身上。阈值取 40,这个数字直接决定手感:太低,周围半透明边缘会偷走点击;太高,猫的轮廓会点不中。

从实现取舍看,这种命中测试依赖像素级透明度,而不是控件矩形。优点是轮廓贴合图像,代价是需要调阈值,并且要把透明键与素材颜色一起设计。

全局鼠标钩子要克制
窗口是穿透的,所以“点猫”不能只靠窗体自己收 MouseDown。这里使用全局低级鼠标钩子:
  1. SetWindowsHookEx(WH_MOUSE_LL /* 14 */, proc, IntPtr.Zero, 0);
复制代码
关键是克制:只处理 WM_LBUTTONDOWN,其他消息立刻放行返回。低级钩子是全局的,回调写得越重,整台机器的鼠标就越钝。这也是 CPU 占用能压下来的前提之一。实测整个动画循环摊到每帧大约 0.12 毫秒。

性能坑:不要用 Application.Idle 驱动动画
这是项目里最值得记录的排障。第一版思路很自然:桌宠在有变化时重绘,那就放在 Application.Idle 里绘制,空闲就画。实测结果很糟:跑 20 秒,用进程 TotalProcessorTime 前后差值对比墙钟时间,20 秒墙钟里烧掉 16.27 秒 CPU 时间,等效单核 81%。桌面上摆一只猫,等于一个核心在满负荷运行。

问题在于 Application.Idle 不是“系统空闲”,而是“消息队列空了”。桌面程序的消息队列大部分时间都是空的,所以该事件触发极其频繁,动画就变成了尽可能快地重绘。修复方式是把重绘从“空闲就画”改成帧驱动:用一个 15 毫秒间隔的 Timer 推进行为状态机,负责散步、发呆、切换动作;只有状态真的变化时才调用 Invalidate() 请求重绘;没有变化就不重绘、不分配、不计算。改完后空闲时单核占用 0.4% 到 0.8%,内存约 60 MB,前后差了约两个数量级,而代码量并没有增加多少。给同样写 WinForms/WPF 动画的人一句提醒:别把 Application.Idle 当定时器用,它不是“空闲时执行一次”,而是“只要没事干就执行”。

持久化:手写 JSON 与数据边界
数据只有一份 config.json,内容包括位置、好感度、互动次数、静音、锁定、缩放、是否置顶。这里没有引入配置库,而是手写 JSON 解析。原因之一是 PowerShell 5.1 的 ConvertFrom-Json 在碰到某些字符时行为不理想,同时也不想为一个小配置引入第三方模块。读文件时找 key,取它后面第一个数字;写的时候同样手拼字符串。

有一个需要提醒用户的坑:程序退出时会保存配置。所以运行期间直接修改 config.json,改动会被覆盖;想改数字,先把程序退干净再改。数据全部限制在自己目录内,不写注册表、不写 %APPDATA%、不联网,卸载就是删文件夹,不留任何东西。

交互设计与等级门槛
几个小决定直接影响了体验。双击把猫叫回屏幕中央,但不给好感度,否则用双击刷等级会比认真互动还快。好感度只有四个入口:点头顶 +2、点身体 +1、拽尾巴 -1、滚轮 ±10。入口少,等级才有意义。等级门槛为 0、20、50、100、200、400,共六档,前两档很快,后面越来越慢。透明色不要用纯黑或纯白,因为素材里大概率会撞色。

适用场景与建议
“PowerShell 内联 C#”适合做零依赖、单文件、不联网的 Windows 小工具,尤其是分发环境不可控、用户不想装运行时的场景。它的最大成本不在写代码,而在调试体验:.ps1 内联代码难以使用常规断点,启动时还要重新编译 C#。因此建议先用这种方案做一个 50 行以内的窗口,跑通 Add-Type 的引用和 here-string 两个坑,再往里堆功能,别等它长到几千行才发现调试成本。

Desktop Cat 以 MIT 开源,源码和免安装 Release 在同一个仓库:GitHub 仓库 wt0812/desktop-cat,Gitee 仓库 wutong2005/desktop-cat;国内软件推荐社区小众软件(Appinn)也有相关收录。以上数字和代码细节来自实际项目与真机实测。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-10-14 12:22 , Processed in 0.033883 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部