淺談IME編程

來源:互聯網
上載者:User

一直想寫一點關於IME編程的東西,今天終於有點時間,希望對後來者有點協助。在此要特別感謝“自由拼音”的作者李振春,我剛開始的幾個問題都是在他的協助下才解決。

首先我們需要明白IME是什麼東西。目前常用的IME基本上有兩種類型:外掛式(如早期的萬能五筆)及IME介面式(Input Method
Editor-IME)。外掛式比較簡單,就是一個exe檔案,通過類比一些Windows輸入訊息來給當前處於活動狀態的編輯視窗輸入文字,一個顯著的優點是IME只要啟動一次,就可以在所有進程中使用;但缺點不不容忽視,首先實現起來也不容易,一個更大的不足是相容性不夠好,通常一個Windows版本需要一人對應的IME版本,此外這類IME為了能夠截獲使用者輸入,通常需要掛接鍵盤鉤子,容易造成系統不穩定或者效率不高。大部分的IME還是採用IME來實現,下面本文主要討論一下IME編程需要注意的問題及解決辦法。

IME是什嗎?IME是在Windows平台上使用的標準的IME介面規範。它實質是一個DLL,Windows為這個DLL定義一系列的介面,不同的介面實現指定的功能。程式員在編寫IME程式時只需要實現這些介面並匯出就可以作為IME使用。關於具體介面的定義不是本文的重點,如果您需要瞭解只需要在網路中搜尋“IME編程指南”就可以明白
,更多資訊參考MSDN。

剛開始IME編程最棘手的問題通常是程式架構搭好了卻不知道如何使用及調試。這裡涉及到一個很重要的問題就是IME的安裝。IME就是Windows的一個外掛程式,需要先進行註冊,Windows才能識別並使用。為此您需要先將您產生的DLL複製到系統目錄(Windows/System32)再調用API
ImmInstallIME就可以實現了,在我的實踐中是先編一個簡單的程式來做安裝工作,在每次IME重新編譯完成以後調用一次以完成IME的註冊。這裡還有一個需要注意的問題是:Windows提供了一種機制,它允許IME程式一旦啟動就就不再退出,這就意味著如果你的程式碼經過修改需要重新安裝時將不得不重新啟動電腦。在IME定義的介面中有一個介面是提供IME的初始化的,它就是BOOL
WINAPI ImeInquire(LPIMEINFO lpIMEInfo,LPTSTR lpszUIClass,LPCTSTR
lpszOption),下面的代碼來自我寫的IME:

 

BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo,LPTSTR lpszUIClass,LPCTSTR lpszOption)
{
lpIMEInfo->dwPrivateDataSize = sizeof(CONTEXTPRIV);//系統根據它為INPUTCONTEXT.hPrivate分配空間
lpIMEInfo->fdwProperty = IME_PROP_KBD_CHAR_FIRST |
#ifdef _UNICODE
IME_PROP_UNICODE |
#endif
IME_PROP_SPECIAL_UI |
IME_PROP_END_UNLOAD ;

lpIMEInfo->fdwConversionCaps = IME_CMODE_FULLSHAPE |
IME_CMODE_NATIVE;
lpIMEInfo->fdwSentenceCaps = IME_SMODE_NONE;
lpIMEInfo->fdwUICaps = UI_CAP_2700;
lpIMEInfo->fdwSCSCaps = 0;
lpIMEInfo->fdwSelectCaps = SELECT_CAP_CONVERSION;
_tcscpy(lpszUIClass,CLSNAME_UI);
return TRUE;
}

lpIMEInfo->fdwProperty告訴Windows系統您編寫的IME的一些特徵,注意一下IME_PROP_END_UNLOAD這個標誌,有了它您編寫的IME會隨著啟動您的IME的應用程式(如NotePad)的退出而退出,否則它將長駐於系統中,這也是為什麼很多IME在升級安裝時需要首先重新啟動電腦的原因。

