【问题标题】:Why does this window subclassing code crash?为什么这个窗口子类化代码会崩溃?
【发布时间】:2010-11-23 17:06:16
【问题描述】:

我正在尝试对当前具有焦点的窗口进行子类化。为此,我使用 CBT 挂钩监视 HCBT_ACTIVATE 事件,并设置和取消设置焦点窗口和先前焦点窗口的 WndProc

问题在于,它仅在我在代码中的某处设置断点时才有效

如果没有断点,一旦我的应用程序退出,我子类化的所有窗口都会按顺序崩溃,即使我已经删除了子类化并恢复了原始 WndProc。

我已验证每当我的应用程序关闭时都会调用 Unsubclass()

// code extracts
HINSTANCE hInst;
HHOOK hHook;

#pragma data_seg(".shared")
HWND hWndSubclass = 0;
FARPROC lpfnOldWndProc = NULL;
#pragma data_seg()
#pragma comment(linker, "/section:.shared,rws")

void Unsubclass()
{
    // if the window still exists
    if (hWndSubclass != 0 && IsWindow(hWndSubclass))
    {
        SetWindowLongPtr(hWndSubclass, GWLP_WNDPROC, (LPARAM)lpfnOldWndProc);
        hWndSubclass = 0;
    }
}

static LRESULT CALLBACK SubClassFunc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_MOVING)
    {
        // this is just test code so I can see it works (it does)
        RECT* r = (RECT*)lParam;
        r->right = r->left + 500;
        r->bottom = r->top + 500;
        return TRUE;
    }
    else if (message == WM_DESTROY)
    {
        Unsubclass();
    }
    return CallWindowProc((WNDPROC)lpfnOldWndProc, hWndSubclass, message, wParam, lParam);
}

void SubclassWindow(HWND hWnd)
{
    // remove the subclassing for the old window
    Unsubclass();
    // subclass the new window
    lpfnOldWndProc = (FARPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LPARAM)SubClassFunc);
    hWndSubclass = hWnd;
}

static LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode == HCBT_ACTIVATE)
    {
        SubclassWindow((HWND)wParam);
    }
    return 0;
}

// ... code that initializes the CBT proc
__declspec(dllexport) BOOL Setup()
{
    hHook = SetWindowsHookEx(WH_CBT, CBTProc, hInst, 0);
}

__declspec(dllexport) BOOL Teardown()
{
    UnhookWindowsHookEx(hHook);
    Unsubclass();
}

BOOL APIENTRY DllMain( HINSTANCE hInstance, 
                       DWORD  Reason, 
                       LPVOID Reserved
                     )
{
    switch(Reason)
    { 
        case DLL_PROCESS_ATTACH:
            hInst = hInstance;
            return TRUE;
        case DLL_PROCESS_DETACH:
            Unsubclass();
            return TRUE;
    }
    return TRUE;
}

【问题讨论】:

  • hWndServer 无关紧要(它是设置 CBT 挂钩的进程)。我已经删除了对它的引用。
  • 是否有可能在最后的 Unsubclass 之后钩子仍然处于活动状态并且在关闭期间仍然子类窗口?
  • @DR:这也是我的恐惧。但据我所知,它应该是即时的:msdn.microsoft.com/en-us/library/ms997565.aspx

标签: winapi visual-c++ dll


【解决方案1】:

您的问题取决于几个方面:

  • UnHookWindowsHook 不会卸载注入的 dll,它所做的只是删除 hook proc。如果需要卸载 dll,则由他们来发明某种卸载机制。
  • 当从拥有窗口的进程以外的进程调用时,SetWindowLongPtr 通常会失败。

这样做的最终结果是,很难安全地移除 windows 挂钩。首先,您的 OldWindowProc 指针不应存储在共享数据区域中。接下来,为了删除子类,您需要能够强制(当前)子类化进程来执行取消子类化。

您可以做的是,首先,注册一个新的唯一消息 id,并使用 RegisterWindowMessage 将其放置在您的共享区域中。 WM_REMOVE_HOOK

UINT idWM_REMOVE_HOOK = RegisterWindowMessage("WM_REMOVE_HOOK");

现在,每当您需要移除挂钩时,

SendMessage(hWndSubClass,idWM_REMOVE_HOOK,0,0);

在你的子类过程中:

if(uMsg == WM_DESTROY || uMsg == idWM_REMOVE_HOOK)
{
  Unsubclass(hwnd);
}

