程序以北大路路通输入法作为分析版本,此版本源码比较清晰,容易入手
第一步: 对窗口进行注册
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输入法接口的实现方法> 作者: 胡宇晓 马少平 夏莹