首页 > 编程语言 >C#模拟键盘输入、键状态和监听键盘消息

C#模拟键盘输入、键状态和监听键盘消息

时间:2024-08-18 20:54:31浏览次数:5  
标签:inputs IntPtr C# 键盘输入 监听 键盘 new INPUT public

模拟键盘输入

模拟键盘输入的功能需要依赖Windows函数实现,这个函数是SendInput,它是专门用来模拟键盘、鼠标等设备输入的函数。

另外和键盘输入相关的函数还有SendKeys,它是System.Windows.Forms. SendKeys,只能在WinFrom项目中使用,并且它的所有功能都可以由SendInput来实现。

另一个是keybd_event函数,这个函数依然是有用的,但是目前官方已经推荐使用SendInput替代它了。

SendInput的定义

[DllImport("user32.dll")]
static extern uint SendInput(int nInputs,INPUT[] pInputs,int cbSize);

INPUT对象中保存了输入内容,nInputs和cbSize代表pInputs的长度和INPUT结构的大小,这两个参数能帮助SendInput正确解析INPUT对象。返回值0表示失败,非零表示正确执行。

 

INPUT的定义

[StructLayout(LayoutKind.Sequential)]
struct KEYBDINPUT {
    public ushort wVk;
    public ushort wScan;
    public uint dwFlags;
    public uint time;
    public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
struct HARDWAREINPUT {
    public uint uMsg;
    public ushort wParamL;
    public ushort wParamH;
}
[StructLayout(LayoutKind.Sequential)]
struct MOUSEINPUT {
    public int dx;
    public int dy;
    public uint mouseData;
    public uint dwFlags;
    public uint time;
    public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Explicit)]
struct MOUSEKEYBDHARDWAREINPUT {
    [FieldOffset(0)]
    public HARDWAREINPUT hi;
    [FieldOffset(0)]
    public KEYBDINPUT ki;
    [FieldOffset(0)]
    public MOUSEINPUT mi;
}
[StructLayout(LayoutKind.Sequential)]
struct INPUT {
    public uint type;
    public MOUSEKEYBDHARDWAREINPUT mkhi;
}

INPUT结构中的type表示消息类型,值为1表示键盘消息。mkhi表示具体的消息内容,它可以模拟三类消息,其中键盘消息使用KEYBDINPUT表示,其它消息类型的结构不在这里介绍(虽然用不到MOUSEINPUT等结构,但是它们的定义不能省略,否则SendInput无法正确解析INPUT中的具体内容)。

⩥FieldOffset(0)将三个结构的起始都放在0位置,所以只能使用其中一个内容,因为一个INPUT也只能表示一个消息,这样设计可以节省空间。

KEYBDINPUT结构中的wVK表示虚拟键码 ,dwFlags的第一位bit默认0表示键盘按下事件,1表示键盘释放事件。

虚拟键码是一种能让Windows以与设备无关的方式处理键盘的技术,可以简单理解为:键盘上的每个键用一个数字来表示。

 

模拟A键

INPUT[] inputs = new INPUT[2];
inputs[0]=new INPUT {
    type=1,
    mkhi=new MOUSEKEYBDHARDWAREINPUT {
        ki=new KEYBDINPUT {
            wVk=0x41
        }
    }
};
inputs[1]=new INPUT {
    type=1,
    mkhi=new MOUSEKEYBDHARDWAREINPUT {
        ki=new KEYBDINPUT {
            wVk=0x41,
            dwFlags=2
        }
    }
};
SendInput(inputs.Length,inputs,Marshal.SizeOf(inputs[0]));

A键的虚拟键码是0x41。type=1表示这是键盘消息,dwFlags=2表示键盘释放事件。

这里INPUT数组模拟的就是使用物理键盘A键的过程。inputs[0]模拟A键按下,inputs[1]模拟A键释放。

 

模拟Ctrl+A

INPUT[] inputs = new INPUT[4];
inputs[0]=new INPUT {
    type=1,
    mkhi=new MOUSEKEYBDHARDWAREINPUT {
        ki=new KEYBDINPUT {
            wVk=0x11
        }
    }
};
inputs[1]=new INPUT {
    type=1,
    mkhi=new MOUSEKEYBDHARDWAREINPUT {
        ki=new KEYBDINPUT {
            wVk=0x41
        }
    }
};
inputs[2]=new INPUT {
    type=1,
    mkhi=new MOUSEKEYBDHARDWAREINPUT {
        ki=new KEYBDINPUT {
            wVk=0x41,
            dwFlags=2
        }
    }
};
inputs[3]=new INPUT {
    type=1,
    mkhi=new MOUSEKEYBDHARDWAREINPUT {
        ki=new KEYBDINPUT {
            wVk=0x11,
            dwFlags=2,
        }
    }
};
SendInput(inputs.Length,inputs,Marshal.SizeOf(inputs[0]));