删除对 DLL_PROCESS_DETATCH 中 UnSubClass 的调用。这是一个危险的竞争条件,会导致您的 dll 在某个随机进程中被卸载,从而破坏另一个进程中潜在有效挂钩的挂钩数据。

【讨论】:

    【解决方案2】:

    lpfnOldWndProc 和 hWndSubclass 是全局指针。似乎每个进程只有一个。如果一个进程创建了多个窗口怎么办?

    那么你将只取消最后一个的子类。

    编辑:另外,你为什么要在 Process DETACH 中拆除?

    【讨论】:

    • 没关系,如果我理解正确的话。一次只能有一个窗口焦点。我只需要访问焦点窗口。每当焦点切换到新窗口时,旧的子类就会被移除,而现在获得焦点的窗口会获得焦点。
    • @EFraim:拥有多个 UI 线程是否重要?我一次只子类化一个窗口instance
    • @Vegard:当然可以。如果线程之间存在竞争条件怎么办? (即,如果在属于不同线程的窗口上做某事)这不太可能但仍然可能。
    • @EFraim:可能只是我,但我看不到这里出现竞争条件的可能性(但我的 Win32 经验有限)。您能否概述一种可能发生这种情况的情况?
    【解决方案3】:

    您正在 DLL 中创建全局系统范围的挂钩。您需要将 HHOOK 句柄和子类化信息存储在共享内存块中,以便所有运行进程中的 DLL 的所有实例都可以访问它们。您的变量在代码中被声明为全局变量,但 DLL 的每个单独实例都将拥有它们自己的本地副本,因此除了 1 个 DLL 实例(调用 Setup() 的那个)之外,它们不会在所有实例中初始化。它们需要在整个系统中全局共享。

    您也不应该在 DLL_PROCESS_DETACH 中调用 TearDown()。当各自的进程终止时,DLL 的每个实例都会调用 TearDown(),但只有实际调用 Setup() 的单个实例才应该调用 Teardown()。

    【讨论】:

    • +1:这听起来像是钱。在设置系统范围的钩子之前,我一直被“不在共享内存中的全局变量”问题所困扰。
    • 1. HHOOK 句柄不需要在共享内存中。该句柄仅在调用 Setup() 和 Teardown() 的过程中是必需的。 2. 你是对的,Teardown() 不应该在进程分离时调用。但是,它应该是 Unsubclass()。我现在会解决这个问题。 3. 将子类化信息放在共享内存中没有任何改变;当我关闭我的应用程序时,我在运行时触摸的每个窗口的进程仍然崩溃(我已经尝试过这个无济于事,但忘记将它包含在问题中)。
    • @Vegard:HHOOK 必须共享,因为每当在本地应用程序进程中调用挂钩函数时,DLL 的每个实例都必须将相同的 HHOOK 传递给 CallNextHookEx()。在调用 SetWindowsHookEx() 的同一个 DLL 实例中仅调用某些挂钩类型。其他钩子类型不是,因此 HHOOK 必须共享。
    • @Vegard:另外,请记住,释放全局挂钩不会卸载活动的 DLL 实例!全局挂钩的 SetWindowsHookEx() 会在每个正在运行的进程中隐式调用 LoadLibrary(),但是 UnhookWindowsHookEx() 无法访问其他进程,因此无法强制它们调用 FreeLibrary()。微软的文档甚至说了这么多。所以应该共享子类化信息,因为调用 UnhookWindowsHookEx() 的 DLL 实例需要知道是否有一个活动的子类,以便它可以告诉拥有 DLL 在最终释放钩子之前取消子类化。
    • @Remy :hhook 似乎是 Windows 的遗产。根据 MSDN CallNextHookEx 文档,使用 0 作为 hHook 在 NT 平台上调用下一个挂钩是安全的。
    【解决方案4】:

    如果调试器会通过添加断点使进程成功,那么很可能这是一个时序问题。 可能发生的情况是,您的主应用程序正在关闭自身并在子类窗口获得再次删除子类所需的消息之前释放资源。您可能希望给他们一些处理周期来处理他们自己在 unhook 和 unsubclassing 之间的消息。 (在 Delphi 中,您可以通过调用 Application.ProcessMessages 来做到这一点,但在您的 C++ 版本中?不知道答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-10-28
      • 1970-01-01
      • 1970-01-01
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多