您现在的位置: IT专家网 > WinSystem子站 > 技巧
XP两种工具在代码中检测并堵塞 GDI 泄漏
Windows 需要一种不太相同的 GDI 泄漏方法,他构建并说明了两种工具,这两种工具旨在检测并消除在 Windows XP、Windows 2000 和 Windows NT 上运行的应用程序中的 GDI 泄漏。
虽然这种方案看起来很完美,但它会在调试器线程和插入调试对象的线程之间引起死锁。第 3 步和第 4 步之间有一个隐患。Win32 调试 API 假定调试器正在利用 WaitForDebugEventsumes 等待一个调试对象事件。当该函数返回时,一直到 ContinueDebugEvent 被调用,调试对象中的所有线程(甚至那些没有生成调试事件的线程)都被挂起。因此,停留在第 3 步和第 4 步的调试器线程将永远不会由调试对象执行,因为在同步通信结束后 ContinueDebugEvent 才应执行。要切记,从由调试器接收的调试事件来同步调试对象中的行为是不可能的。
在 GDIUsage 的情况下,基于消息的机制提供的通知速度好像已足够迅速。真正的问题出在其他地方。由于调试对象检索到第一条消息后就启动渗透线程,所以所有静态链接的 DLL 都已经初始化。如果其中的一些已经分配了 GDI 对象,这样的消耗将永远不会被 GDIUsage 检测到。在这种情况下,需要另一种方法来执行修补代码。GDIUsage 帮助您检测和找到的 GDI 对象创建不是在应用程序的生存期内发布的,而是在其启动时发布的。
显示 GDI 对象
图 1 中所示的 GDIUsage 用户界面允许用户获得在任何给定时间点某进程使用的 GDI 对象的快照,并且稍后可以与同一进程的当前状态进行对比。每组对象都保存在 CGdiResources 对象中。可以在 TM_GET_LIST 注释中看到,对象列表一旦通过内存映射文件在调试对象和调试器之间被序列化和封送处理,利用 CreateFromList 就可以初始化一个 CGdiResources 对象。
在 Windows 9x 版本的 GDIUsage 中,负责显示 GDI 对象的 CgdiResourcesDlg 类接受指向 CGdiResources 对象的指针作为参数。遗憾的是,在 Windows XP 版本的 GDIUsage 中,由于用来以图形方式显示 GDI 对象(在调试对象内部创建)的 GDI 函数总是出故障,因此在调试器的上下文中,CGdiResourcesDlg 就变得毫无价值。
解决方案是移去 GDITrace.dll 内部的 CGdiResourceDlg 和 CgdiResources,同先前讨论的一样,GDITrace.dll 通过 Windows 挂钩被加载到调试对象中。在检索完已分配对象的列表后,就该显示这个列表了。使用的通信机制和先前讨论的一样,但在显示列表并为渗透的线程发送消息之前,该列表必须在调试器的上下文中保存到内存映射文件。调试器将 CGdiResources 列表(或者当前的快照,或者对比的结果,取决于用户单击的按钮)序列化到内存映射文件,并将一条 TM_SHOW_LIST 消息发送给调试对象中渗透的线程,其中利用 GDIUsage 主对话框窗口的句柄作为参数。在这样的处理中,由 CGDIDebugger::ShowList 完成序列化,而由 ShowRemoteList 完成消息发送。
与 TM_GET_LIST 一样,由渗透的线程处理 TM_SHOW_LIST 消息,接着发送到 CGDITraceApp::OnShowList。这种方法根据内存映射文件中保存的序列化与封送数据初始化 CgdiResources。现在,有可能让 CGdiResourcesDlg 来显示对应的 GDI 对象了。
与 TM_GET_LIST 消息不同,为了将该显示命令发送给调试对象,调试器不需要任何返回代码或信息。在任何情况下,用户都希望 GDIUsage 保持禁用,直到他关闭了该 CGdiResourcesDlg 对话框。这就是将 GDIUsage 主对话框的句柄作为父窗口传递给 CGdiResourcesDlg 对象的原因。
除了有两点改进之外,该类的实现从 Windows 9x 版本以后就没有更改过。第一点改进是利用构建 GDI 句柄的方法来检测它是否引用一个常用对象以及是否在列表框的对应行中添加一个 *。这种功能在 GDIUsage 中是不可见的,因为创建常用对象的 API 没有被修补过,但您可能在编写自己的代码时想实现它。
第二点改进是,为了呈现对应的堆栈跟踪,用户单击或者双击 GDI 对象列表时需要进行检测,如图 10 所示。由于不应当将 CGdiResourcesDlg 代码链接到堆栈跟踪代码,因此定义了一个通用的回调接口 INotificationCallBack。CGDITraceApp 类派生于该界面,它实现了 OnDoubleClick 并使用 CGdiResourcesDlg::SetNotificationCallBack 对其自身进行注册。
当用户双击某个 GDI 对象时,CGdiResourcesDlg 要检查是否已经注册了一个回调。如果是,它就调用该回调的 OnDoubleClick 方法,并以被双击的 GDI 对象对应的 CGdiObj 说明作为参数。然后,CGDITraceApp 从传递的 CGdiObj 提取调用堆栈,并用它实例化 CcallStackDlg,从而为用户显示堆栈跟踪(有关实现的详细信息,请参见 GDITrace.cpp 中的 CGDITraceApp::OnDoubleClick)。
- 本文关键词:
- API
- IT技术
- Windows
- Windows XP
- 操作系统