在這個介面中還有一點需要特別注意,那就是lpIMEInfo->dwPrivateDataSize,至少我是經過很多次測試才基本證實Windows根據該值為INPUTCONTEXT.hPrivate分配空間。此外如果您修改了這個介面,按照我個人的經驗是需要重新調用ImmInstallIME來安裝。

在安裝完成後,在IME列表中應該已經有了您自己的IME。點擊調試,由於它是一個DLL,您需要先選擇一個宿主程式,一般選擇“記事本”,以調試方式啟動“記事本”後,在這個“記事本”中開啟您的IME,您就可以在原始碼中設定斷點了。需要說明的是,VC6.0調試DLL不太好用,首先需要打上SP5或者SP6,這樣也不能夠在DLL啟動的時候就設定斷點,推薦使用.net來調試。

IME上下文(HIMC):HIMC是什嗎?在IME編程時必然要接觸到IME上下文這個術語,剛接觸時聽起來實在是半懂不懂。由於IME是一個外掛程式,它需要和調用它的應用程式通訊,在IME中產生的編碼及重碼資訊儲存在哪裡應用程式才能正確的讀取呢?答案就在於IME上下文。IME上下文是由User.exe(一個系統進程)為應用程式分配的記憶體控制代碼,在應用程式中啟動的IME在這塊記憶體中寫入資料,User.exe再將資料傳遞到應用程式。

UIWnd:在IME中需要匯出一個介面,原型如LRESULT WINAPI UIWndProc(HWND hUIWnd, UINT
message,WPARAM wParam, LPARAM
lParam),hUIWnd是由User.exe傳過來的視窗控制代碼,它是IME中建立的視窗如編碼視窗,重碼視窗,狀態列視窗的宿主(Owner),初學IME編程的人可能會問這個視窗顯示在哪裡呢?其實它並不是一個普通的視窗,它只是一個用來傳遞Windows訊息的視窗(Message
Only),在使用時,您不需要關心它在哪裡,只需要使用它就好了。

一個IME需要匯出19個(Win98版本)介面,但是對於一個只需要實現一般意義的文字輸入的軟體,您只需要實現幾個基本的介面就可以讓IME正常工作了。下面逐一介紹一下這幾個介面。

 

/**********************************************************************/
/* ImeSelect() */
/* Return Value: */
/* TRUE - successful, FALSE - failure */
/**********************************************************************/
BOOL WINAPI ImeSelect(HIMC hIMC,BOOL fSelect)

在這個介面中,系統通知IME當前是否開啟了IME輸入。一般IME啟動時會調用一次,在一些軟體(如EmEditor)中提供開啟與關閉IME的功能就是通過這個介面實現的。如果開啟IME,一般會在這個介面中做一些資料的初始化工作。

 

/***********************************************************************/
/*系統調用這個介面來判斷IME是否處理當前鍵盤輸入 */
/*HIMC hIMC:輸入上下文 */
/*UINT uKey:索引值 */
/*LPARAM lKeyData: unknown */
/*CONST LPBYTE lpbKeyState:鍵盤狀態,包含256鍵的狀態 */
/*return : TRUE-IME處理,FALSE-系統處理 */
/*系統則調用ImeToAsciiEx,否則直接將鍵盤訊息發到應用程式 */
/**********************************************************************/
BOOL WINAPI ImeProcessKey(HIMC hIMC,UINT uKey,LPARAM lKeyData,CONST LPBYTE lpbKeyState)

觀察注釋您可以看到在個介面是用來判斷使用者敲擊的哪個鍵需要處理,哪個鍵又應該交給系統自己處理,如果IME需要自己處理使用者輸入的鍵,則在這個介面中返回true,否則返回false。

 

