深入解析 Windows CE 输入法架构与 IMM 接口开发
在 Windows CE 及桌面 Windows 操作系统中,输入法编辑器(IME)的用户界面通常由三个核心组件构成:状态窗口(Status Window,用于指示当前的输入模式和状态)、组合窗口(Composition Window,用于实时显示用户的击键和编码过程)以及候选窗口(Candidate Window,用于呈现与当前编码匹配的字词列表以供选择)。
IME 动态链接库的核心导出接口
IME 在系统底层表现为一个标准的动态链接库(DLL)。操作系统通过调用该 DLL 导出的一组约定函数来与输入法进行通信。一个合格的 IME 模块必须实现并导出这些特定签名的函数。以下是使用现代 C++ 风格重构的 IME 核心导出函数声明示例:
// IME 核心导出函数接口定义
extern "C" {
// 初始化输入法并获取基本属性
BOOL WINAPI ImeInquire(LPIMEINFO imeInfo, LPTSTR uiWndClass, DWORD systemInfo);
// 处理键盘输入事件,判断是否由 IME 拦截
BOOL WINAPI ImeProcessKey(HIMC inputContext, UINT virtualKey, LPARAM keyData, CONST LPBYTE keyState);
// 将虚拟键击键转换为具体的字符或编码消息
UINT WINAPI ImeToAsciiEx(UINT virtualKey, UINT scanCode, CONST LPBYTE keyState,
LPDWORD translationBuffer, UINT stateFlags, HIMC inputContext);
// 激活或停用当前的输入法上下文
BOOL WINAPI ImeSelect(HIMC inputContext, BOOL isSelected);
BOOL WINAPI ImeSetActiveContext(HIMC inputContext, BOOL isActive);
// 管理候选词和组合字符串的更新
BOOL WINAPI ImeSetCompositionString(HIMC inputContext, DWORD index, LPCVOID compData,
DWORD compLen, LPCVOID readData, DWORD readLen);
// 处理来自应用程序或系统的 IME 控制通知
BOOL WINAPI NotifyIME(HIMC inputContext, DWORD action, DWORD index, DWORD value);
// UI 窗口过程,处理输入法界面的绘制和交互
LRESULT WINAPI UIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI StatusWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI CompWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
LRESULT WINAPI CandWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
}
输入法管理器 (IMM) 与上下文控制
为了完成上述导出函数的功能,IME 内部需要深度依赖 Windows 提供的输入法管理器(Input Method Manager, IMM)API。IMM 不仅供 IME 内部使用,也允许宿主应用程序直接干预输入法的行为。
在 IMM 架构中,有两个关键概念:
- 输入上下文 (IMC, Input Method Context):代表与当前输入法关联的应用程序线程或窗口的上下文环境。
- 输入上下文组件 (IMCC, Input Context Component):IMC 内部的具体数据模块,如组合字符串缓冲区、候选词列表等,通常作为
INPUTCONTEXT结构的成员存在。
以下是 IME 开发中常用的 IMM 函数分类解析:
1. 消息生成与上下文锁定
当 IME 完成编码转换后,需要将结果传递给应用程序。这通过锁定 IMC、修改消息缓冲区并生成消息来实现。
// 锁定 IMC 以获取内部数据结构的指针
LPINPUTCONTEXT ctx = ImmLockIMC(hIMC);
if (ctx) {
// 修改 ctx->hMsgBuf 中的消息队列
// ...
// 通知系统将消息队列中的内容发送给应用程序
ImmGenerateMessage(hIMC);
// 解锁 IMC,必须与 Lock 成对出现
ImmUnlockIMC(hIMC);
}
2. 组件缓冲区管理
IMCC 用于动态管理内存。当需要存储候选词或组合字符串时,需分配和调整 IMCC 的大小。
// 创建一个新的 IMCC 缓冲区
HIMCC candidateBuffer = ImmCreateIMCC(sizeof(CANDIDATEINFO));
// 调整缓冲区大小以适应更多数据
candidateBuffer = ImmReSizeIMCC(candidateBuffer, newSize);
// 锁定以获取内存指针进行写入
LPCANDIDATEINFO pInfo = (LPCANDIDATEINFO)ImmLockIMCC(candidateBuffer);
// ... 写入数据 ...
ImmUnlockIMCC(candidateBuffer);
// 销毁不再需要的缓冲区
ImmDestroyIMCC(candidateBuffer);
关键数据结构重构
IME 与系统之间的数据交换依赖于一系列严谨的结构体。以下是对几个核心数据结构的现代化重构,采用了更清晰的命名规范:
// 输入法基本属性配置
struct ImeConfiguration {
uint32_t privateDataSize; // 自定义数据结构的字节数
uint32_t propertyFlags; // 键盘事件响应特性 (如 IME_PROP_UNICODE)
uint32_t conversionCaps; // 支持的功能特性 (如全角、软键盘)
uint32_t sentenceCaps; // 语句处理模式
uint32_t uiCapabilities; // 用户界面能力 (如旋转屏幕支持)
uint32_t scsCapabilities; // 设置组合字符串的能力
uint32_t selectCapabilities; // 切换输入法时的状态保留策略
};
// 候选词列表数据结构
struct CandidateListData {
uint32_t totalSize; // 结构体及后续数据的总字节数
uint32_t listStyle; // 列表数据的解析方式 (如 IME_CAND_READ)
uint32_t itemCount; // 当前列表中的候选词总数
uint32_t selectedIndex; // 当前高亮选中的候选词索引
uint32_t pageStartIndex; // 当前页面显示的起始索引 (用于翻页)
uint32_t pageSize; // 每页显示的候选词数量
uint32_t itemOffsets[]; // 各个候选词字符串相对于结构体起始地址的偏移量
};
// 输入上下文核心结构 (简化版)
struct InputContextData {
HWND ownerWindow; // 拥有此 IMC 的窗口句柄
BOOL isOpen; // 输入法是否处于开启状态
POINT statusWindowPos; // 状态窗口的屏幕坐标
uint32_t conversionMode; // 当前转换模式 (如中文、英文、全角)
uint32_t sentenceMode; // 标点符号模式
COMPOSITIONFORM compForm; // 组合窗口的位置和样式设置
CANDIDATEFORM candForm[4]; // 候选窗口的位置设置 (支持多个)
HIMCC compStringHandle; // 组合字符串 IMCC 句柄
HIMCC candidateInfoHandle; // 候选词信息 IMCC 句柄
HIMCC messageBufferHandle; // 待发送消息队列 IMCC 句柄
uint32_t initializedFlags; // 标识哪些字段已被系统初始化
};
UI 窗口的消息处理机制
IME 的 UI 窗口(由 UIWndProc 处理)不直接接收常规的鼠标或键盘消息,而是通过系统派发的 WM_IME_* 系列消息来驱动界面的更新与状态切换。
LRESULT CALLBACK ImeUIWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_IME_SETCONTEXT: {
// 系统通知 IME 激活或休眠特定的 UI 组件
BOOL isActive = (BOOL)wParam;
DWORD uiComponents = (DWORD)lParam;
if (isActive) {
if (uiComponents & ISC_SHOWUICOMPOSITIONWINDOW) {
ShowCompositionWindow(hWnd, TRUE);
}
if (uiComponents & ISC_SHOWUICANDIDATEWINDOW) {
ShowCandidateWindow(hWnd, TRUE);
}
} else {
HideAllImeWindows(hWnd);
}
return 0;
}
case WM_IME_NOTIFY: {
// 处理输入法状态变更的子消息
UINT subMsg = (UINT)wParam;
switch (subMsg) {
case IMN_OPENSTATUSWINDOW:
CreateAndShowStatusWindow(hWnd);
break;
case IMN_CLOSESTATUSWINDOW:
DestroyStatusWindow(hWnd);
break;
case IMN_OPENCANDIDATE:
InitializeCandidateList(hWnd, (DWORD)lParam);
break;
case IMN_CHANGECANDIDATE:
UpdateCandidateListUI(hWnd, (DWORD)lParam);
break;
case IMN_CLOSECANDIDATE:
DestroyCandidateWindow(hWnd);
break;
case IMN_SETCOMPOSITIONWINDOW:
RepositionCompositionWindow(hWnd);
break;
}
return 0;
}
case WM_IME_CONTROL: {
// 响应应用程序对 IME 状态的查询或设置
UINT controlCmd = (UINT)wParam;
if (controlCmd == IMC_GETCANDIDATEPOS) {
CANDIDATEFORM* candForm = (CANDIDATEFORM*)lParam;
GetCandidateWindowPosition(candForm);
return 0; // 成功返回 0
}
return 1; // 不支持的命令返回非零
}
case WM_IME_COMPOSITION: {
// 组合字符串发生变化时触发
// 可通过 ImmGetCompositionString 获取最新的拼音或编码
UpdateCompositionStringUI(hWnd, lParam);
return 0;
}
case WM_IME_STARTCOMPOSITION:
ShowCompositionWindow(hWnd, TRUE);
return 0;
case WM_IME_ENDCOMPOSITION:
HideCompositionWindow(hWnd);
ClearCompositionBuffer();
return 0;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}