本地键盘挂钩终止目标进程

Posted

技术标签:

【中文标题】本地键盘挂钩终止目标进程【英文标题】:Local keyboard hook terminates the target process 【发布时间】:2021-03-05 02:45:40 【问题描述】:

我正在尝试使用托管 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 位的 当HookProc 被触发时_hookCallback_ 不为空(虽然我不确定它是否指向一个有效的内存地址) KeyboardProc::handler 不应被垃圾回收,因为 KeyboardProc 实例的存在时间与 C# 应用程序一样长 在 DLL 的 Install 函数中使用 _hookCallback 函数指针可以完美运行,但在 HookProc 函数中使用时会终止进程。 没有任何异常,进程突然终止

还尝试过什么:

使HookCallback 成为UnmanagedFunctionPointer,以及使用Marshal.GetFunctionPointerForDelegate 并通过使用GCHandle.Alloc()GC.KeepAlive() 告诉垃圾收集器不要收集handler 属性:

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);

【问题讨论】:

在另一个函数中添加GC.KeepAlive(handler);,可能在Uninstall @Charlieface 你能解释一下吗? HookProc 会立即终止进程,不仅是在卸载挂钩之后。 见***.com/questions/52952678/… 虽然知道确切的例外情况会有所帮助 这就是问题所在。我不知道会发生什么样的异常。进程突然终止。 如果您需要函数指针在Install 的生命周期之外 保持活动状态,那么您需要在Install 之外的委托上使用KeepAlive。如果您查看 Windows 事件查看器 -> 应用程序事件,您应该在那里找到异常。我不明白为什么这根本需要另一个 C++ dll,你可以从 C# 完成整个事情。 【参考方案1】:

C# 代码调用 DLL 的 Install() 函数,并翻转了第二个和第三个参数。

改变这一行:

instance = Install(WH_KEYBOARD, handler, process.MainWindowHandle);

到这里:

instance = Install(WH_KEYBOARD, process.MainWindowHandle, handler);

更新:另外,您没有显示 DLL 的 Install() 函数的 C# 代码声明。但是在 C++ 代码中,Install() 函数没有指定调用约定,所以它(可能,取决于编译器配置)默认为 __cdeclHookProc() 使用 __stdcall 调用约定)。因此,请确保 C# 代码在导入 Install() 函数时指定了正确的调用约定。 C# delegates 默认使用__stdcall

【讨论】:

这实际上是一个错字,我的错。我编辑了帖子以包含 DLL 函数导入。当从 C++ 调用 C# 委托时,问题存在于 HookProc。 @Henri 在Install()HookProc() 中对_hookCallback() 的调用没有任何区别,所以发生了其他事情。 是的,但是会发生什么? @Charlieface 谈到了垃圾收集,但我认为这也不是问题所在,因为 KeyboardHook 实例的存在时间与 C# 应用程序一样长。 @Henri 如果您在将handler 传递给Install() 时使用Marshal.GetFunctionPointerForDelegate(),您是否有同样的问题?有关详细信息,请参阅Delegates As Callbacks Part 2。它还涵盖了垃圾收集问题。 通过将挂钩安装到虚拟 WinForms C# 应用程序中,我可以在 Windows 事件查看器中找到该虚拟应用程序中 UnsafeNativeMethods.PeekMessage(MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32, Int32) 处的 .NET 运行时错误,如果有帮助的话。【参考方案2】:

进程因访问无效的内存地址而终止。

每个 Windows 进程都有不同的virtual memory 区域。换句话说,进程A 中的0x1234 内存地址与进程B 中的0x1234 内存地址不同,因为0x1234 是绑定到它的虚拟内存地址。相应的流程。

为了实现 C++ DLL 和 C# 应用程序(任何不同的进程)之间的通信,需要inter-process communication (IPC)。

对于那些对这个特定案例感兴趣的人,我最终创建了一个 invisible dummy window,作为通过来自 DLL 的 SendMessage 调用接收消息的中心点。

【讨论】:

以上是关于本地键盘挂钩终止目标进程的主要内容,如果未能解决你的问题,请参考以下文章

更改键盘挂钩回调中的键盘布局

什么会导致 Windows 取消挂钩低级别(全局)键盘挂钩?

全局键盘挂钩

覆盖应用程序的低级键盘挂钩问题

C++ 键盘钩子 CTRL 键卡住

Windows 全局键盘挂钩 - Delphi