/****************************************************************************************************************/
/* function:應用程式調用這個介面來進行輸入內容相關的轉換,IME程式在這個介面中轉換使用者的輸入 */
/* UINT uVKey:索引值,如果在ImeInquire介面中為fdwProperty設定了屬性IME_PROP_KBD_CHAR_FIRST,則高位元組是輸入索引值*/
/* UINT uScanCode:按鍵的掃描碼,有時兩個鍵有同樣的索引值,這時需要使用uScanCode來區分 */
/* CONST LPBYTE lpbKeyState:鍵盤狀態,包含256鍵的狀態 */
/* LPDWORD lpdwTransKey:訊息緩衝區,用來儲存IME要發給應用程式的訊息,第一個雙字是緩衝區可以容納的最大訊息條數 */
/* UINT fuState:Active menu flag(come from msdn) */
/* HIMC hIMC:輸入上下文 */
/* return : 返回儲存在訊息緩衝區lpdwTransKey中的訊息個數 */
/****************************************************************************************************************/
UINT WINAPI ImeToAsciiEx (UINT uVKey,UINT uScanCode,CONST LPBYTE lpbKeyState,LPDWORD lpdwTransKey,UINT fuState,HIMC hIMC)

這個介面可以說是IME最重要的部分,程式員需要在這個介面中實現編碼與重碼的轉換,轉換完成或者顯示在編碼視窗及重碼視窗,或者發送到應用程式。由於在這個介面中沒有傳入視窗控制代碼,如果通知IME程式的視窗更新顯示呢?當然我們可以使用全域變數,在此我個人推薦的方法是使用IME訊息(沒有什麼道理),您將訊息類型、參數儲存到lpdwTransKey指示的緩衝區中,User.exe會根據訊息類型做相應的處理並傳遞到UIWnd這個視窗中。

那麼如何輸入文字呢?要輸入文字需要3個訊息配合使用,分別是WM_IME_STARTCOMPOSITION、WM_IME_COMPOSITION和WM_IME_ENDCOMPOSITION,它們分別指示開始輸入編碼,輸入編碼或者結果(視參數而異)及編碼輸入完成。在開始編寫IME的時候,為了省事,我的IME在使用者確定要輸入一個重碼時才連續調用這3個訊息以向編碼器中輸入文字。由於WM_IME_STARTCOMPOSITION和WM_IME_ENDCOMPOSITION需要成對使用,這種方法可以確保它們配對。最初這種方式工作得很好,但是後來發現在一些軟體中出現相容性問題。如“智能五筆”在“遨遊”中就存在這個問題,在“遨遊”中的地址欄中開啟“智能五筆”,當需要使用回退鍵來刪除錯誤輸入的編碼時,會發現刪除的不是編碼視窗中的編碼而是編輯器中的文字。這是因為類似“遨遊”這類軟體主動接管了按鍵輸入如處理一些修飾鍵,當它發現這些修飾鍵不在WM_IME_STARTCOMPOSITION和WM_IME_ENDCOMPOSITION這兩個訊息之間時就自己處理修飾鍵而不是先交給User.exe了。因此正確的流程應該是在開始輸入編碼時發送WM_IME_STARTCOMPOSITION,輸入結束後發送WM_IME_ENDCOMPOSITION訊息。

/**********************************************************************//* UIWndProc() *//* IME UI window procedure *//**********************************************************************/LRESULT WINAPI UIWndProc(HWND hUIWnd, UINT message,WPARAM wParam, LPARAM lParam)

這是一個非常重要的介面,基本上一它負責各種訊息的傳遞。一般您需要在這個介面中根據不同的訊息類型,實現IME視窗(如編碼視窗、重碼視窗、狀態列視窗)的顯示、隱藏及更新等操作。這個介面實現的功能可能非常複雜,視情況而異,在此就不做更加深入的說明了。在使用時可以參見樣本工程。

 

BOOL WINAPI ImeConfigure(HKL hKL,HWND hWnd, DWORD dwMode, LPVOID lpData)

這是最後一個需要注意的介面,在顯示IME屬性配置時會Windows會調用這個介面。

 

基本的介面就介紹到這裡,下面談一談我個人在編寫IME程式時遇到的一些問題或者發現的一些需要注意的地方。

