这是我当前的设置:我有一个C ++ DLL,可将其功能之一全局地钩接到计算机上运行的每个进程。使用DLLMain
winapi函数在SetWindowsHookEx
中完成了钩接,我正在钩住WH_CBT
和WH_SHELL
事件。我还具有一个C#应用程序,该应用程序通过p / invoke(LoadLibrary()
)加载DLL,从而触发了DLLMain
中钩子的安装。 DLL中的处理程序通过命名管道将事件信息发送到C#应用。
基于我在microsoft documentary上所读的内容,这些事件将在目标进程的线程上处理,并且必须由独立的C ++ DLL安装(不同于WH_MOUSE_LL和WH_KEYBOARD_LL可以由任何人安装应用程序,甚至straight from a C# app using p/invoke)。
到目前为止,一切正常。托管应用正在接收数据。当我关闭应用程序时,会出现问题,因为处理程序仍处于挂接状态,因此DLL文件正在使用中,无法删除。
由于处理程序未在我的应用程序中运行,而是被注入到我的计算机上运行的其他进程中,因此C#应用程序不能只是简单地调用UnhookWindowsHookEx
或FreeLibrary
,因为事件处理程序属于其他进程。
问题:
如何从托管应用程序中触发一个解除挂钩例程,以确保该DLL不再被任何进程使用?
这是我尝试过的事情:
我想提出的唯一解决方案是创建一个退出事件(使用CreateEvent
),并且每次处理程序收到WH_CBT
或WH_SHELL
消息时,它都会检查是否设置了退出事件,在这种情况下,它将退出其所属的进程,并在处理消息之前返回。
这种方法的问题在于,在我关闭应用程序并卸载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呼叫。
答案 0 :(得分:3)
“ 挂钩是在DLLMain中完成的”-这是完全错误的处理方法。
每次将DLL加载到新进程中时,它将安装一组新的Shell / CBT钩子,您不希望/不需要这样做。您只需要一套。
正确解决方案是让您的DLL导出其InstallHooks()
和RemoveHooks()
函数,然后在将DLL加载到自身之后,只有C#应用调用它们。单一的钩子集将根据需要将DLL加载到所有正在运行的进程中,而无需每次调用SetWindowsHookEx()
。
此外,请勿从内部内的钩子回调自身中调用UnhookWindowsHookEx()
。在C#应用程序退出之前,它应该调用RemoveHooks()
,然后可以在调用hTerm
之前发出UnhookWindowsHookEx()
事件的信号。如果Continue()
返回false,仅应退出回调。但是,即使CallNextHookEx()
返回false,也不要跳过调用Continue()
,因为其他应用程序可能还会安装其他挂钩,并且您也不想破坏它们。
请尝试以下类似操作:
#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调用。