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

时间:2009-08-28 12:02:24

标签: winapi visual-c++ dll

我正在尝试子类化当前具有焦点的窗口。我通过使用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;
}

4 个答案:

答案 0 :(得分:3)

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

  • UnHookWindowsHook不会卸载注入的dll,它所做的就是删除钩子proc。如果dll需要卸载它们,就可以发明某种卸载机制。
  • 从拥有该窗口的进程以外的进程调用时,SetWindowLongPtr通常会失败。

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

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

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_PROCESS_DETATCH中对UnSubClass的调用。它是一个危险的竞争条件,它会导致你的dll在一些随机进程中被卸载,以便在另一个进程中删除潜在有效钩子的钩子数据。

答案 1 :(得分:0)

lpfnOldWndProc和hWndSubclass是全局指针。好像你每个进程只有一个。如果进程创建多个窗口会怎样?

然后你只会取消最后一个。

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

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

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