1、關於IME視窗:閱讀一些IME的代碼會奇怪,為什麼IME視窗在建立時需要指定WM_DISABLE屬性呢?原來是因為如果不指定這個屬性標誌,在開啟IME時,會導致當前的應用程式失去輸入焦點。但是指定了這個標誌後,IME視窗不能收到滑鼠訊息怎麼辦?解決的方法就在於WM_SETCURSOR這個訊息。這個訊息不管視窗是否可用,只要有滑鼠在視窗內視窗都會收到。您可以在這個訊息中類比滑鼠訊息也可以選擇調用SetCapture這個函數,這樣視窗就可以收到滑鼠訊息了。

2、關於視窗模式:使用了幾種IME後,你會發現,有的IME的編碼視窗和重碼視窗是一個視窗,有的又是兩個視窗,它們有什麼區別?或者有的人會覺得這個問題很可笑,但是當您研究了一段IME可能就會發現您也有類似的問題:因為在IME的匯出介面中關於使用者介面的函數就有4個,其它3個分類對應3個視窗回呼函數。事實上它們並沒有本質的區別,關鍵在於您的IME的使用範圍。一些軟體(如某些遊戲)為了介面的整體美觀,不希望使用者在開啟IME時顯示IME自己的視窗,而是希望IME按照它的意願將IME視窗需要顯示的內容顯示在它建立的視窗中,英文稱之為IME
Aware。由於我自己的IME目標不是在遊戲中使用,所在並沒有按照這個規矩來管理IME視窗,而是為了簡化,將編碼視窗和重碼視窗顯示的內容放到了一個視窗中。

3、關於自訂訊息:UIWndProc在WM_IME_NOTIFY中提供了一個IMN_PRIVATE,最初我理解為這個訊息應該和WM_USER一樣,當我需要不只一人自訂訊息時只需要在這個ID的基礎上增加值就好了。但事實是您定義的值可能是系統已經佔用的(視Windows的版本而異),您能夠使用的自訂訊息應該只有這一個,為了指示多個訊息類型,我使用的方法是在WM_IME_NOTIFY的LPARAM中進行區分。

4、調試資訊輸出:一般編寫IME都不會使用MFC,為了輸出調試資訊,一般只能使用OutputDebugString這個API,在範例程式碼中的helper.c中我編寫了一個類比TRACE的函數Helper_Trace,您可以用這個函數來將調試資訊輸出到調試視窗。

5、最後再談一談IME類型:前面提到IME分為外掛式和IME兩種,但是目前一些IME發展了第3種類型,那就是結合這兩種類型的優點。例如拼音加加,啟動拼音加加您會發現進程列表裡會多一個拼音加加的服務進程,其實它才是拼音加加IME的核心即資料處理部分。拼音加加的IME部分只是一個外殼,它提供傳統的IMEIME一樣的系統相容性。在我的IME中也採用了這種結構,使用記憶體檔案對應及普通的Windows訊息結合來實現兩個進程間的通訊。您可以在我的IME的原始碼中找到進程間的通訊原始碼及IME代碼。

好象沒有更多的經驗可言,總之,IME其實並不神秘,在我看來,只要能夠在VC中跟蹤代碼,我就不相信我會搞不定它!

一家之言,如果有什麼錯誤,還請大家批評指正。

關於範例程式碼:範例程式碼是我編寫的一個最基本的IME程式的架構,它顯示您輸入的編碼,並顯示一個固定的重碼,輸入空格後實現該重碼上屏的功能。通常我們能找到的代碼是一個完整的工程,這樣對於初學IME編程的人可能會陷入大量的非IME編碼架構的閱讀中,對於實際的IME編程並沒有多大的意義。這份代碼就是為了讓您擺脫那些無謂代碼的閱讀。

 

如果需要更加完整的IME代碼,推薦參考“自由拼音”的原始碼,當然也可以參考我寫的IME《啟程輸入之星》的代碼,您可以在我的網站上找到下載,http://www.setoutsoft.cn。這份代碼的介面部分我自認為是當前的IME中寫得非常出色的。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.