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