keybd_event 被 SendInput 替代

Max Woods發表於2014-08-23

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) );

相關文章