本地键盘钩子终止目标进程
Local keyboard hook terminates the target process
我正在尝试使用托管 C# 代码中的 C++ DLL 将 LOCAL 键盘挂钩安装到进程中,如下所示:
public class KeyboardHook
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("DLL.dll", CallingConvention = CallingConvention.Cdecl)]
protected static extern IntPtr Install(int idHook, IntPtr windowHandle, HookCallback callback);
private IntPtr instance;
private HookCallback handler;
public KeyboardHook()
{
instance = IntPtr.Zero;
handler = Callback;
}
public void Install(Process process)
{
instance = Install(WH_KEYBOARD, process.MainWindowHandle, handler);
}
public void Uninstall()
{
UnhookWindowsHookEx(instance);
}
private IntPtr Callback(int nCode, IntPtr wParam, IntPtr lParam)
{
// TODO Use hook data here
return CallNextHookEx(instance, nCode, wParam, lParam);
}
}
C++ DLL 代码应该足以将挂钩数据分派给 C# 的 Callback
函数,如下所示:
// dll.h
#pragma data_seg(".foo")
HOOKPROC _hookCallback = NULL;
#pragma comment(linker, "/SECTION:.foo,RWS")
#pragma data_seg()
static HINSTANCE _moduleHandle = NULL;
extern "C" __declspec(dllexport)
HHOOK Install(int idHook, HWND window, HOOKPROC hookCallback);
extern "C" __declspec(dllexport)
LRESULT CALLBACK HookProc(int code, WPARAM wparam, LPARAM lparam);
// dll.cpp
HHOOK Install(int idHook, HWND window, HOOKPROC hookCallback)
{
auto processId = 0ul;
auto threadId = GetWindowThreadProcessId(window, &processId);
_hookCallback = hookCallback;
_hookCallback(-1, NULL, NULL); // Test callback (works)
return SetWindowsHookExA(idHook, HookProc, _moduleHandle, threadId);
}
LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam)
{
// The following line terminates the target process
return _hookCallback(code, wParam, lParam);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
_moduleHandle = hModule;
break;
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
当 DLL KeyboardProc 函数被触发时,本地挂钩已成功安装,但是,从 C++ DLL 调用 C# 委托会终止应用程序。为什么?
备注:
- DLL 和应用程序都是 32 位的
_hookCallback_
在触发HookProc
时不为null(虽然我不确定它是否指向有效的内存地址)
KeyboardProc::handler
不应进行垃圾回收,因为 KeyboardProc 实例的寿命与 C# 应用程序的寿命一样长
- 在 DLL 的
Install
函数中使用 _hookCallback
函数指针可以完美运行,但在 HookProc
函数内部使用时会终止进程。
- 没有任何异常,进程突然终止
还尝试了什么:
使 HookCallback
成为 UnmanagedFunctionPointer
,并使用 Marshal.GetFunctionPointerForDelegate
并通过使用 [= 告诉垃圾收集器不要收集 handler
属性 25=] 和 GC.KeepAlive()
:
public class KeyboardHook
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("DLL32.dll", CallingConvention = CallingConvention.Cdecl)]
protected static extern IntPtr Install(int idHook, IntPtr windowHandle, IntPtr delegatePointer);
// ...
protected readonly GCHandle garbageCollectorHandle;
public KeyboardHook()
{
instance = IntPtr.Zero;
handler = new HookCallback(Callback);
garbageCollectorHandle = GCHandle.Alloc(handler); // Or GC.KeepAlive(handler)
}
~KeyboardHook()
{
garbageCollectorHandle.Free();
}
public void Install(Process process)
{
IntPtr delegatePointer = Marshal.GetFunctionPointerForDelegate(handler);
instance = Install(WH_KEYBOARD, process.MainWindowHandle, delegatePointer);
}
// ...
}
直接使用 handler
进入 SetWindowsHookExA
(C++):
HHOOK Install(int idHook, HWND window, HOOKPROC hookCallback)
{
auto processId = 0ul;
auto threadId = GetWindowThreadProcessId(window, &processId);
_hookCallback = hookCallback;
return SetWindowsHookExA(idHook, hookCallback, _moduleHandle, threadId);
}
C# 代码正在调用 DLL 的 Install()
函数,第二个和第三个参数翻转。
更改此行:
instance = Install(WH_KEYBOARD, handler, process.MainWindowHandle);
为此:
instance = Install(WH_KEYBOARD, process.MainWindowHandle, handler);
UPDATE:此外,您没有显示 DLL 的 Install()
函数的 C# 代码声明。但是在 C++ 代码中,Install()
函数没有指定调用约定,因此它(可能取决于编译器配置)默认为 __cdecl
(HookProc()
使用 __stdcall
调用约定)。因此,请确保 C# 代码在导入 Install()
函数时指定了正确的调用约定。 C# delegate
s 默认使用 __stdcall
。
进程因访问无效内存地址而终止。
每个 Windows 进程都有不同的 virtual memory 区域。换句话说,进程 A
中的 0x1234
内存地址与进程 B
中的 0x1234
内存地址不指向相同的 value/function,因为 0x1234
是绑定到其对应进程的虚拟内存地址。
为了实现 C++ DLL 和 C# 应用程序(任何不同的进程)之间的通信,需要 inter-process communication (IPC)。
对于那些对这个特定案例感兴趣的人,我最终创建了一个 作为中心点,通过来自 DLL 的 SendMessage
调用接收消息。
我正在尝试使用托管 C# 代码中的 C++ DLL 将 LOCAL 键盘挂钩安装到进程中,如下所示:
public class KeyboardHook
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("DLL.dll", CallingConvention = CallingConvention.Cdecl)]
protected static extern IntPtr Install(int idHook, IntPtr windowHandle, HookCallback callback);
private IntPtr instance;
private HookCallback handler;
public KeyboardHook()
{
instance = IntPtr.Zero;
handler = Callback;
}
public void Install(Process process)
{
instance = Install(WH_KEYBOARD, process.MainWindowHandle, handler);
}
public void Uninstall()
{
UnhookWindowsHookEx(instance);
}
private IntPtr Callback(int nCode, IntPtr wParam, IntPtr lParam)
{
// TODO Use hook data here
return CallNextHookEx(instance, nCode, wParam, lParam);
}
}
C++ DLL 代码应该足以将挂钩数据分派给 C# 的 Callback
函数,如下所示:
// dll.h
#pragma data_seg(".foo")
HOOKPROC _hookCallback = NULL;
#pragma comment(linker, "/SECTION:.foo,RWS")
#pragma data_seg()
static HINSTANCE _moduleHandle = NULL;
extern "C" __declspec(dllexport)
HHOOK Install(int idHook, HWND window, HOOKPROC hookCallback);
extern "C" __declspec(dllexport)
LRESULT CALLBACK HookProc(int code, WPARAM wparam, LPARAM lparam);
// dll.cpp
HHOOK Install(int idHook, HWND window, HOOKPROC hookCallback)
{
auto processId = 0ul;
auto threadId = GetWindowThreadProcessId(window, &processId);
_hookCallback = hookCallback;
_hookCallback(-1, NULL, NULL); // Test callback (works)
return SetWindowsHookExA(idHook, HookProc, _moduleHandle, threadId);
}
LRESULT CALLBACK HookProc(int code, WPARAM wParam, LPARAM lParam)
{
// The following line terminates the target process
return _hookCallback(code, wParam, lParam);
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
_moduleHandle = hModule;
break;
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
当 DLL KeyboardProc 函数被触发时,本地挂钩已成功安装,但是,从 C++ DLL 调用 C# 委托会终止应用程序。为什么?
备注:
- DLL 和应用程序都是 32 位的
_hookCallback_
在触发HookProc
时不为null(虽然我不确定它是否指向有效的内存地址)KeyboardProc::handler
不应进行垃圾回收,因为 KeyboardProc 实例的寿命与 C# 应用程序的寿命一样长- 在 DLL 的
Install
函数中使用_hookCallback
函数指针可以完美运行,但在HookProc
函数内部使用时会终止进程。 - 没有任何异常,进程突然终止
还尝试了什么:
使 HookCallback
成为 UnmanagedFunctionPointer
,并使用 Marshal.GetFunctionPointerForDelegate
并通过使用 [= 告诉垃圾收集器不要收集 handler
属性 25=] 和 GC.KeepAlive()
:
public class KeyboardHook
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("DLL32.dll", CallingConvention = CallingConvention.Cdecl)]
protected static extern IntPtr Install(int idHook, IntPtr windowHandle, IntPtr delegatePointer);
// ...
protected readonly GCHandle garbageCollectorHandle;
public KeyboardHook()
{
instance = IntPtr.Zero;
handler = new HookCallback(Callback);
garbageCollectorHandle = GCHandle.Alloc(handler); // Or GC.KeepAlive(handler)
}
~KeyboardHook()
{
garbageCollectorHandle.Free();
}
public void Install(Process process)
{
IntPtr delegatePointer = Marshal.GetFunctionPointerForDelegate(handler);
instance = Install(WH_KEYBOARD, process.MainWindowHandle, delegatePointer);
}
// ...
}
直接使用 handler
进入 SetWindowsHookExA
(C++):
HHOOK Install(int idHook, HWND window, HOOKPROC hookCallback)
{
auto processId = 0ul;
auto threadId = GetWindowThreadProcessId(window, &processId);
_hookCallback = hookCallback;
return SetWindowsHookExA(idHook, hookCallback, _moduleHandle, threadId);
}
C# 代码正在调用 DLL 的 Install()
函数,第二个和第三个参数翻转。
更改此行:
instance = Install(WH_KEYBOARD, handler, process.MainWindowHandle);
为此:
instance = Install(WH_KEYBOARD, process.MainWindowHandle, handler);
UPDATE:此外,您没有显示 DLL 的 Install()
函数的 C# 代码声明。但是在 C++ 代码中,Install()
函数没有指定调用约定,因此它(可能取决于编译器配置)默认为 __cdecl
(HookProc()
使用 __stdcall
调用约定)。因此,请确保 C# 代码在导入 Install()
函数时指定了正确的调用约定。 C# delegate
s 默认使用 __stdcall
。
进程因访问无效内存地址而终止。
每个 Windows 进程都有不同的 virtual memory 区域。换句话说,进程 A
中的 0x1234
内存地址与进程 B
中的 0x1234
内存地址不指向相同的 value/function,因为 0x1234
是绑定到其对应进程的虚拟内存地址。
为了实现 C++ DLL 和 C# 应用程序(任何不同的进程)之间的通信,需要 inter-process communication (IPC)。
对于那些对这个特定案例感兴趣的人,我最终创建了一个 SendMessage
调用接收消息。