keybd_event 被 SendInput 替代


keybd_event

   函数功能:该函数合成一次击键事件。系统可使用这种合成的击键事件来产生WM_KEYUP或WM_KEYDOWN消息,键盘 驱动程序中断处理程序调用keybd_event函数。在Windows NT中该函数己被使用SendInput来替代它。
函数原型;VOID keybd_event(BYTE bVk,BYTE bScan,DWORD dwFlags,DWORD dwExtralnfo);
   参数:
   bVk:     定义一个虚拟键码。键码值必须在1~254之间。
 bScan:   定义该键的硬件扫描码。
 dwFlags:定义函数操作的各个方面的一个标志位集。应用程序可使用如下一些预定义常数的组合设置标志位。
    KEYEVENTF_EXTENDEDKEY:若指定该值,则扫描码前一个值为OXEO(224)的前缀字节。
    KEYEVENTF_KEYUP:若指定该值,该键将被释放;若未指定该值,该键将被按下。
 dwExtralnfo:定义与击键相关的附加的32位值。
 返回值:该函数无返回值。
#include
#include
void main()
{
   Sleep(3000);
   keybd_event(16,0,0,0); //按下Shift键
   keybd_event('A',0,0,0); //按下a键
   keybd_event('A',0,KEYEVENTF_KEYUP,0); //松开a键
   keybd_event(16,0,KEYEVENTF_KEYUP,0); //松开Shift键
   //构成组合键---->按下Shift的同时按下a,形成 A
}
       备注:尽管keybd_event传递一个与OEM相关的硬件扫描码给系统,但 应用程序不能用此扫描码。系统在内部将扫描码转换成 虚拟键码,并且在传送给 应用程序前清除键码的UP/down位。 应用程序可以摸拟PRINTSCREEN键的按下来获得一个屏幕 快照,并把它存放到 剪切板中。若要做到这一点,则要将keybd_event的bVk参数置为VK_SNAPSHOT,bScan参数置为0(用以获得全屏 快照) 或hScan置为1(仅获得活动窗口的快照)。Windows CE:WindowsCE支持dwFlags参数附加的标志位。即使用KEYEVENTF_SILENT标志模拟击键,而不产生敲击的声音。 Windows CE不支持KEYEVENTF_EXTENDEDKEY标志。

       速查:Windows NT:3.1及以上版本;Windows:95及以上版本 ;Windows CE:1.0及以上版本;头文件:winuser.h;库文件:user32.lib。


SendInput模拟键盘输入问题

最近接触到这个函数,因此了解了一下,总结一下列在这。

我了解它的出发点是如何通过它向活动窗口输入字符,这是很多程序都有的功能(我猜Visual Assist X就用了这个功能)。

根据MSDN,此函数模拟按键操作,将一些消息插入键盘或鼠标的输入流中,Windows对它进行处理,生成相应的WM_KEYDOWN或 WM_KEYUP事件,这些事件与普通键盘输入一起进入应用程序的消息循环,它们不仅可以转换为WM_CHAR消息,还可以转换为其它(诸如加速键)等消 息。

使用它来发送字符消息,并没有看起来那么简单。这有两个需要考虑的问题:

1. 输入法的转换。例如需要向活动窗口发送一些英文字符,我们可能想象这样来实现:获取对应键盘字符的虚拟键码,发送一个SendInput。但是如果活动窗 口正在使用一个输入法,那么我们发送出去的消息,会进入输入法的Composition窗口,最终被转换为象形文字或被丢弃。只有当输入法关闭时,程序运 行的效果才会像我们期望的那样,在活动窗口中显示出英文字符。

2. 对于中文字符,应该怎么发送给活动窗口?由于SendInput模拟的是WM_KEYDOWN和WM_KEYUP事件,按照一般的思路,我们是否应该获取 中文字符的输入法编码(拼音或五笔码),然后向活动窗口发送编码相关的SendInput?那这不仅要求活动窗口开启输入法,甚至还要获知它的编码方式。

如上所述,若直接如想象中那样使用SendInput来输入字符,则必须分析活动窗口的输入法状态。而且输入英文时,要求关闭输入法,输入中文时,又要求打开输入法。若真要以这样的思路来实现,则必定是难以成功的。

 

