【问题标题】:SetWindowSubclass is leaking user objectsSetWindowSubclass 正在泄漏用户对象
【发布时间】:2009-07-13 15:55:26
【问题描述】:

我正在使用Bear 来检查用户对象,并且在 RemoveWindowSubclass 上 WindowProc 计数永远不会减少。 USER 中的总数也是任务管理器中的用户对象。

我阅读了 Raymond 的 Safer subclassing 关于在销毁窗口之前删除子类的评论,但我的测试完全没有销毁它。

comctl 的工具提示类在内部为 TTF_SUBCLASS 的工具使用相同的子类化 API,因此如果您使用非协作工具提示,则会发生更多泄漏。

这是VB6代码

'--- Form1.frm '
Option Explicit

Private Declare Function SetWindowSubclass Lib "comctl32" (ByVal hwnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long, ByVal dwRefData As Long) As Long
Private Declare Function DefSubclassProc Lib "comctl32" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function RemoveWindowSubclass Lib "comctl32" (ByVal hwnd As Long, ByVal pfnSubclass As Long, ByVal uIdSubclass As Long) As Long

Private Sub Command1_Click()
    Call SetWindowSubclass(hwnd, AddressOf RedirectTabPaneEditWndProc, 10, ObjPtr(Me))
End Sub

Private Sub Command2_Click()
    Call RemoveWindowSubclass(hwnd, AddressOf RedirectTabPaneEditWndProc, 10)
End Sub

Friend Function frWndProc(ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    frWndProc = DefSubclassProc(hwnd, wMsg, wParam, lParam)
End Function

'--- Module1.bas '
Option Explicit

Public Function RedirectTabPaneEditWndProc( _
            ByVal hwnd As Long, _
            ByVal wMsg As Long, _
            ByVal wParam As Long, _
            ByVal lParam As Long, _
            ByVal uIdSubclass As Long, _
            ByVal This As Form1) As Long
    #If uIdSubclass Then '--- touch args
    #End If
    RedirectTabPaneEditWndProc = This.frWndProc(hwnd, wMsg, wParam, lParam)
End Function

如果有人可以发表评论,那么发生了什么以及如何解决泄漏将会很棒。

如果您使用 SetWindowSubclass API 进行密集的子类化,其他任何人都会收到警告。

干杯,

【问题讨论】:

    标签: vb6 subclass memory-leaks


    【解决方案1】:

    我认为称其为“泄漏”有点夸张。确实,调用 RemoveWindowSubclass 时不会恢复用户对象,但再次调用 SetWindowSubclass 时也不会分配另一个对象。您可以反复设置和移除挂钩,并且在整个过程的生命周期中,相同的用户对象似乎被一遍又一遍地重用。

    我又做了一些测试,扩展了您最简单的情况。仅作为背景参考,带有两个命令按钮且没有窗口挂钩的表单的每个实例都会消耗 六个 用户对象。 每个窗口类调用 SetWindowSubclass 确实会多消耗一个用户对象。也就是说,我可以加载该表单的多个实例,并为表单本身以及包含的两个命令按钮挂钩消息流,并使用总共两个用户对象。正如您所观察到的,这些在整个过程的生命周期内都不会被回收。

    内部设计可以更干净吗?可能。再说一次,可能不是。我根本不认为这有太多值得关注的理由。更令人担忧的原因是设计的应用程序可能会引起关注。在这种情况下,可能需要对整体 UI 设计进行根本性的重新考虑。我简直无法想象你什么时候会在一个进程中子类化这么多窗口类,以至于每个类的这个额外对象可能很重要。

    【讨论】:

    • 任何时间点子类窗口的总数可能不会那么高。在表单被加载/子类化/卸载的几天时间里,这些泄漏会累积起来。大多数 GDI 泄漏都是这样,慢慢毒化进程,直到 VB 运行时抱怨“无法加载控制”或“内存不足”。
    • 试试我的做法。加载、子类化、卸载、重复。您在初始窗口类上受到打击,而不是在每个窗口上。您计划子类化多少个窗口类? (你知道什么是窗口类,对吧?)
    • 这个泄露的子类槽不是你暗示的每个窗口类,而是每个 wndproc 地址。因此,如果您使用 Matt Curland 的 PushParamThunk 生成一个程序集存根来子类化表单(每个子类化实例上的新存根),然后有人(如 comctl32 工具提示)使用 SetWindowSubclass 子类化相同的 hwnd,即使类名不会更改 (ThunderRT6Form) 分配子类插槽,因为 wndproc 地址在任何先前的插槽中都无法匹配。我对我的观察持肯定态度(Bear 和崩溃),但你需要 MS 的人来确认我的解释。
    【解决方案2】:

    这是在 VB6 中进行子类化的一种不寻常的方式。 SetWindowLong(GWL_WNDPROC) 可能会让您更幸运 - 请参阅 Karl Peterson 的 this VB6 code

    有趣的是,看起来 Karl 是 experimenting right now,与您使用的 comctl32 函数相同。编辑:是的,他是posted an article。编辑:哦,还有an answer to this question :)

    【讨论】:

    • 我确实将所有内容都迁移到了 GWL_WNDPROC,但 comctl 工具提示在内部使用了新的子类,并且它正在泄漏......链接的 10 倍。
    猜你喜欢
    • 2011-09-08
    • 2014-12-10
    • 1970-01-01
    • 1970-01-01
    • 2012-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多