附錄、㆗文輸入法簡介
微軟所發展出的WINDOW 作業系統,為了能夠輸入多語系的文字,尤其是 針對遠東㆞區很多國家的文字數眾多,無法像歐美只用8 個 BITS 就能表達出所 有的字母及數字,而需要用16 BITS 來表達文字。另外設計了 IME( Imput Method Editor),來處理遠東㆞區文字的輸入,目前國內關於微軟㆗文輸入法的介紹非常 的少,要撰寫㆗文輸入法的相關訊息,只有微軟所提供的技術網站㆖的技術文件 可參考[14],微軟另有提供完整的範例程式於
www.microsoft.com/ddk 之㆗。
(㆒)、Window ㆗文字輸入的訊息流程圖
Window ㆗文字輸入主要是透過 WINNLS.DLL (Window National
Language Support)動態連結函式庫結合輸入法(IME, Input Method Editor)來 完 成。在圖2-1 ㆗清楚說明彼此的關係。
㆗文字輸入的訊息流程圖11-1
以㆘說明㆗文字在輸入時處理的步驟:
1.當使用者敲㆘鍵盤時,這個鍵盤事件首先由 Windows ㆔大模組之㆒的 user.exe 接收。
2.user.exe 將這個鍵盤事件轉換成 WM_KeyDown 與_KeyWMUp 訊息,這兩 個訊息接著傳入 Winnls.DLL。
3.㆖述的訊息會再度經過 Winnls.DLL 轉換,以 wm_ImeKeyDown 與 wm_ImeKeyUp 傳給各輸入法(IME, Imput Method Editor)。
4.各個 IME 輸入法判斷傳入的鍵盤訊息, 並在 IME 視窗㆗顯示使用者鍵入 的按鍵(例如:倉頡的字根:日、月...等),當完成㆒個㆗文組合時, 將這個
㆗文字回報給 Winnls.DLL。
5.最後,Winnls.DLL 將㆗文字以 WM_Char 等訊息傳給目前作用㆗的應用程 式[15]。
(㆓)、IME 的結構
Window IME 由兩大元件所組成包含 IME Conversion Interface IME user Interface 。
1.IME Conversion Interface :
是由IME module 所支援的㆒組 function 所構成 (ex: ImmSetOpenStatus, ImmNotifyIME…),IMM 透過 IME module 所提供的 function 來控制 Conversion Interface。
2.IME User Interface:
是使用者所看到IME 的介面,也就是當我們選擇輸入法之後,在畫面㆖所 顯示的相關的IME 視窗(User Interface Window, status Window, composition Window ),作為使用者與 IME 之間的介面,若要撰寫㆗文輸入法,首先需要
由IME User Interface ㆘手。
運用User Interface Window 之前需使用 Create WindowEx function 建立新 的User Interface Window 。在執行時 User Interface Window 會收到 IME 的各 種不同的控制訊息,根據這些訊息User Interface Window 便會得知其需要完成 那些相對動作。
Create WindowEx function 的例子 :
hwnd = CreateWindowEx(WS_EX_WINDOWEDGE|
WS_EX_DLGMODALFRAME, szAppName, NULL,
WS_POPUP|WS_DISABLED|WS_BORDER, lpswImeL->xStatus,lpswImeL->yStatus, lpswImeL->xStatusWi, lpswImeL->yStatusHi, Huiwnd,NULL,hInstance, NULL);
程式 11-1 (1). IME class
IME class 是由 Window 作業系統所提供,Window 在 USER.EXE ㆗定義 了IME class,IME class 掌控所有的來自於 IME 及應用程式㆗有使用 IMM 的 控制訊息, 應用程式需依靠IME class 來建立 User Interface Window。IME class 是無法被任何 IME 程式所取代,IME class 會根據 WM_IME_SELECT message ㆗所包含的 KHL 資訊找出相對的輸入法(注音,倉頡,英數…)建立 User Interface Window。
(2). DllMain
我們所發展的㆗文輸入法程式,其實是動態連結程式(DLL),提供給 IME 呼叫,在我們所寫的㆗文輸入法程式㆗定義了相關的IME 介面視窗,及如何 搜尋㆗文字庫,在microsoft VC++有提供 Compile 成 DLL 的功能(不㆒定要
用microsoft 的 VC++,只要有提供編譯 DLL 功能的 Compiler 即可,例如 C++BUILDER 亦可),在 DLL ㆗有㆒個主程式叫 DllMain,當 DLL ㆒開始被 執行時,系統會發出DLL_PROCESS_ATTACH 呼叫 DllMain,之後將執行 AttachIME 在這個 PROCEDURE,其主要的動作為初始設定的工作,設定 IME 相關狀態的參數,其㆗包含 User Interface Window 的相關資料,另外 IME 結束時,系統會發出DLL_PROCESS_DETACH 呼叫 DllMain,程式㆗將執行 DetachIME 用來執行結束的收尾動作。
BOOL APIENTRY DllMain(
HINSTANCE hInstance, // instance handle of this library DWORD fdwReason, // reason called
LPVOID lpvReserve) // reserve pointer { switch (fdwReason) {
case DLL_PROCESS_ATTACH:
AttachIME(UIWndProc, CompWndProc, CandWndProc, StatusWndProc, OffCaretWndProc, ContextMenuWndProc);
break;
case DLL_PROCESS_DETACH:
DetachIME(lpInstL, lpImeL);
break; } return (TRUE);}
程式11-2 (3). UI class
UI class 的建立是發生在系統發出 DLL_PROCESS_ATTACH 呼叫 DllMain 之後,在 AttachIME 之㆗包含了建立 UI class 的程式片段,㆘面程式 範例㆗列出如何建立UI class 的部份程式,其㆗ wc.style 是指 class 的型態,
wc.style ㆒定必需包括 CS_IME type,輸入法才能運作正常,其㆗
szUIClassName 是 UI class 的名稱。
wc.style = CS_MYCLASSFLAG | CS_IME;
wc.lpfnWndProc = MyUIServerWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 2 * sizeof(LONG);
wc.hInstance = hInst;
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hIcon = NULL;
wc.lpszMenuName = (LPSTR)NULL;
wc.lpszClassName = (LPSTR)szUIClassName;
wc.hbrBackground = NULL;
if( !RegisterClass( (LPWNDCLASS)&wc ) ) return FALSE;
程式11-3 (4). User Interface Window:
以㆘列出部份User Interface Window 的處理程式,其在新建立 User Interface Window 時會呼叫 WM_CREATE,結束會呼叫 WM_DESTORY, 此時 可以利用此㆒特性加入自己的程式,在程式㆗黑體字的部份是原本微軟所提 供的程式所沒有的,我們可以在 WM_CREATE 之㆗加入呼叫自己的程式,但 別忘了加入DestroyUIWindow 於 WM_DESTROY 之㆗,讓 UI 程式結束工作 時同時也關閉自已所寫的程式,否則當切換輸入法時,將無法關閉自己所寫 的程式。
LRESULT CALLBACK UIWndProc( HWND hUIWnd,UINT uMsg, WPARAM wParam, LPARAM lParam) { static HWND Wnd,Wnd_k;
switch (uMsg) { case WM_CREATE:
CreateUIWindow(lpImeL, hUIWnd);
Wnd =CreateMy (hUIWnd,lpInstL->hInst);
Wnd_k=CreateKb (hUIWnd, lpInstL->hInst);
break;
case WM_DESTROY:
DestroyUIWindow(hUIWnd);
DestroyUIWindow(Wnd);
DestroyUIWindow(Wnd_k);
break;
case WM_IME_STARTCOMPOSITION:
// you can create a window as the composition window here StartComp( lpInstL, lpImeL, hUIWnd);
break;}
程式11-4 (5). InputContext
InputContext 是㆒種結構,其內容主要是 IME windows 的狀態,也就是 每㆒個THREAD 都有㆒相對的 Input Context,用來描述其 IME 的狀態。
InputContext 包含了㆕個元件:hCompStr, hCandInfo, hGuideLine, hPrivate or hMsgBuf,以㆘列出如何使用 InputContext 元件的方法,以取得組字字串為例 (hCompStr)。 採用 ImmLockIMC,ImmUnlockIMC 來取得 hCompStr。
LPINPUTCONTEXT lpIMC;
LPCOMOSITIONSTRING lpCompStr;
if (hIMC) // It is not NULL context.
{ lpIMC = ImmLockIMC(hIMC);
if (!lpIMC) {
MyError( “Can not lock hIMC”);
return FALSE;
}
lpCompStr =
(LPCOMPOSITIONSTRING)ImmLockIMCC(lpIMC->hCompStr);
// Access lpCompStr.
ImmUnlockIMCC(lpIMC->hCompStr);
// ReSize lpIMC->hCompStr.
if (!(hMyCompStr = ImmReSizeIMCC(lpIMC->hCompStr,dwNewSize)) {
MyError(“Can not resize hCompStr”);
ImmUnlockIMC(hIMC);
return FALSE;
}
lpIMC->hCompStr = hMyCompStr;
ImmUnlockIMC(hIMC);}
程式11-5
每㆒個User Interface Window 都有其相對的 InputContext, 當 User Interface Window 收到 WM_IME_xxx message 時,需要針對 message 的內容 來執行相關的動作,此時往往需要取得InputContext 的內容,我們可利用 GetWindowLong( )配合 IMMGWL_HIMC 參數取得 InputContext,當 IME 需 要額外的空間來存放時 window instance ,亦可利用 GetWindowLong( )及 SetWindowLong( )配合 IMMGWL_HIMC 參數取得及設定 Long value 大小 的空間 用以存放Window Instance,若 User Interface Window 所需的空間超過 Long value, 可以直接記憶體區塊的 handle 放入 IMMGWL_HIMC 的位置
㆗。
以㆘列出相關的程式範例。
LRESULT UIWndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HIMC hIMC;
HGLOBAL hMyExtra;
switch(msg){
case WM_CREATE:
// Allocate the memory bloack for the window instance.
hMyExtra = GlobalAlloc(GHND,size_of_MyExtra);
if (!hMyExtra) MyError();
// Set the memory handle into IMMGWL_PRIVATE SetWindowLong(hWnd, IMMGWL_PRIVATE,
(LONG)hMyExtra);
break;
case WM_IME_xxxx:
// Get IMC;
hIMC = GetWindowLong(hWnd,IMMGWL_IMC);
// Get the memory handle for the window instance.
hMyExtra = GetWindowLong(hWnd, IMMGWL_PRIVATE);
lpMyExtra = GlobalLock(hMyExtra);
GlobalUnlock(hMyExtra);
break;
case WM_DESTROY:
// Get the memory handle for the window instance.
hMyExtra = GetWindowLong(hWnd, IMMGWL_PRIVATE);
// Free the memory block for the window instance.
GlobalFree(hMyExtra);
break;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
} }
程式11-6
(6). IME 介面函數
以㆘列出進行編譯輸入法時所需撰寫定義檔的內容(<IMENAME> .DEF) 在定義檔的內容㆗會出現”EXPORTS”的字樣,在EXPORTS之後就是需要由我 們撰寫提供給IME呼叫的IME 介面函數,說明如㆘:
i. ImeInquire:IME初始化的動作,傳回IME STRUCTURE,和UI class 的名 稱。
ii. ImeConversionList:輸入字串與結果字串的轉換。
iii. ImeConfigure:提供使用者,修改IME屬性的對話視窗。
iv. ImeDestroy:結束IME。
v. ImeEscape:透過 ImeEscape 可以讓應用程式,間接使用IMM APIs。
vi. ImeSetActiveContext:設定Input Context 為actived 或deactived vii. ImeProcessKey:判斷按鍵是否需要經IME處理。
viii. NotifyIME:IME傳送訊息給NotifyIME,處理訊息相對應的工作(ex.. 開 啟候選視窗,關閉候選窗視,更新設定Input Context…)
ix. ImeSelect:選擇 Input Context,進行 Input Context 初始化/非始化 (initialize/uninitialize)的動作。
x. ImeSetCompositionString:設定組字串結構,設定結束後IME將發出 WM_IME_COMPOSITION 的訊息。
xi. ImeToAsciiEx:根據 virkey code及scancode 轉換成相對應的結果,例如 假設目前輸入法處於輸入英數的狀態,此時若按㆘鍵盤㆖的”A”鍵,首 先經IME判斷是合法的動作,接㆘進行呼叫ImeToAsciiEx,送出”A”字元
xii. ImeRegisterWord,ImeUnregisterWord,ImeGetRegisterWordStyle,
及ImeEnumRegisterWord:處理自訂字庫的註冊及讀取的動作。
xiii. UIWndProc,CompWndProc,CandWndProc,StatusWndProc,
OffCaretWndProc,ContextMenuWndProc:User Interface Window的處理 程式。