那么,有没有不依赖活动窗口输入法状态的方式呢?

其实是有的,使用SendInput模拟键盘输入时,其参数是KEYBDINPUT结构,通过将其dwFlags成员设置 KEYEVENTF_UNICODE就可以了。使用此方式,只需将KEYBDINPUT.wScan设置为字符的Unicode编码即可。对于英文字符, 不需要关闭活动窗口的输入法;对于中文字符,也不要求活动窗口打开输入法和将字符转换为输入法编码。

MSDN对此方式的说明为:INPUT_KEYBOARD支持非键盘的输入方式,例如手写识别或语音识别,通过KEYEVENTF_UNICODE 标识,这些方式与键盘(文本)输入别无二致。如果指定了KEYEVENTF_UNICODE,SendInput发送一个WM_KEYDOWN或 WM_KEYUP消息给活动窗口的线程消息队列,消息的wParam参数为VK_PACKET。GetMessage或PeedMessage一旦获得此 消息,就把它传递给TranslateMessage,TranslateMessage根据wScan中指定的Unicode字符产生一个 WM_CHAR消息。若窗口是ANSI窗口,则Unicode字符会自动转换为相应的ANSI字符。

 

任何需要向活动窗口输入字符(包括英文)的功能均应使用这种方式来实现。事实上,键盘消息转换为字符消息的过程是很复杂的,这可能与键盘布局、区 域、换档状态等诸多因素有关,这也是Windows要使用TranslateMessage来转换消息的原因。因此,不应该试图通过击键事件来意图向活动 窗口输入特定的字符。

 

经测试,SendInput还有两个值得注意的地方:

1. 没有为KEYBDINPUT.dwFlags指定KEYEVENTF_KEYUP标识时,SendInput将生成WM_KEYDOWN消息,否则生成 WM_KEYUP消息,由于只有WM_KEYDOWN会转换为字符消息,因此,若以输入字符为目标,则不应指定KEYEVENTF_KEYUP标识。

2. 如果我们想达到实际做一次击键所产生的效果:顺序产生一个WM_KEYDOWN和一个WM_KEYUP事件。则必须分别以不指定 KEYEVENTF_KEYUP和指定KEYEVENTF_KEYUP的方式执行一次SendInput操作。SendInput允许在一次调用中发送多 个模拟消息:

  INPUT input[2];
  memset(input, 0, 2 * sizeof(INPUT));


  input[0].type = INPUT_KEYBOARD;
  input[0].ki.wVk = data;


  input[1].type = INPUT_KEYBOARD;
  input[1].ki.wVk = data;
  input[1].ki.dwFlags = KEYEVENTF_KEYUP;

  SendInput(2, input, sizeof(INPUT));

但实际上,这将导致不产生任何消息。这两个消息必须分开发送,如下所示:

  INPUT input[2];
  memset(input, 0, 2 * sizeof(INPUT));

  input[0].type = INPUT_KEYBOARD;
  input[0].ki.wVk = data;
  SendInput(1, input, sizeof(INPUT));

 

  input[1].type = INPUT_KEYBOARD;
  input[1].ki.wVk = data;
  input[1].ki.dwFlags = KEYEVENTF_KEYUP;

  SendInput(1, input + 1, sizeof(INPUT));

 

关于第二点内容,我很有疑问。因为之前有人在网上帖的代码是合并发送的,想必有人这么做过并且成功了。我不清楚是否与系统或其它因素有关。我也曾试图尝试解决此问题,但没有成功:

1. 根据MSDN,KEYBDINPUT.time是一个时间戳,如果为零,系统将使用它自己的时间戳。因此我怀疑两个一起发送的事件,是不是因为其时间戳相同,而被忽略掉了。于是我在上述代码中显式设置了该属性,再合并发送,结果依然是没有产生任何消息。

2. 我分别尝试了两种情况:合并发送的两条消息都没有指定KEYEVENTF_KEYUP(期望得到两个相同的字符输入);合并发送的两条消息具有不同的虚拟 键码且都不指定KEYEVENTF_KEYUP(期望获得两个不同的字符输入)。结果依然失败,没有产生任何消息。