0x11是Ctrl的虚拟键码,这里模拟了按下Ctrl键,按下A键,释放A键,释放Ctrl键的过程,实现了Ctrl+A的组合键效果。

 

SendInput除了能模拟击键消息外还可以在文本输入中模拟字符消息。

KEYBDINPUT结构的wScan表示字符内容,将dwFlags的第二位bit置1表示使用wScan属性而非wVK。

模拟文本输入

string ntext = "你好";
INPUT[] inputs = new INPUT[ntext.Length*2];
for(int i = 0;i<ntext.Length;i++) {
    ushort ch = ntext[i];
    inputs[i*2]=new INPUT {
        type=1,
        mkhi=new MOUSEKEYBDHARDWAREINPUT {
            ki=new KEYBDINPUT {
                wScan=ch,
                dwFlags=4
            }
        }
    };
    inputs[i*2+1]=new INPUT {
        type=1,
        mkhi=new MOUSEKEYBDHARDWAREINPUT {
            ki=new KEYBDINPUT {
                wScan=ch,
                dwFlags=4|2
            }
        }
    };
}
SendInput(inputs.Length,inputs,Marshal.SizeOf(inputs[0]));

 

键状态

有时需要知道键盘按键的当前状态,可以使用GetKeyState函数。

GetKeyState的定义

[DllImport("user32.dll")]
static extern short GetKeyState(int VKey);

参数是键的虚拟码,对于开关键(Caps Look、Num Lock和Scroll Lock),返回值1表示开启状态。对于其它键返回负数表示按下状态。

CapsLock键状态

short iState = GetKeyState(0x14);

 

监听键盘消息

对于WinForm和WPF程序,要监听输入到本程序的键盘消息直接使用窗口的KeyDown和KeyUp事件即可。

对于其它键盘消息(即给本程序以外的键盘消息),需要使用钩子(hook)。

钩子是Windows系统消息处理机制中的一个节点,可以安装钩子来监听系统中的Windows消息。

Windows消息分很多种,对于特定的一类消息需要使用对应的特定类型的钩子,这里只介绍键盘消息的钩子。

钩子的安装需要调用系统SetWindowsHookEx方法。

 

SetWindowsHookEx的定义

[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hmod, int threadID);

idHook等于13表示全局键盘消息钩子,lpfn代表键盘消息处理程序,返回非IntPtr.Zero表示安装成功。

 

安装钩子

delegate int HookProc(int code,IntPtr wParam,IntPtr lParam);
static HookProc KeyboardProc;
static void InstallKeyboardHook() {
    KeyboardProc=KeyboardHookCallback;
    pKeyboardHook=SetWindowsHookEx(13,keyboardProc,IntPtr.Zero,0);
}

KeyboardHookCallback就是自定义的具体处理键盘消息的方法。

 

消息处理

[DllImport("user32.dll")]
static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
static int KeyboardHookCallback(int code,IntPtr wParam,IntPtr lParam) {
    if(code<0)
        return CallNextHookEx(IntPtr.Zero,code,wParam,lParam);
    int vkCode = Marshal.ReadInt32(lParam);
    System.Diagnostics.Debug.Write(vkCode+" ");
    long downup = (long)wParam;
    switch(downup) {
        case 256:
            System.Diagnostics.Debug.WriteLine("down");
            break;
        case 257:
            System.Diagnostics.Debug.WriteLine("up");
            break;
        case 260:
            System.Diagnostics.Debug.WriteLine("sys_down");
            break;
        case 261:
            System.Diagnostics.Debug.WriteLine("sys_up");
            break;
     }
     return CallNextHookEx(IntPtr.Zero,code,wParam,lParam);
}

从lParam中读取键的虚拟码(lParam其实是指向类似前文提到的KEYBDINPUT结构的指针),wParam表示击键事件的类型。CallNextHookEx将消息传递给下一个消息处理节点。

