winforms SetWindowsHookEx在C# .net核心中不起作用?

vohkndzv  于 2023-01-14  发布在  Windows
关注(0)|答案(3)|浏览(149)

我正在使用下面的代码尝试获得操作系统范围的键盘输入,但没有成功:

using System;

using System.Diagnostics;
using System.Runtime.InteropServices;

class InterceptKeys

{
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x0100;
    private static LowLevelKeyboardProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;

    public static void Main()

    {
        _hookID = SetHook(_proc);

        while(true)
            continue;
    }

    private static IntPtr SetHook(LowLevelKeyboardProc proc)
    {
        using (Process curProcess = Process.GetCurrentProcess())

        using (ProcessModule curModule = curProcess.MainModule)
        {
            return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
                GetModuleHandle(curModule.ModuleName), 0);
        }
    }
    private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
        {
            int vkCode = Marshal.ReadInt32(lParam);
            Console.WriteLine(vkCode);
        }

        return CallNextHookEx(_hookID, nCode, wParam, lParam);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]

    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]

    private static extern IntPtr GetModuleHandle(string lpModuleName);

}

HookCallback没有被调用。我怀疑它只是试图监听一个不存在的表单,而不是在系统范围内运行。

rryofs0p

rryofs0p1#

低级Windows钩子内部使用Windows消息传递。调用SetWindowsHookEx的线程必须在最后有消息循环,这允许调用HookCallback函数。在C++中,消息循环如下所示:

MSG msg;
BOOL result;

for (;;)
{
    result = GetMessage(&msg, nullptr, 0, 0);

    if (result <= 0)
    {
        break;
    }

    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

找到GetMessageTranslateMessageDispatchMessageMSG的所有必需的PInvoke定义,将此代码转换为C#并将其放置在while(true)中,而不是无限循环中。您可以在PInvoke.Net中找到所有这些内容,另请参阅Microsoft论坛讨论:
未调用控制台键盘挂钩
https://social.msdn.microsoft.com/Forums/vstudio/en-US/ed5be22c-cef8-4615-a625-d05caf113afc/console-keyboard-hook-not-getting-called?forum=csharpgeneral

xn1cxnb4

xn1cxnb42#

我显然很晚了,但只是希望我能帮助(如果操作员还没有得到他/她需要的帮助),所以我张贴我的答案。
MSDN文档中指出,当您要设置系统范围的钩子时,必须给予hMod参数提供
DLL的句柄,该DLL包含lpfn参数所指向的挂接过程
以及
如果dwThreadId参数为零或指定由其他进程创建的线程的标识符,则lpfn参数必须指向DLL中的挂接过程
但是,看看这个:
设置窗口挂接Ex(2,kbdHookProc,获取模块句柄(“用户32”),0)
kbdHookProc是我的C# winforms应用程序中的一个函数,但是我在hMod参数中给出的值是通过GetModuleHandle加载user32.dll获得的hinstance。我正在使用键盘钩子(WH_KEYBOARD)来监视capslock、numlock和scroll lock键的锁定。不要问我为什么这样做,它是否有效,或者为什么有效,因为我不知道,但是,是的,它有效!

vwhgwdsa

vwhgwdsa3#

完整的答案;
正如Alex所说,您需要一个消息循环来处理Windows消息并调用钩子,

public class MessageLoop
{
    [DllImport("user32.dll")]
    private static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin,
        uint wMsgFilterMax);
    
    [DllImport("user32.dll")]
    private static extern bool TranslateMessage([In] ref MSG lpMsg);
    
    [DllImport("user32.dll")]
    private static extern IntPtr DispatchMessage([In] ref MSG lpmsg);
    
    [StructLayout(LayoutKind.Sequential)]
    public struct MSG
    {
        IntPtr hwnd;
        uint message;
        UIntPtr wParam;
        IntPtr lParam;
        int time;
        POINT pt;
        int lPrivate;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int X;
        public int Y;

        public POINT(int x, int y)
        {
            X = x;
            Y = y;
        }

        public static implicit operator System.Drawing.Point(POINT p)
        {
            return new System.Drawing.Point(p.X, p.Y);
        }

        public static implicit operator POINT(System.Drawing.Point p)
        {
            return new POINT(p.X, p.Y);
        }

        public override string ToString()
        {
            return $"X: {X}, Y: {Y}";
        }
    }
    
    private Action InitialAction { get; }
    private Thread? Thread { get; set; }
    
    public bool IsRunning { get; private set; }

    public MessageLoop(Action initialAction)
    {
        InitialAction = initialAction;
    }

    public void Start()
    {
        IsRunning = true;
        
        Thread = new Thread(() =>
        {
            InitialAction.Invoke();
            
            while (IsRunning)
            {
                var result = GetMessage(out var message, IntPtr.Zero, 0, 0);

                if (result <= 0)
                {
                    Stop();
                    
                    continue;
                }

                TranslateMessage(ref message);
                DispatchMessage(ref message);
            }
        });
        
        Thread.Start();
    }

    public void Stop()
    {
        IsRunning = false;
    }
}

我在这里使用一个单独的线程以避免阻塞主线程。
问题中的InterceptKeys类不需要修改;

class InterceptKeys
{
    // ...

    public static void Main()
    {
        var loop = new MessageLoop(() => {
            _hookID = SetHook(_proc);
        });
        

        while (Console.ReadKey(true) != ConsoleKey.X) // For exemplary purposes
        {
            continue;
        }

        loop.Stop();
    }

    // ...
}

相关问题