我不清楚这是否意味着:对于键盘输入,不允许将消息合并发送。

相关知识:

1. 输入法也可以处理SendInput发送的Unicode消息,具体方式不详。见MSDN中ImmGetProperty方法的参考:当dwIndex参数为IGP_PROPERTY时,IME_PROP_ACCEPT_WIDE_VKEY是一个可能的返回值,它表示IME会处理SendInput函数以VK_PACKET注入的Unicode字符,若返回值无该标识,则Unicode字符会直接发送给应用程序。


在VC中使用SendInput函数实现中文的自动输入

首先是,头文件必须包含以下两个:
#include
#include

前者是SendInput函数要用到,后者是字符串转换的时候要用到。


void SendAscii(wchar_t data, BOOL shift)
{
  INPUT input[2];
  memset(input, 0, 2 * sizeof(INPUT));
 
  if (shift)
  {
    input[0].type = INPUT_KEYBOARD;
    input[0].ki.wVk = VK_SHIFT;
    SendInput(1, input, sizeof(INPUT));
  }

  input[0].type = INPUT_KEYBOARD;
  input[0].ki.wVk = data;

  input[1].type = INPUT_KEYBOARD;
  input[1].ki.wVk = data;
  input[1].ki.dwFlags = KEYEVENTF_KEYUP;

  SendInput(2, input, sizeof(INPUT));

  if (shift)
  {
    input[0].type = INPUT_KEYBOARD;
    input[0].ki.wVk = VK_SHIFT;
    input[0].ki.dwFlags = KEYEVENTF_KEYUP;
    SendInput(1, input, sizeof(INPUT));  
  }
}


void SendUnicode(wchar_t data)
{
  INPUT input[2];
  memset(input, 0, 2 * sizeof(INPUT));
 
  input[0].type = INPUT_KEYBOARD;
  input[0].ki.wVk = 0;
  input[0].ki.wScan = data;
  input[0].ki.dwFlags = 0x4;//KEYEVENTF_UNICODE;
 
  input[1].type = INPUT_KEYBOARD;
  input[1].ki.wVk = 0;
  input[1].ki.wScan = data;
  input[1].ki.dwFlags = KEYEVENTF_KEYUP | 0x4;//KEYEVENTF_UNICODE;
 
  SendInput(2, input, sizeof(INPUT));
}

