Winapi 为什么这个窗口子类化代码崩溃?

Winapi 为什么这个窗口子类化代码崩溃?,winapi,visual-c++,dll,Winapi,Visual C++,Dll,我正在尝试对当前具有焦点的窗口进行子类化。我通过使用CBT钩子监视HCBT\u ACTIVATE事件,并设置和取消设置聚焦窗口和先前聚焦窗口的WndProc 问题是,只有在代码中的某个地方设置了断点时,它才起作用 如果没有断点,那么一旦我的应用程序退出,我已经子类化的所有窗口都会依次崩溃,即使我已经删除了子类化并恢复了原始WndProc 我已经验证了每当我的应用程序关闭时都会调用Unsubclass() // code extracts HINSTANCE hInst; HHOOK hHook;

我正在尝试对当前具有焦点的窗口进行子类化。我通过使用CBT钩子监视
HCBT\u 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;
}

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

然后,您将只取消最后一个类


编辑:还有,为什么要在进程分离中拆除?

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


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

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

  • UnHookWindowsHook不会卸载注入的DLL,它所做的只是删除hook进程。如果DLL需要卸载,则取决于他们发明某种卸载机制
  • SetWindowLongPtr通常在从拥有该窗口的进程以外的进程调用时失败
这样做的净结果是,很难安全地移除windows挂钩。首先,您的旧WindowProc指针不应存储在共享数据区域中。接下来,为了删除子类,您需要能够协同(当前)子类化进程来执行取消子类化

您可以做的是,首先,注册一个新的唯一消息id,并使用RegisterWindowMessage将其放置在共享区域中<代码>WM_移除挂钩

UINT idWM_REMOVE_HOOK = RegisterWindowMessage("WM_REMOVE_HOOK");
现在,每当你需要取下钩子

SendMessage(hWndSubClass,idWM_REMOVE_HOOK,0,0);
在子类proc中:

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

删除对DLL_进程_DETATCH中UnSubClass的调用。这是一种危险的竞争条件,将导致在某个随机进程中卸载dll,从而在另一个进程中丢弃可能有效的钩子的钩子数据。

如果调试器通过添加断点使进程成功,则很可能是时间问题。
可能发生的情况是,您的主应用程序正在关闭自身并释放资源,而子类窗口刚好在获得再次删除子类所需的消息之前。您可能希望给他们一些处理周期,以便在取消钩住和取消分类之间处理他们自己的消息。在Delphi中,你可以通过调用Appultual.PosialMeX来实现这一点,但是C++的版本不知道。d现在聚焦的窗口接收焦点。@EFraim:有多个UI线程是否重要?我一次只对一个窗口实例进行子类化。@Vegard:当然可以。如果线程之间存在争用条件怎么办?(即,如果在属于不同线程的窗口上执行某项操作)这不太可能,但仍然是可能的。@EFraim:可能只是我,但我看不出这里存在竞争条件的可能性(但我的Win32体验有限)。您能概括一下可能发生这种情况的一种场景吗?hWndServer与此无关(这是设置CBT挂钩的过程)。我已经删除了对它的引用。是否有可能在最后一次取消类之后,钩子仍然处于活动状态,并且在关闭期间仍然是windows的子类?@DR:这也是我的担心。但据我所知,应该是立即的:+1:听起来像是钱。我被“不在共享内存中的全局对象”咬了一口设置系统范围挂钩时出现问题。1.HHOOK句柄不需要位于共享内存中。该句柄仅在调用Setup()和Teardown()的进程中是必需的。2.您是对的,进程分离时不应调用Teardown()。但是,它应取消类()3.将子类化信息放在共享内存中不会改变任何东西;当我关闭应用程序时,我触摸的每个窗口的进程仍然会崩溃(我已经尝试过了,但忘记将其包含在问题中)@Vegard:HHOOK必须共享,因为每当在本地应用程序进程中调用hook函数时,DLL的每个实例都必须将相同的HHOOK传递给CallNextHookEx()。在调用SetWindowsHookEx()的同一DLL实例中只调用某些钩子类型。其他钩子类型不是,因此HHOOK必须共享。@Vegard:另外,请记住,释放全局钩子不会卸载活动的DLL实例!全局钩子的SetWindowsHookEx()会在每个正在运行的进程中隐式调用LoadLibrary(),但会取消钩子Windows HookEx()无法访问其他进程,因此无法强制它们调用FreeLibrary()。Microsoft的文档甚至会这么说。因此子类化信息应该共享,因为调用UnhookWindowsHookEx()的DLL实例需要知道是否存在活动的子类