最近参与公司RPA项目开发,其中遇到了自动化输入功能,主要从三中不同的模式实现,包括(Ui Automation、Windows Message、Simulate),其中Simulate需要调用Win32API中的SendInput函数模拟键盘输入字符串,下面就详细介绍下SendInput函数的使用。
Win 32 API 中SendInput函数描述
UINT WINAPI SendInput(
__in UINT nInputs,
__in LPINPUT pInputs,
__in int cbSize);
对应的C#代码:
[DllImport("user32")]
public static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
其中参数pInputs是的数组类型,数组元素INPUT结构,所以我们下面还要在C#中定义对应的INPUT结构或者对象。INPUT结构中主要是定义你需要的鼠标或者键盘等操作。nInputs指明pInputs数组长度。cbSize指明INPUT结构的大小。
下面是Win32API 中INPUT结构描述:
typedef struct tagINPUT {
DWORD type;
union {
MOUSEINPUT mi;
KEYBDINPUT ki;
HARDWAREINPUT hi;
};
} INPUT, *PINPUT, FAR* LPINPUT;
还有Win32API中MOUSEINPUT,KEYBDINPUT,HARDWAREINPUT结构的定义:
typedef struct tagMOUSEINPUT {
LONG dx;
LONG dy;
DWORD mouseData;
DWORD dwFlags;
DWORD time;
ULONG_PTR dwExtraInfo;
} MOUSEINPUT, *PMOUSEINPUT;
typedef struct tagKEYBDINPUT {
WORD wVk;
WORD wScan;
DWORD dwFlags;
DWORD time;
ULONG_PTR dwExtraInfo;
} KEYBDINPUT, *PKEYBDINPUT;
typedef struct tagHARDWAREINPUT {
DWORD uMsg;
WORD wParamL;
WORD wParamH;
} HARDWAREINPUT, *PHARDWAREINPUT;
对应的C#代码:
public struct INPUT
{
[FieldOffset(0)]
public int type;
[FieldOffset(4)]
public KEYBDINPUT ki;
[FieldOffset(4)]
public MOUSEINPUT mi;
[FieldOffset(4)]
public HARDWAREINPUT hi;
}
public struct MOUSEINPUT
{
public int dx;
public int dy;
public int mouseData;
public int dwFlags;
public int time;
public IntPtr dwExtraInfo;
}
public struct KEYBDINPUT
{
public short wVk;
public short wScan;
public int dwFlags;
public int time;
public IntPtr dwExtraInfo;
}
public struct HARDWAREINPUT
{
public int uMsg;
public short wParamL;
public short wParamH;
}
上面主要是一些函数和数据结构的定义,下面给出模拟键盘主要代码:
public void SimulateInputString(string sText)
{
char[] cText = sText.ToCharArray();
foreach (char c in cText)
{
Win32API.INPUT[] input = new Win32API.INPUT[2];
if (c >= 0 && c < 256)//a-z A-Z
{
short num = Win32API.VkKeyScan(c);//获取虚拟键码值
if (num != -1)
{
bool shift = (num >> 8 & 1) != 0;//num >>8表示 高位字节上当状态,如果为1则按下Shift,否则没有按下Shift,即大写键CapsLk没有开启时,是否需要按下Shift。
if ((Win32API.GetKeyState(20) & 1) != 0 && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')))//Win32API.GetKeyState(20)获取CapsLk大写键状态
{
shift = !shift;
}
if (shift)
{
input[0].type = 1;//模拟键盘
input[0].ki.wVk = 16;//Shift键
input[0].ki.dwFlags = 0;//按下
Win32API.SendInput(1u, input, Marshal.SizeOf((object)default(Win32API.INPUT)));
}
input[0].type = 1;
input[0].ki.wVk = (short)(num & 0xFF);
input[1].type = 1;
input[1].ki.wVk = (short)(num & 0xFF);
input[1].ki.dwFlags = 2;
Win32API.SendInput(2u, input, Marshal.SizeOf((object)default(Win32API.INPUT)));
if (shift)
{
input[0].type = 1;
input[0].ki.wVk = 16;
input[0].ki.dwFlags = 2;//抬起
Win32API.SendInput(1u, input, Marshal.SizeOf((object)default(Win32API.INPUT)));
}
continue;
}
}
input[0].type = 1;
input[0].ki.wVk = 0;//dwFlags 为KEYEVENTF_UNICODE 即4时,wVk必须为0
input[0].ki.wScan = (short)c;
input[0].ki.dwFlags = 4;//输入UNICODE字符
input[0].ki.time = 0;
input[0].ki.dwExtraInfo = IntPtr.Zero;
input[1].type = 1;
input[1].ki.wVk = 0;
input[1].ki.wScan = (short)c;
input[1].ki.dwFlags = 6;
input[1].ki.time = 0;
input[1].ki.dwExtraInfo = IntPtr.Zero;
Win32API.SendInput(2u, input, Marshal.SizeOf((object)default(Win32API.INPUT)));
}
}
上面代码同样调用了Win32API中其他一些函数,下面简单介绍下:
VkKeyScan:
函数功能:该函数将一个字符翻译成相应的虚拟键码和对于当前键盘的转换状态。该函数己被VkKeyScanEx函数所替代。仍然可以使用VkKeyscan函数,但是不必再定义键盘布局。
函数原型:SHORT VkKeyScan(TCHARch);
参数:
ch:定义被翻译成虚拟键码的字符。
返回值:若函数调用成功,则返回值的低位字节中包含了虚拟键码,高位字节中包含了上挡状态,这些状态可以是如下标志位的组合:
1:按下的可以是任一Shift键。2:按下的可以是任一Ctrl键。
4:按下的可以是任一AIt键。8:按下的是Hankaku键。
16:保留(由键盘驱动程序定义)。32:保留(由键盘驱动程序定义)。
GetKeyState:
函数功能:该函数检取指定虚拟键的状态。该状态指定此键是UP状态,DOWN状态,还是被触发的(开关每次按下此键时进行切换)。
函数原型:SHORT GetKeyState(int nVirtKey);
参数:
nVrtKey:
定义一虚拟键。若要求的虚拟键是字母或数字(A~Z,a~z或0~9),nVirtKey必须被置为相应字符的ASCII码值,对于其他的键,nVirtKey必须是一虚拟键码。若使用非英语键盘布局,则取值在ASCIIa~z和0~9的虚拟键被用于定义绝大多数的字符键。例如,对于德语键盘格式,值为ASCII0(OX4F)的虚拟键指的是"0"键,而VK_OEM_1指"带变音的0键"
返回值:返回值给出了给定虚拟键的状态,状态如下:
若高序位为1,则键处于DOWN状态,否则为UP状态。
若低序位为1,则键被触发。例如CAPS LOCK键,被找开时将被触发。若低序位置为0,则键被关闭,且不被触发。触发键在键盘上的指示灯,当键被触发时即亮,键不被触发时即灭。