//为方便使用,下面这个函数包装了前两个函数。
void SendKeys(CString msg)
{
  short vk;
  BOOL shift;

  USES_CONVERSION;
  wchar_t* data = T2W(msg.GetBuffer(0));
  int len = wcslen(data);

  for(int i=0;i
  {
    if (data[i]>=0 && data[i]<256) //ascii字符
    {
      vk = VkKeyScanW(data[i]);

      if (vk == -1)
      {
        SendUnicode(data[i]);
      }
      else
      {
        if (vk < 0)
        {
          vk = ~vk + 0x1;
        }
       
        shift = vk >> 8 & 0x1;
       
        if (GetKeyState(VK_CAPITAL) & 0x1)
        {
          if (data[i]>='a' && data[i]<='z' || data[i]>='A' && data[i]<='Z')
          {
            shift = !shift;
          }
        }

        SendAscii(vk & 0xFF, shift);
      }
    }
    else //unicode字符
    {
      SendUnicode(data[i]);
    }
  }
}

直接调用SendKeys函数就可以在当前光标的位置自动输入指定的字符串,下面的例子演示了如何自动打开记事本程序并输入一段话:
void CSendInputDlg::OnTest()
{
  ShellExecute(NULL, NULL, "notepad.exe", NULL, NULL, SW_SHOWNORMAL);
 
  Sleep(500); //为了确保记事本程序打开完毕,稍等片刻

  CWnd *pWnd = FindWindow(NULL, "无标题 - 记事本");
  if (pWnd)
  {
    pWnd->SetForegroundWindow();
    SendKeys("我是sway,我爱中国!\nI love China!\nEmail: xmujava@163.com\t\n2010-05-21  \b\b");
  }
}

 

 

//////////////////////////////////////////////////////////////////////////////////////////////////////

SendInput模拟键盘和鼠标事件

INPUT kbinput[5];
   ZeroMemory( &kbinput, sizeof(INPUT)*5 );

   kbinput[0].type = INPUT_KEYBOARD;
   kbinput[0].ki.wVk = 'Z';

   kbinput[1].type = INPUT_KEYBOARD;
   kbinput[1].ki.wVk = 'W';

   kbinput[2].type = INPUT_KEYBOARD;
   kbinput[2].ki.wVk = 'J';
   //kbinput[2].ki.dwFlags = KEYEVENTF_KEYUP;

   kbinput[3].type=INPUT_MOUSE;
   kbinput[3].mi.dx=100;
   kbinput[3].mi.dy=100;
   kbinput[3].mi.mouseData=0;
   kbinput[3].mi.dwFlags=MOUSEEVENTF_RIGHTDOWN;

   kbinput[4].type=INPUT_MOUSE;
   kbinput[4].mi.dx=100;
   kbinput[4].mi.dy=100;
   kbinput[4].mi.mouseData=0;
   kbinput[4].mi.dwFlags=MOUSEEVENTF_RIGHTUP;

   UINT uRet = SendInput( 5, kbinput, sizeof(INPUT) );


#include <windows.h> #include <iostream> #include <fstream> #include <string> #include <conio.h> using namespace std; const string CONFIG_FILE = "key_config.txt"; const string DEFAULT_KEY = "F8"; // 虚拟键码映射表 const unordered_map<string, SHORT> vkMap = { {"A", 0x41}, {"B", 0x42}, {"C", 0x43}, {"D", 0x44}, {"E", 0x45}, {"F", 0x46}, {"G", 0x47}, {"H", 0x48}, {"I", 0x49}, {"J", 0x4A}, {"K", 0x4B}, {"L", 0x4C}, {"M", 0x4D}, {"N", 0x4E}, {"O", 0x4F}, {"P", 0x50}, {"Q", 0x51}, {"R", 0x52}, {"S", 0x53}, {"T", 0x54}, {"U", 0x55}, {"V", 0x56}, {"W", 0x57}, {"X", 0x58}, {"Y", 0x59}, {"Z", 0x5A}, {"0", 0x30}, {"1", 0x31}, {"2", 0x32}, {"3", 0x33}, {"4", 0x34}, {"5", 0x35}, {"6", 0x36}, {"7", 0x37}, {"8", 0x38}, {"9", 0x39}, {"F1", VK_F1}, {"F2", VK_F2}, {"F3", VK_F3}, {"F4", VK_F4}, {"F5", VK_F5}, {"F6", VK_F6}, {"F7", VK_F7}, {"F8", VK_F8}, {"F9", VK_F9}, {"F10", VK_F10}, {"F11", VK_F11}, {"F12", VK_F12}, {"SPACE", VK_SPACE}, {"ENTER", VK_RETURN}, {"ESC", VK_ESCAPE}, {"LEFT", VK_LEFT}, {"RIGHT", VK_RIGHT}, {"UP", VK_UP}, {"DOWN", VK_DOWN}, {"CTRL", VK_CONTROL}, {"ALT", VK_MENU}, {"SHIFT", VK_SHIFT}, {"TAB", VK_TAB}, {"CAPSLOCK", VK_CAPITAL}, {"BACKSPACE", VK_BACK} }; // 获取键名对应的虚拟键码 SHORT GetKeyCode(const string& keyName) { auto it = vkMap.find(keyName); if (it != vkMap.end()) { return it->second; } return 0; } // 获取虚拟键码对应的键名 string GetKeyName(SHORT vkCode) { for (const auto& pair : vkMap) { if (pair.second == vkCode) { return pair.first; } } return ""; } // 初始化配置文件 void InitConfigFile() { ofstream file(CONFIG_FILE); if (file.is_open()) { file << DEFAULT_KEY; file.close(); } } // 读取配置的键名 string ReadConfigKey() { ifstream file(CONFIG_FILE); string keyName; if (file.is_open()) { file >> keyName; file.close(); } return keyName.empty() ? DEFAULT_KEY : keyName; } // 保存键名到配置文件 void SaveConfigKey(const string& keyName) { ofstream file(CONFIG_FILE); if (file.is_open()) { file << keyName; file.close(); } } // 等待用户按键并返回键名 string WaitForUserKeyPress() { cout << "请按下要设置的键 (按ESC取消)...\n"; string lastKey; while (true) { if (_kbhit()) { int ch = _getch(); // 处理功能键 if (ch == 0 || ch == 0xE0) { int ext = _getch(); SHORT vkCode = MapVirtualKey(ext, MAPVK_VSC_TO_VK); lastKey = GetKeyName(vkCode); if (!lastKey.empty()) { cout << "检测到按键: " << lastKey << endl; } } // 处理普通键 else { SHORT vkCode = MapVirtualKey(ch, MAPVK_VSC_TO_VK); lastKey = GetKeyName(vkCode); if (!lastKey.empty()) { cout << "检测到按键: " << lastKey << endl; } // ESC键取消 if (vkCode == VK_ESCAPE) { return ""; } } // 等待按键释放 while (_kbhit()) { _getch(); } // 返回最后一个有效按键 if (!lastKey.empty()) { return lastKey; } } Sleep(10); } } // 模拟按键按下 void SimulateKeyPress(SHORT vkCode) { INPUT input[2] = { {0}, {0} }; // 按下键 input[0].type = INPUT_KEYBOARD; input[0].ki.wVk = vkCode; // 释放键 input[1].type = INPUT_KEYBOARD; input[1].ki.wVk = vkCode; input[1].ki.dwFlags = KEYEVENTF_KEYUP; SendInput(2, input, sizeof(INPUT)); } // 串口监听函数 void MonitorSerialPort(SHORT vkCode) { HANDLE hSerial = CreateFile("COM3", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hSerial == INVALID_HANDLE_VALUE) { cout << "无法打开串口COM3\n"; return; } // 配置串口 DCB dcb = { 0 }; dcb.DCBlength = sizeof(DCB); if (!GetCommState(hSerial, &dcb)) { cout << "获取串口状态失败\n"; CloseHandle(hSerial); return; } dcb.BaudRate = CBR_9600; dcb.ByteSize = 8; dcb.StopBits = ONESTOPBIT; dcb.Parity = NOPARITY; if (!SetCommState(hSerial, &dcb)) { cout << "设置串口状态失败\n"; CloseHandle(hSerial); return; } cout << "开始监听串口 (按ESC退出)...\n"; cout << "当串口开关按下时将模拟按下: " << GetKeyName(vkCode) << endl; char buffer; DWORD bytesRead; bool running = true; while (running) { if (ReadFile(hSerial, &buffer, 1, &bytesRead, NULL) && bytesRead > 0) { if (buffer == '1') { // 假设串口发送'1'表示开关按下 SimulateKeyPress(vkCode); } } // 检查是否按下ESC键 if (_kbhit() && _getch() == 27) { running = false; } Sleep(10); } CloseHandle(hSerial); } // 主菜单 void ShowMainMenu(const string& currentKey) { system("cls"); cout << "===== 串口按键模拟器 =====\n"; cout << "当前配置按键: " << currentKey << "\n\n"; cout << "1. 更改按键配置\n"; cout << "2. 开始监听串口\n"; cout << "3. 退出程序\n"; cout << "请选择: "; } int main() { // 初始化配置文件 ifstream testFile(CONFIG_FILE); if (!testFile.good()) { InitConfigFile(); } testFile.close(); string currentKey = ReadConfigKey(); bool running = true; while (running) { ShowMainMenu(currentKey); int choice; cin >> choice; switch (choice) { case 1: { // 更改按键配置 string newKey = WaitForUserKeyPress(); if (!newKey.empty()) { cout << "要将按键配置更改为 " << newKey << " 吗? (Y/N): "; char confirm; cin >> confirm; if (toupper(confirm) == 'Y') { SaveConfigKey(newKey); currentKey = newKey; cout << "按键配置已更新\n"; } } else { cout << "取消更改\n"; } break; } case 2: // 开始监听串口 MonitorSerialPort(GetKeyCode(currentKey)); break; case 3: // 退出程序 running = false; break; default: cout << "无效选择\n"; break; } cout << "按任意键继续..."; _getch(); } return 0; }
最新发布
07-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值