程序以北大路路通输入法作为分析版本,此版本源码比较清晰,容易入手
第一步: 对窗口进行注册
BOOL WINAPI DllMain (HINSTANCE hInstDLL, DWORD dwFunction, LPVOID lpNot) { switch(dwFunction) { case DLL_PROCESS_ATTACH: g_hInst = hInstDLL; if (!RegisterIMEClass(g_hInst)) return FALSE; LoadMB(hInstDLL); break; case DLL_PROCESS_DETACH: UnregisterIMEClass(g_hInst); DestroyMB(); break; case DLL_THREAD_ATTACH: break; case DLL_THREAD_DETACH: break; } return TRUE; }
static BOOL RegisterIMEClass( HANDLE hInstance ) { WNDCLASSEX wc; // register class of UI window. wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_PY | CS_IME; wc.lpfnWndProc = UIWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 2 * sizeof(LONG); wc.hInstance = hInstance; wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hIcon = NULL; wc.lpszMenuName = (LPTSTR)NULL; wc.lpszClassName = UICLASSNAME; wc.hbrBackground = NULL; wc.hIconSm = NULL; if( !RegisterClassEx( (LPWNDCLASSEX)&wc )) return FALSE; // register class of composition window. wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_PY | CS_IME; wc.lpfnWndProc = CompWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hIcon = NULL; wc.lpszMenuName = (LPTSTR)NULL; wc.lpszClassName = COMPCLASSNAME; wc.hbrBackground = NULL; wc.hIconSm = NULL; if( !RegisterClassEx( (LPWNDCLASSEX)&wc )) return FALSE; // register class of candadate window. wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_PY | CS_IME; wc.lpfnWndProc = CandWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hIcon = NULL; wc.lpszMenuName = (LPTSTR)NULL; wc.lpszClassName = CANDCLASSNAME; wc.hbrBackground = NULL; wc.hIconSm = NULL; if( !RegisterClassEx( (LPWNDCLASSEX)&wc )) return FALSE; // register class of status window. wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_PY | CS_IME; wc.lpfnWndProc = StatusWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hCursor = LoadCursor( NULL, IDC_ARROW ); wc.hIcon = NULL; wc.lpszMenuName = (LPTSTR)NULL; wc.lpszClassName = STATUSCLASSNAME; wc.hbrBackground = NULL; wc.hIconSm = NULL; if( !RegisterClassEx( (LPWNDCLASSEX)&wc )) return FALSE; return TRUE; } static void UnregisterIMEClass( HANDLE hInstance ) { UnregisterClass(UICLASSNAME,g_hInst); UnregisterClass(COMPCLASSNAME,g_hInst); UnregisterClass(CANDCLASSNAME,g_hInst); UnregisterClass(STATUSCLASSNAME,g_hInst); }
第二步 设置窗口处理过程:
UI用户界面窗口
LRESULT WINAPI UIWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { HIMC hIMC = {0}; LPINPUTCONTEXT lpIMC = NULL; LONG lRet = 0L; // 为什么代码没有用CreateWindowEx创建UI窗口 // 由IME默认窗口 通过ImeInquire函数 确定当前输入法使用的UI用户界面窗口类后,IME默认窗口会自动创建UI窗口,并将IME消息发送给UI窗口, 进而通过UIWndProc处理这些消息 // g_hUIWnd = hWnd; hIMC = (HIMC)GetWindowLong(hWnd,IMMGWL_IMC); if (!hIMC) { if (IsIMEMessage(message)) return 0; } switch (message) { case WM_CREATE: SetCandWindowPos(-1, -1); CreateCandWindow(hWnd); break; case WM_DESTROY: if (IsWindow(GetStatusWnd())) { DestroyWindow(GetStatusWnd()); } if (IsWindow(GetCandWnd())) { DestroyWindow(GetCandWnd()); } break; case WM_IME_SETCONTEXT: if (wParam) { if (hIMC) { lpIMC = ImmLockIMC(hIMC); if (lpIMC) { ShowCandWindow(); } else { if (IsWindow(GetCandWnd())) { ShowWindow(GetCandWnd(), SW_HIDE); } } ImmUnlockIMC(hIMC); } else { if (IsWindow(GetCandWnd())) { ShowWindow(GetCandWnd(), SW_HIDE); } } } break; case WM_IME_STARTCOMPOSITION: break; case WM_IME_COMPOSITION: ShowCandWindow(); //移动脱字符时用,将组合框、候选框通由状态条处理的关键一步 break; case WM_IME_ENDCOMPOSITION: if (IsWindow(GetCandWnd())) { ShowWindow(GetCandWnd(), SW_HIDE); } break; case WM_IME_COMPOSITIONFULL: break; case WM_IME_SELECT: break; case WM_IME_CONTROL: break; case WM_IME_NOTIFY: lRet = NotifyHandle(hIMC, hWnd,message,wParam,lParam); break; default: return DefWindowProc(hWnd,message,wParam,lParam); } return lRet; }
Composition窗口过程
LRESULT WINAPI CompWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { switch (message) { case WM_PAINT: case WM_SETCURSOR: case WM_MOUSEMOVE: case WM_LBUTTONUP: case WM_RBUTTONUP: break; default: if (!IsIMEMessage(message)) { return DefWindowProc(hWnd,message,wParam,lParam); } break; } return 0L; }
candadate window窗口处理过程
LRESULT WINAPI CandWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { switch (message) { case WM_PAINT: PaintCandWindow(hWnd); break; case WM_SETCURSOR: case WM_MOUSEMOVE: case WM_LBUTTONUP: case WM_RBUTTONUP: break; default: if (!IsIMEMessage(message)) { return DefWindowProc(hWnd,message,wParam,lParam); } break; } return 0L; }
status window窗口 处理过程
LRESULT WINAPI StatusWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hPainthDC = NULL; HDC hDC = NULL; PAINTSTRUCT ps = {0}; hDC = GetDC(hWnd); switch (message) { case WM_CREATE: g_hBmpStatus = LoadBitmap(GetInstance(), MAKEINTRESOURCE(STATUSBITMAP)); break; case WM_PAINT: hPainthDC = BeginPaint(hWnd, &ps); PaintStatusWindow(hPainthDC, g_dwImeStatus); EndPaint(hWnd, &ps); break; case WM_SETCURSOR: GetWindowRect(hWnd, &g_drc); if (!SetDragRgn(hWnd, message, wParam, lParam, &g_ptdif, &g_drc, &g_DragFlg)) { HIMC hIMC; DWORD dwImePos = 0; SetCursor(LoadCursor(NULL, IDC_ARROW)); dwImePos = CheckButtonPos(hWnd, lParam); if (hIMC = GethIMC()) { SetIMEStatus(dwImePos, 0); SetIMEOpenStatus(hIMC); ImmUnlockIMC(hIMC); } } if (HIWORD(lParam) == WM_LBUTTONDOWN) { SetCapture(hWnd); g_fCaptrue = TRUE; } break; case WM_LBUTTONDOWN: break; case WM_MOUSEMOVE: SetDragRgn(hWnd, message, wParam, lParam, &g_ptdif, &g_drc, &g_DragFlg); break; case WM_LBUTTONUP: if (!SetDragRgn(hWnd, message, wParam, lParam, &g_ptdif, &g_drc, &g_DragFlg)) { PaintStatusWindow(hDC, g_dwImeStatus); } if (g_fCaptrue) { ReleaseCapture(); g_fCaptrue = FALSE; } break; case WM_DESTROY: DeleteObject(g_hBmpStatus); break; case WM_MOVE: break; default: if (!IsIMEMessage(message)) { return DefWindowProc(hWnd, message, wParam, lParam); } break; } ReleaseDC(hWnd, hDC); return 0; }
注册 了 UI窗口 写作窗口 候选窗口 状态窗口
但我在分析输入法源程序时发现:程序只CreateCandWindow、CreateStatusWindow 创建了候选窗口和状态窗口 这两个窗口。
我明白 写作窗口放在在候选窗口中处理了,但不明白 为什么没有创建UI窗口,因为UI窗口负责处理输入法消息,如果没有创建此窗口,那怎么会调用此窗口的窗口过程来处理输入法消息呢?
又查了许多资料,结合源码分析,终于知道了原因所在: 当选择某个输入法时,IMM 首先调用ImeInquire 用以获得输入法相关信息。其中就包括输入法的用户界面UI窗口类名,从而据此自动创建用户界面窗口,完成初始化等工作。
下面是IME转换界面15个接口函数的实现,在路路通源程序的基础上,给其添加了一些说明
/////////////////////////////////////////////////////////////////////////////////// // // 函 数:1 // // // // // // // 参 数: // // 作 用:刚选择某输入法时,IMM首先调用此函数用以获得IME相关信息 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo,LPTSTR lpszUIClass,LPCTSTR lpszOption) { if (!lpIMEInfo) return (FALSE); lpIMEInfo->dwPrivateDataSize = 0; 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, UICLASSNAME); return TRUE; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数:2 // // , // // // // // 参 数: 在打开输入法时 fSelect为TRUE, 在关闭输入法时fSelect为FALSE // // 作 用: 在打开或关闭输入法时被调用,在此函数中对输入法上下文进行初始化或恢复释放。 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI ImeSelect(HIMC hIMC, BOOL fSelect) { LPINPUTCONTEXT lpIMC; BYTE bKeyData[256] = {0}; if (!hIMC) return FALSE; lpIMC = ImmLockIMC(hIMC); if (!lpIMC) return FALSE; lpIMC->fOpen = fSelect; //对输入法上下文进行初始化 ImmUnlockIMC(hIMC); if (fSelect) //打开输入法 { //检测大写键状态 GetKeyboardState(bKeyData); if (bKeyData[VK_CAPITAL] & 1) //切换键由低字节判断其按下还是释放 { SetIMEStatus(IMEPOS_SET, VK_CAPITAL); SetIMEOpenStatus(hIMC); } } return TRUE; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数:3 // // , // // // // // 参 数: // // 作 用: 处理键盘消息: IMM通过此函数,对键盘消息进行分类筛选,一类可以直接发给应用程序,一类需要发送给IME进行转换 // // 返回值: 返回值为FALSE,说明键盘消息被直接发送给了应用程序; 返回值为TRUE 说明键盘消息被发送给了IME,被发送IME后,IMM会立即调用ImeToAsciiEx对键盘消息进行转换。 // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI ImeProcessKey(HIMC hIMC, UINT vKey, LPARAM lKeyData, CONST LPBYTE lpbKeyState) { BOOL fRet = FALSE; PrintfToStatus(_T("%x"), vKey); fRet = ImeProcessKeyHandler(hIMC, vKey, lKeyData, lpbKeyState); return fRet; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数:4 // // , // // // // // 参 数: // // 作 用: 输入法编程最重要部分----- 此函数将IMM传递过来的键盘消息转换为composition写作窗口中的字符串,然后再查找码表,更新候选窗口,最后选择某候选字符作为最终结果 // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// UINT WINAPI ImeToAsciiEx ( UINT uVKey, UINT uScanCode, CONST LPBYTE lpbKeyState, LPDWORD lpdwTransKey, UINT fuState, HIMC hIMC) { LPINPUTCONTEXT lpIMC = NULL; if (!(lpIMC = ImmLockIMC(hIMC))) return 0; //如果键为释放状态,不作处理 if (lpIMC->fOpen && !(uScanCode & 0x8000)) { LPARAM lParam = ((DWORD)uScanCode << 16) + 1L; KeyDownHandler(uVKey, lParam); } ImmUnlockIMC(hIMC); return 0; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数:5 // // , // // // // // 参 数: // // 作 用: (在控制面板或其它方式中)设置输入法属性时被调用 可显示输入法属性设置对话框 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI ImeConfigure(HKL hKL,HWND hWnd, DWORD dwMode, LPVOID lpData) { DialogBox(GetInstance(), MAKEINTRESOURCE(DIALOGCONFIG), hWnd, ConfigDialogProc); InvalidateRect(hWnd, NULL, FALSE); return TRUE; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数:6 // // , // // // // // 参 数: // // 作 用: 系统通知输入法编辑器根据参数修改输入法编辑器的当前状态。 // 比如:显示/隐藏候选窗口,选定某个候选窗口,更新候选窗口页起始位置和页尺寸,更新输入上下文内容,修改写作串内容等 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI NotifyIME(HIMC hIMC, DWORD dwAction, DWORD dwIndex, DWORD dwValue) { BOOL bRet = FALSE; switch(dwAction) { case NI_OPENCANDIDATE: break; case NI_CLOSECANDIDATE: break; case NI_SELECTCANDIDATESTR: break; case NI_CHANGECANDIDATELIST: break; case NI_SETCANDIDATE_PAGESTART: break; case NI_SETCANDIDATE_PAGESIZE: break; case NI_CONTEXTUPDATED: switch (dwValue) { case IMC_SETCONVERSIONMODE: break; case IMC_SETSENTENCEMODE: break; case IMC_SETCANDIDATEPOS: break; case IMC_SETCOMPOSITIONFONT: break; case IMC_SETCOMPOSITIONWINDOW: break; case IMC_SETOPENSTATUS: bRet = TRUE; break; default: break; } break; case NI_COMPOSITIONSTR: switch (dwIndex) { case CPS_COMPLETE: break; case CPS_CONVERT: break; case CPS_REVERT: break; case CPS_CANCEL: break; default: break; } break; default: break; } return bRet; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数: // // , // // // // // 参 数: // // 作 用: 在输入串和结果串之间进行变换以便于可以重新转换。 // 比如 输入串 a 结果串 啊 a---啊相互转换 所以,在此函数中不应该产生任何相关的输入法编辑器消息。 // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// DWORD WINAPI ImeConversionList(HIMC hIMC, LPCTSTR lpSource, LPCANDIDATELIST lpCandList, DWORD dwBufLen, UINT uFlag) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数: // // , // // // // // 参 数: // // 作 用: 结束输入法编辑器的工作 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI ImeDestroy(UINT uForce) { return FALSE; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数: // // , // // // // // 参 数: // // 作 用: 应用程序可通过此函数,直接访问IME的特定功能,这些功能无法通过其他的IMM函数调用实现。 // // 目 的: 为了支持特定语种的函数或者IME的私有函数 // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// LRESULT WINAPI ImeEscape(HIMC hIMC, UINT uSubFunc, LPVOID lpData) { return FALSE; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数: // // , // // // // // 参 数: // // 作 用: 如果在某个窗口中打开了输入法编辑器,那么此接口函数会在应用程序窗口获得或失去输入焦点时被调用。 // 在此函数中,可以获得当前输入法上下文,并通知IME用户窗口组件,令其刷新显示 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI ImeSetActiveContext(HIMC hIMC, BOOL fFlag) { return TRUE; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数: // // , // // // // // 参 数: // // 作 用: 向输入法编辑器的词典里增加一个新词 // // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI ImeRegisterWord(LPCTSTR lpRead, DWORD dw, LPCTSTR lpStr) { return FALSE; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数: // // , // // // // // 参 数: // // 作 用: 把某个词从此输入法编辑器词典里去掉 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI ImeUnregisterWord(LPCTSTR lpRead, DWORD dw, LPCTSTR lpStr) { return FALSE; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数: // // , // // // // // 参 数: // // 作 用: 取得本输入法编辑器支持的词风格列表 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// UINT WINAPI ImeGetRegisterWordStyle(UINT nItem, LPSTYLEBUF lp) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数: // // , // // // // // 参 数: // // 作 用: 列出符合给定条件的所有字符串 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// UINT WINAPI ImeEnumRegisterWord(REGISTERWORDENUMPROC lpfn, LPCTSTR lpRead, DWORD dw, LPCTSTR lpStr, LPVOID lpData) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // // 函 数: // // , // // // // // 参 数: // // 作 用: 根据参数中给出的数据,修改写作字符串。此函数向编辑器发送一条WM_IME_COMPOSITION消息 // // 返回值: // // 备 注: // ///////////////////////////////////////////////////////////////////////////////// BOOL WINAPI ImeSetCompositionString(HIMC hIMC, DWORD dwIndex, LPCVOID lpComp, DWORD dwComp, LPCVOID lpRead, DWORD dwRead) { return FALSE; }
相关源码 在: http://download.csdn.net/detail/shuilan0066/3693695
参考资料: 1 http://www.pkucn.com/viewthread.php?tid=221029&highlight=%CA%E4%C8%EB%B7%A8%B1%E0%B3%CC%C2%FE%CC%B8
2 论文 <基于IMM-IME输入法接口的实现方法> 作者: 胡宇晓 马少平 夏莹