天天看點

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

但實際上,這将導緻不産生任何消息。這兩個消息必須分開發送,如下所示:

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

  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字元會直接發送給應用程式。

繼續閱讀