如何从另一个进程中解开全局钩子?
How do I unhook a global hook from another process?
这是我当前的设置:我有一个 C++ DLL,它将它的一个函数全局挂接到计算机上的每个进程 运行。挂钩是在 DLLMain
中使用 SetWindowsHookEx
winapi 函数完成的,我正在挂钩 WH_CBT
和 WH_SHELL
事件。我还有一个 C# 应用程序,它使用 p/invoke (LoadLibrary()
) 加载 DLL,这会触发从 DLLMain
安装挂钩。 DLL 中的处理程序通过命名管道向 C# 应用程序发送事件信息。
基于我在 microsoft documentary, these events will be handled on the target process's thread, and have to be installed by a standalone C++ DLL (unlike WH_MOUSE_LL and WH_KEYBOARD_LL that can be installed by any application, even straight from a C# app using p/invoke) 上阅读的内容。
到目前为止一切正常;托管应用程序正在按应有的方式接收数据。当我关闭应用程序时出现问题,因为处理程序仍然被挂钩,因此 DLL 文件正在使用中,无法删除。
由于处理程序不在我的应用程序中 运行,而是注入到我计算机上的其他进程 运行,因此 C# 应用程序不能简单地调用 UnhookWindowsHookEx
或FreeLibrary
,因为事件处理器的指针属于其他进程。
问题:
如何从托管应用程序中触发解除挂钩例程,以确保 DLL 不再被任何进程使用?
这是我试过的:
我想出的唯一解决方案是创建一个退出事件(使用 CreateEvent
),每次处理程序收到 WH_CBT
或 WH_SHELL
消息时,它检查是否设置了退出事件,在这种情况下,它会在处理消息之前从它所属的进程和 returns 中脱钩。
这种方法的问题是,在我关闭我的应用程序并卸载 DLL 之后,我必须等到其余进程至少收到一次 WH 事件,这样属于它们的处理程序才能自行解除挂钩。
这是 DLL 的代码:
#include <windows.h>
#include <sstream>
HANDLE hTERM;
HHOOK hCBT;
HHOOK hShell;
void __declspec(dllexport) InstallHooks(HMODULE h);
void __declspec(dllexport) RemoveHooks();
int Continue()
{
return WAIT_TIMEOUT == WaitForSingleObject(hTERM, 0);
}
LRESULT FAR PASCAL _cbtProc(int c, WPARAM w, LPARAM l)
{
if (!Continue()) { RemoveHooks(); return 0; }
// Handling the message ...
return CallNextHookEx(0, c, w, l);
}
LRESULT FAR PASCAL _shellProc(int c, WPARAM w, LPARAM l)
{
if (!Continue()) { RemoveHooks(); return 0; }
// Handling the message ...
return CallNextHookEx(0, c, w, l);
}
void InstallHooks(HMODULE h)
{
hTERM = OpenEvent(EVENT_ALL_ACCESS, 0, __TEXT("{0C3ED513-F38C-4996-8130-F9A3C93D890B}"));
if (!Continue())
return;
hCBT = SetWindowsHookEx(WH_CBT, _cbtProc, h, 0);
hShell = SetWindowsHookEx(WH_SHELL, _shellProc, h, 0);
}
void RemoveHooks()
{
UnhookWindowsHookEx(hCBT);
UnhookWindowsHookEx(hShell);
if (hTERM) CloseHandle(hTERM); hTERM = 0;
}
int FAR PASCAL DllMain(HMODULE h, DWORD r, void* p)
{
switch (r)
{
case DLL_PROCESS_ATTACH: InstallHooks(h); break;
case DLL_PROCESS_DETACH: RemoveHooks(); break;
default: break;
}
return 1;
}
托管 C# 应用程序的源代码没有什么特别之处,因为它唯一做的就是在启动时调用 LoadLibrary
,处理来自管道的消息,最后设置出口需要时与另一个 p/invoke 调用事件。
"挂钩是在 DLLMain" 中完成的 - 这是处理此问题的完全错误的地方。
每次将 DLL 加载到新进程中时,它都会安装一组新的 Shell/CBT 挂钩,您不会 want/need 发生这种情况。你只需要一套。
正确的 解决方案是让您的 DLL 导出它的 InstallHooks()
和 RemoveHooks()
函数,然后只有您的 C# 应用程序在它有之后调用它们将 DLL 加载到自身中。这组挂钩将根据需要处理将 DLL 加载到所有 运行 进程中,而无需您每次都调用 SetWindowsHookEx()
。
此外,请勿从 内部 调用 UnhookWindowsHookEx()
挂钩回调本身。在 C# 应用程序退出之前,它应该调用 RemoveHooks()
,然后它可以在调用 UnhookWindowsHookEx()
之前发出 hTerm
事件信号。如果 Continue()
returns false,回调应该简单地退出,仅此而已。但是不要跳过调用 CallNextHookEx()
即使 Continue()
returns false,因为其他应用程序可能安装了额外的钩子,你不想破坏它们。
尝试更像这样的东西:
#include <windows.h>
HMODULE hModule = NULL;
HANDLE hTERM = NULL;
HHOOK hCBT = NULL;
HHOOK hShell = NULL;
static bool Continue()
{
return (WAIT_TIMEOUT == WaitForSingleObject(hTERM, 0));
}
LRESULT CALLBACK _cbtProc(int code, WPARAM wParam, LPARAM lParam)
{
if (Continue()) {
// Handle the message ...
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
LRESULT CALLBACK _shellProc(int code, WPARAM wParam, LPARAM lParam)
{
if (Continue()) {
// Handle the message ...
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
__declspec(dllexport) BOOL WINAPI InstallHooks()
{
if (!Continue())
return FALSE;
if (!hCBT)
hCBT = SetWindowsHookEx(WH_CBT, _cbtProc, hModule, 0);
if (!hShell)
hShell = SetWindowsHookEx(WH_SHELL, _shellProc, hModule, 0);
return ((hCBT) && (hShell)) ? TRUE : FALSE;
}
__declspec(dllexport) void WINAPI RemoveHooks()
{
if (hTERM)
SetEvent(hTERM);
if (hCBT) {
UnhookWindowsHookEx(hCBT);
hCBT = NULL;
}
if (hShell) {
UnhookWindowsHookEx(hShell);
hShell = NULL;
}
}
BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, void* lpvReserved)
{
hModule = hinstDLL;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
hTERM = CreateEvent(NULL, TRUE, FALSE, TEXT("{0C3ED513-F38C-4996-8130-F9A3C93D890B}"));
if (!hTERM) return FALSE;
break;
case DLL_PROCESS_DETACH:
if (hTERM) {
CloseHandle(hTERM);
hTERM = NULL;
}
break;
}
return TRUE;
}
然后,您的 C# 应用程序可以简单地加载 DLL 并在需要时调用 InstallHooks()
和 RemoveHooks()
。例如,分别在应用程序启动和关闭时使用 PInvoke 调用。
这是我当前的设置:我有一个 C++ DLL,它将它的一个函数全局挂接到计算机上的每个进程 运行。挂钩是在 DLLMain
中使用 SetWindowsHookEx
winapi 函数完成的,我正在挂钩 WH_CBT
和 WH_SHELL
事件。我还有一个 C# 应用程序,它使用 p/invoke (LoadLibrary()
) 加载 DLL,这会触发从 DLLMain
安装挂钩。 DLL 中的处理程序通过命名管道向 C# 应用程序发送事件信息。
基于我在 microsoft documentary, these events will be handled on the target process's thread, and have to be installed by a standalone C++ DLL (unlike WH_MOUSE_LL and WH_KEYBOARD_LL that can be installed by any application, even straight from a C# app using p/invoke) 上阅读的内容。
到目前为止一切正常;托管应用程序正在按应有的方式接收数据。当我关闭应用程序时出现问题,因为处理程序仍然被挂钩,因此 DLL 文件正在使用中,无法删除。
由于处理程序不在我的应用程序中 运行,而是注入到我计算机上的其他进程 运行,因此 C# 应用程序不能简单地调用 UnhookWindowsHookEx
或FreeLibrary
,因为事件处理器的指针属于其他进程。
问题:
如何从托管应用程序中触发解除挂钩例程,以确保 DLL 不再被任何进程使用?
这是我试过的:
我想出的唯一解决方案是创建一个退出事件(使用 CreateEvent
),每次处理程序收到 WH_CBT
或 WH_SHELL
消息时,它检查是否设置了退出事件,在这种情况下,它会在处理消息之前从它所属的进程和 returns 中脱钩。
这种方法的问题是,在我关闭我的应用程序并卸载 DLL 之后,我必须等到其余进程至少收到一次 WH 事件,这样属于它们的处理程序才能自行解除挂钩。
这是 DLL 的代码:
#include <windows.h>
#include <sstream>
HANDLE hTERM;
HHOOK hCBT;
HHOOK hShell;
void __declspec(dllexport) InstallHooks(HMODULE h);
void __declspec(dllexport) RemoveHooks();
int Continue()
{
return WAIT_TIMEOUT == WaitForSingleObject(hTERM, 0);
}
LRESULT FAR PASCAL _cbtProc(int c, WPARAM w, LPARAM l)
{
if (!Continue()) { RemoveHooks(); return 0; }
// Handling the message ...
return CallNextHookEx(0, c, w, l);
}
LRESULT FAR PASCAL _shellProc(int c, WPARAM w, LPARAM l)
{
if (!Continue()) { RemoveHooks(); return 0; }
// Handling the message ...
return CallNextHookEx(0, c, w, l);
}
void InstallHooks(HMODULE h)
{
hTERM = OpenEvent(EVENT_ALL_ACCESS, 0, __TEXT("{0C3ED513-F38C-4996-8130-F9A3C93D890B}"));
if (!Continue())
return;
hCBT = SetWindowsHookEx(WH_CBT, _cbtProc, h, 0);
hShell = SetWindowsHookEx(WH_SHELL, _shellProc, h, 0);
}
void RemoveHooks()
{
UnhookWindowsHookEx(hCBT);
UnhookWindowsHookEx(hShell);
if (hTERM) CloseHandle(hTERM); hTERM = 0;
}
int FAR PASCAL DllMain(HMODULE h, DWORD r, void* p)
{
switch (r)
{
case DLL_PROCESS_ATTACH: InstallHooks(h); break;
case DLL_PROCESS_DETACH: RemoveHooks(); break;
default: break;
}
return 1;
}
托管 C# 应用程序的源代码没有什么特别之处,因为它唯一做的就是在启动时调用 LoadLibrary
,处理来自管道的消息,最后设置出口需要时与另一个 p/invoke 调用事件。
"挂钩是在 DLLMain" 中完成的 - 这是处理此问题的完全错误的地方。
每次将 DLL 加载到新进程中时,它都会安装一组新的 Shell/CBT 挂钩,您不会 want/need 发生这种情况。你只需要一套。
正确的 解决方案是让您的 DLL 导出它的 InstallHooks()
和 RemoveHooks()
函数,然后只有您的 C# 应用程序在它有之后调用它们将 DLL 加载到自身中。这组挂钩将根据需要处理将 DLL 加载到所有 运行 进程中,而无需您每次都调用 SetWindowsHookEx()
。
此外,请勿从 内部 调用 UnhookWindowsHookEx()
挂钩回调本身。在 C# 应用程序退出之前,它应该调用 RemoveHooks()
,然后它可以在调用 UnhookWindowsHookEx()
之前发出 hTerm
事件信号。如果 Continue()
returns false,回调应该简单地退出,仅此而已。但是不要跳过调用 CallNextHookEx()
即使 Continue()
returns false,因为其他应用程序可能安装了额外的钩子,你不想破坏它们。
尝试更像这样的东西:
#include <windows.h>
HMODULE hModule = NULL;
HANDLE hTERM = NULL;
HHOOK hCBT = NULL;
HHOOK hShell = NULL;
static bool Continue()
{
return (WAIT_TIMEOUT == WaitForSingleObject(hTERM, 0));
}
LRESULT CALLBACK _cbtProc(int code, WPARAM wParam, LPARAM lParam)
{
if (Continue()) {
// Handle the message ...
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
LRESULT CALLBACK _shellProc(int code, WPARAM wParam, LPARAM lParam)
{
if (Continue()) {
// Handle the message ...
}
return CallNextHookEx(NULL, code, wParam, lParam);
}
__declspec(dllexport) BOOL WINAPI InstallHooks()
{
if (!Continue())
return FALSE;
if (!hCBT)
hCBT = SetWindowsHookEx(WH_CBT, _cbtProc, hModule, 0);
if (!hShell)
hShell = SetWindowsHookEx(WH_SHELL, _shellProc, hModule, 0);
return ((hCBT) && (hShell)) ? TRUE : FALSE;
}
__declspec(dllexport) void WINAPI RemoveHooks()
{
if (hTERM)
SetEvent(hTERM);
if (hCBT) {
UnhookWindowsHookEx(hCBT);
hCBT = NULL;
}
if (hShell) {
UnhookWindowsHookEx(hShell);
hShell = NULL;
}
}
BOOL WINAPI DllMain(HMODULE hinstDLL, DWORD fdwReason, void* lpvReserved)
{
hModule = hinstDLL;
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
hTERM = CreateEvent(NULL, TRUE, FALSE, TEXT("{0C3ED513-F38C-4996-8130-F9A3C93D890B}"));
if (!hTERM) return FALSE;
break;
case DLL_PROCESS_DETACH:
if (hTERM) {
CloseHandle(hTERM);
hTERM = NULL;
}
break;
}
return TRUE;
}
然后,您的 C# 应用程序可以简单地加载 DLL 并在需要时调用 InstallHooks()
和 RemoveHooks()
。例如,分别在应用程序启动和关闭时使用 PInvoke 调用。