输入法 编程分析

 

 

程序以北大路路通输入法作为分析版本,此版本源码比较清晰,容易入手

 

第一步: 对窗口进行注册

 

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输入法接口的实现方法>  作者: 胡宇晓 马少平 夏莹

 

 

 

 

 

 

 

 

 

 

 

 

 

阅读更多

扫码向博主提问

shuilan0066

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • c++
  • mfc
  • python
  • duilib
去开通我的Chat快问
个人分类: 输入法
上一篇输入法工作原理
下一篇vs2005断点无效
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