⩥使用前文提到的SendInput方法模拟键盘输入也能被钩子监听到。

⩥应避免在消息处理过程中进行耗时操作。

 

卸载钩子

这里需要使用UnhookWindowsHookEx

UnhookWindowsHookEx的定义

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

传入SetWindowsHookEx的返回值即可,返回true则卸载成功。

 

2024-08-18 20:52:01【出处】:https://www.cnblogs.com/yxllxy/p/18361608

=======================================================================================

标签:inputs,IntPtr,C#,键盘输入,监听,键盘,new,INPUT,public
From: https://www.cnblogs.com/mq0036/p/18366063

相关文章

  • 51单片机之LCD1602调试工具
    一、LCD1602介绍使用LCD1602液晶屏作为调试窗口,提供类似printf函数的功能,可实时观察单片机内部数据的变换情况,便于调试和演示。LCD1602是一种能够同时显示16x02(2行16列)即32个字符的液晶显示屏,也被称为1602字符型液晶。本篇文章提供的LCD1602代码属于模块化的代码,使用者只需......
  • C++ 设计模式——建造者模式
    建造者模式建造者模式组成部分建造者模式使用步骤1.定义产品类2.创建具体产品类3.创建建造者接口4.实现具体建造者5.创建指挥者类6.客户端代码建造者模式UML图建造者模式UML图解析建造者模式的优缺点建造者模式的适用场景完整代码建造者模式建造者模式(B......
  • JSP基于Java的学生综合测评管理系统7pc27程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表系统功能:学生,教师,课程类型,课程信息,权重设置,思想道德,拓展素质,课程成绩,总成绩信息,考勤信息技术要求:开发语言:JSP前端使用:HTML5,CSS,JSP动态网页技术后......
  • typedef在C/C++的用法
    typedef是C和C++中的一个关键字,用于为已有的数据类型创建新的类型名。它的主要用途如下:1.定义别名typedef最基本的功能是为一个现有的类型定义一个别名,使代码更简洁或更具可读性。例如:typedefunsignedlongulong;ulonga,b;这段代码将unsignedlong类型重......
  • extern在头文件中添加是否必要?(C/C++)
    在C和C++编程中,extern关键字通常用于表示函数或变量的声明(而非定义),特别是在跨文件使用时。尽管在函数声明中使用extern不是强制性的,但它有特定的作用,尤其在变量声明方面。让我们深入探讨一下。1.函数声明的基本概念当你在头文件中声明一个函数时,通常只需要提供函......
  • HTTP Error 503. The service is unavailable.
    第一次遇见这个问题,装了IIS重写模块导致的。查了资料才知道,是URLRewrite的版本和2012系统的lIS不兼容导致。最新的URLRewrite的版本是2018年9月20日的7.1.1993.2351版,就是这个版本产生问题,不能用在2012上,在它前面的一个版本是2017年6月7日的7.1.1980.0版,这个......
  • AIGC时代算法工程师的面试秘籍(第二十式2024.8.5-8.18) |【三年面试五年模拟】
    写在前面【三年面试五年模拟】旨在整理&挖掘AI算法工程师在实习/校招/社招时所需的干货知识点与面试方法,力求让读者在获得心仪offer的同时,增强技术基本面。也欢迎大家提出宝贵的优化建议,一起交流学习......
  • cmake 常用命令记录
    cmake常用命令记录命令cmake_minimum_requiredprojectadd_executablesetconfigure_filetarget_**optionmessageadd_libraryadd_subdirectoryadd_definitionsinstallcpackmacrostringfileDEFINEDlistfind_packagectest构建类型扩展gtest命令cmake_minimum_required......
  • LeetCode 556. 下一个更大元素 III(next_permutation())
    题目:556.下一个更大元素III思路:用到next_permutation(),细节看注释。next_permutation、prev_permutationclassSolution{public:intnextGreaterElement(intn){ //转变为string类型,便于调用next_permutation()strings=to_string(n);......
  • 【代码随想录训练营第42期 Day32打卡 - 从零开始动态规划 - LeetCode 509. 斐波那契数
    目录一、做题心得二、动规五步走三、题目与题解题目一:509.斐波那契数题目链接题解1:记忆性递归 题解2:动态规划题目二:70.爬楼梯 题目链接题解:动态规划题目三:746.使用最小花费爬楼梯题目链接题解:动态规划三、小结一、做题心得今天开始动态规划章节的第一......