Windows调试必备工具
你知道吗,windows系统左下角的”win徽标“,除了可以左键打开“开始”菜单,还可以右键点击。
其中有个叫事件管理器的东西。今天碰到个问题,就是通过事件管理器+GitHub Copilot解决的。接下来详细说说是怎么解决的。
最近在写一个计时器程序,基础要求CPU占用要小于10%。试了下python随便写了个带gui的程序,cpu占用最少也要15%,那只能skip了。
接下来想起来之前写winui3的时候,还听说过个叫winform的东西,用的是C#,随便试了一下,占有率只有0.3%(还得是C/C++/C#),那就决定是你啦!
写者注
至于为什么不用winui3?winui3和winform一对比实在是太笨重了,虽然ui库+xaml确实很方便也很好用,但是对于计时器这种简单的应用还是杀鸡用牛刀了。
接下来这个计时器还有个要求,要无论在什么情况下,它都能始终“置顶”。意思就是其他应用程序的窗口不管怎么动,都不能遮挡住这个计时器,系统本身的窗口除外。
实现这个有两种方法,一种是直接把TopMost设为true,但这个方法不知道为什么在其他主机上面跑会失效,所以换了种方法。导入user32.dll来调用windows本身的API。
[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
const uint SWP_NOMOVE = 0x0002;
const uint SWP_NOSIZE = 0x0001;
const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
const uint WINEVENT_OUTOFCONTEXT = 0x0000;
void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
if (!this.TopMost)
{
SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
}
当然这么写还不够,应用窗口创建的时候这个函数就得跟着。而且我为了以防万一,写了个钩子函数监听窗口变化,只要不是置顶就重新置顶它。
然而这里就出现了一个问题:这个程序在执行一段时间后会自己闪退。后面发现是在运行了一段时间后再切换窗口就会闪退。
我反正是毫无头绪。内存泄漏?跑了一下并不是,稳定37mb。CPU占用过高?0%-0.5%的占用。本机内存不够?昨天刚申请的16g内存,才跑到10g。更要命的是vs本身的报错不明确,只说窗口的创建有异常,其他啥都看不到。
接下来想起来有个叫事件管理器的玩意,抱着试试的心态看了看,诶,还别说,真的有更详细的报错信息。
垃圾回收?把这段扔给copilot解释看看。
我是没学过.net,委托是啥,看不懂。但我大致、直觉、或许、也许感觉是钩子函数出了问题。我想象了一下,可能是这个委托啥的被.net自动回收了,但windows api不知道。变成了委托用钩子钩住了Windows api,windows api也知道有个东西钩着自己,但把东西传过去的时候发现原来只剩个钩子了,委托早就被.net回收了。(怎么有点恐怖游戏的意思)
那就照着改呗,不让垃圾收集器回收这个委托不就好了。
namespace WinFormsApp1
{
public partial class Form1 : Form
{
private WinEventDelegate _winEventDelegate;
public Form1()
{
InitializeComponent();
SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
_winEventDelegate = new WinEventDelegate(WinEventProc);
SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, _winEventDelegate, 0, 0, WINEVENT_OUTOFCONTEXT);
}
}
}
改完之后再跑,不会闪退了,问题解决。
具体的原理我是还没搞懂,只是有个大概的思路。主要是.net暂时没有深入理解的打算,还是先把python搞熟练再说。