SendInput模擬鍵盤輸入的問題


SendInput模擬鍵盤輸入的問題

 http://www.cnblogs.com/yedaoq/archive/2010/12/30/1922305.html

最近接觸到這個函數,因此了解了一下,總結一下列在這。

我了解它的出發點是如何通過它向活動窗口輸入字符,這是很多程序都有的功能(我猜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(期望獲得兩個不同的字符輸入)。結果依然失敗,沒有產生任何消息。

我不清楚這是否意味着:對於鍵盤輸入,不允許將消息合並發送。

若您了解其中緣由或測試到與我不同的現象,請與我聯系:yedaoq@126.com

 

相關知識:

1. 輸入法也可以處理SendInput發送的Unicode消息,具體方式不詳。見MSDN中ImmGetProperty方法的參考:當dwIndex參數為IGP_PROPERTY時,IME_PROP_ACCEPT_WIDE_VKEY是一個可能的返回值,它表示IME會處理SendInput函數以VK_PACKET注入的Unicode字符,若返回值無該標識,則Unicode字符會直接發送給應用程序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM