2020年8月

作者不详,防止没有数据,备份一份
Windows 95输入法编辑器(IME)

原著:Microsoft
翻译:TBsoft Software Studio

一、关于Windows 95混合语言IME

在Windows 95中,IME是一个动态链接库(DLL),与Windows 3.1远东版本IME不同
的是,每一个运行的IME相当于混合语言键盘布局中的一种。与Windows 3.1 IME相
比较,Windows 95混合语言IME提供下列增强功能:
●运行时相当于混合语言环境的一个部件
●为每一个应用程序任务提供多重输入上下文
●为每一个应用程序线程提供一个活动的IME
●通过应用程序消息循环给应用程序提供信息(消息顺序不能改变)
●为无IME支持应用程序和部分IME支持应用程序提供有力的支持
要得到全部的增强功能,应用程序需要支持Windows 95 IME应用程序I/F。
本文档描述了Windows 95 IME体系结构的应用程序I/F。

1、IME的结构

Windows 95 IME必须提供两个部件:IME转换接口和IME用户接口。IME转换接口由
一组IME模块引出函数提供,这些函数被IMM(输入法管理器——译者注)调用。
IME用户接口由一组窗口提供,这些窗口接收消息并提供IME的用户界面。

2、IME支持应用程序(IME感知应用程序——译者注)

应用程序有下列类型:
●无IME支持应用程序:这种应用程序不控制IME,然而,如果应用程序接受DBCS字
符,用户可以通过IME在应用程序中输入DBCS字符。
●部分IME支持应用程序:这种应用程序只控制不同的IME上下文,例如打开和关闭
IME、写作窗口等等,但是不重新显示任何IME用户界面。
●完全IME支持应用程序:这种应用程序负责管理通过IME显示给应用程序的任何信
息。
在Windows 95中,一个无IME支持应用程序有一个缺省的IME窗口和一个缺省的输入
上下文。
部分IME支持应用程序使用预定义的“IME”类创建自己的IME窗口,可以管理或者
不管理自己的输入上下文。
完全IME支持应用程序自己管理输入上下文,显示输入上下文给出的任何需要的信
息,不使用IME窗口。

二、IME用户界面

IME用户界面包括IME窗口、用户界面(UI)窗口以及UI窗口的部件。

1、特征

IME类是实现IME用户界面部分的预定义全局窗口类。“IME”类与预定义的公共控
制窗口类有许多相同的特点,IME窗口实例与静态控制一样通过CreateWindowEx函
数创建,IME类窗口自己不响应用户输入,取而代之的是接收不同类型的控制消息
实现全部IME用户接口。应用程序可以使用IME类创建自己的IME窗口,还可以使用
ImmGetDefaultIMEWnd函数获取缺省IME窗口。创建自己的IME窗口或者使用缺省
IME窗口的应用程序被称为IME支持应用程序,具有以下优点(与对应的Windows
3.1应用程序比较):
●包括候选字列表窗口(候选窗口),每一个应用程序可以有自己的用户界面窗口
实例,使得用户可以在任何输入过程的中途停止并切换到另一个应用程序。在
Windows 3.1日文版本中,用户切换到另一个应用程序是必须放弃当前输入过程。

●因为IME用户界面窗口包括应用程序窗口句柄,IME用户界面窗口可以为应用程序
提供缺省行为。例如当应用程序移动时IME用户界面窗口自动移动,自动跟随窗口
中的插入符号位置,为每一个应用程序标示模式等等。
即使系统仅仅只提供一个IME类,IME窗口仍然有两种类型。一种类型是系统为无
IME支持应用程序创建的IME窗口,DefWindowProc函数为该窗口处理消息,
DefWindowProc函数的IME用户接口被线程的所有无IME支持窗口共享,在文档中,
这种窗口称为缺省IME窗口。另一种类型是IME支持应用程序创建的IME窗口,在文
档中,IME支持应用程序创建的IME窗口称作应用程序IME窗口。

2、缺省和应用程序IME窗口

当线程初始化时系统创建缺省IME窗口,这就是说,线程自动获取缺省IME窗口。缺
省IME窗口为无IME支持应用程序提供IME用户界面,当IME或者IMM生成一个IME消息
(WM_IME_*)时,无IME支持应用程序传递该消息到DefWindowProc函数,
DefWindowProc函数发送需要的消息到为应用程序提供缺省IME用户界面的缺省IME
窗口。IME支持应用程序当不从IME获取消息时也可以使用缺省IME窗口,需要时可
以使用自身的IME窗口。

3、IME类

IME类是Windows 95远东版本预定义的窗口类,就像Edit是预定义的窗口类一样。
预定义的IME类实现全部的IME用户接口,处理所有来自IME和包含IMM函数的应用程
序的消息,应用程序使用IME类创建自己的IME窗口。系统IME类不能被被任何IME替
换。
窗口过程与IME类通过WM_IME_SELECT消息交互,该消息包括新选中的IME的键盘布
局,IME类使用键盘布局查找到每一个IME定义的类名。使用类名,IME类为当前活
动的IME创建IME用户界面窗口。

4、IME UI类

每一个IME必须向系统注册自己的用户界面(UI)类,UI类提供IME相关功能。当
IME附加在进程上时IME注册自己的UI类,这就是说,当DLLEntry函数被调用
DLL_PROCESS_ATTACH功能时,IME必须在对ImeInquire函数的调用过程中指定UI类
名。UI类应该使用CS_IME窗口风格注册以使得每一个应用程序都可以使用UI类。
UI类名(包括空终结符)可以使用16位的TCHAR字符,这个限制可能延续到
Windows的未来版本。
当注册一个UI类时,应该指定8个字节的窗口附加数据(这就是说,设置
WNDCLASSEX类的cbWndExtra成员的值为2*sizeof(LONG)),系统使用该窗口附加数
据。
IME可以在为应用程序执行任务时注册任何类和创建任何窗口。
下面的实例显示了怎样注册IME窗口类:

BOOL WINAPI DLLEntry (
HINSTANCE hInstDLL,
DWORD dwFunction,
LPVOID lpNot)
{
switch (dwFunction) {
case DLL_PROCESS_ATTACH:
hInst= hInstDLL;

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;

wc.style = CS_MYCLASSFLAG | CS_IME;
wc.lpfnWndProc = MyCompStringWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = cbMyWndExtra;
wc.hInstance = hInst;

wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = NULL;
wc.lpszMenuName = (LPSTR) NULL;
wc.lpszClassName = (LPSTR) szUICompStringClassName;
wc.hbrBackground = NULL;

if(!RegisterClass((LPWNDCLASS)&wc))
return FALSE;

break;

case DLL_PROCESS_DETACH:
UnregisterClass(szUIClassName,hInst);
UnregisterClass(szUICompStringClassName,hInst);
break;
}
return TRUE;
}

5、UI窗口

IME类对应的IME窗口被应用程序或者系统创建,当IME窗口被创建时,IME自身提供
的UI窗口被创建并被IME窗口所拥有。每一个UI窗口有一个当前的输入上下文,当
UI窗口接收到IME消息(WM_IME_*)时,可以通过调用GetWindowLong函数和指定
IMMGWL_IMC索引值查找到输入上下文,UI窗口可以根据输入上下文处理消息,UI窗
口可以在除响应WM_CREATE消息以外的任何时间查找到输入上下文。
IME不允许改变UI窗口的窗口附加数据,如果UI窗口的某个实例需要窗口附加数据
,可以使用IMMGWL_PRIVATE参数值调用SetWindowLong和GetWindowLong函数,
IMMGWL_PRIVATE参数值提供为UI窗口的某个实例存取附加数据中LONG类型值的能力
,如果需要大于LONG类型值的附加数据,可以保存一个内存块的句柄到
IMMGWL_PRIVATE域。
UI窗口过程可以使用DefWindowProc函数,但是UI窗口不允许传递IME消息给
DefWindowProc函数,即使某个IME消息没有被处理,UI窗口也不允许传递该消息给
DefWindowProc函数。

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);

}
}

UI窗口必须在当前选定的输入上下文中执行动作,当一个窗口被激活时,UI窗口接
收到提供当前输入上下文的消息,此后,UI窗口运行在当前选中的输入上下文上。
输入上下文必须包括UI窗口显示写作窗口、状态窗口等需要的所有信息。
UI窗口要求输入上下文,但是窗口不必自己更新输入上下文。当UI窗口需要更新输
入上下文时,应该调用IMM函数,因为输入上下文由IMM函数管理,当输入上下文更
新时,IMM和IME接收到通知消息。
例如,有时UI窗口当鼠标单击时需要改变输入上下文的转换模式,为了设置转换模
式,UI窗口调用ImmSetConversionMode函数,该函数为NotifyIME生成一个通知消
息并发送WM_IME_NOTIFY消息到UI窗口,如果UI窗口改变转换模式的显示,UI窗口
会等待处理WM_IME_NOTIFY消息。

6、UI窗口的部件

UI窗口可以根据输入上下文注册和显示写作窗口和状态窗口,UI窗口的部件类的窗
口风格必须包括CS_IME。UI窗口的一个窗口实例从当前输入上下文接收例如写作字
符串、字体、位置等信息,当应用程序的一个窗口获得焦点时,系统获取该窗口自
己的输入上下文并将当前输入上下文传递给UI窗口,系统发送WM_IME_SETCONTEXT
消息和输入上下文的句柄给应用程序,应用程序传递该消息给UI窗口。如果当前输
入上下文被更新,UI窗口应该重新绘制写作窗口,无论何时输入上下文改变,UI窗
口都应该显示正确的写作窗口,可以保证IME的状态。
UI窗口可以创建子窗口或者弹出式窗口显示状态、写作字符串或者候选字列表,这
些窗口必须是UI窗口的附属窗口,而且必须创建为不可接收输入(Disable)窗口
,任何IME创建的窗口都不应该获取焦点。

三、输入上下文

1、缺省输入上下文

缺省情况下系统给每个线程一个输入上下文,该输入上下文被线程的所有无IME支
持窗口共享。

2、输入上下文与窗口的交互

应用程序的一个窗口可以使用窗口句柄与输入上下文交互以维护任何IME状态,包
括中间写作字符串。一旦应用程序使得输入上下文与窗口句柄交互,无论何时窗口
被激活,系统自动选中输入上下文。使用这个特点,应用程序可以轻松地完成
Windows 3.1下必须的复杂切换处理。

3、使用输入上下文

当应用程序或者系统创建新的输入上下文时,系统准备新的输入上下文,新的输入
上下文已经包括IMCC,这个IMC的部件由hCompStr、hCandInfo、hGuideLine、
hPrivate和hMsgBuf组成。IME基本上不需要创建输入上下文和输入上下文的部件,
不过IME可以改变它们的大小,可以通过锁定它们查找到部件的指针。

⑴存取HIMC

为了存取输入上下文,IME必须调用ImmLockIMC函数以查找到输入上下文的指针,
ImmLockIMC函数给IMC增加imm锁定计数,ImmUnlockIMC函数减少之。

⑵存取HIMCC

为了存取输入上下文中的一个部件,IME必须调用ImmLockIMCC函数获取IMCC的指针
,ImmLockIMCC函数给IMCC增加imm锁定计数,ImmUnlockIMCC函数减少之,
ImmReSizeIMCC函数可以修改IMCC的大小以指定新的大小。
某些情况下,IME可能需要自己创建输入上下文的一个部件,这种情况下,IME可以
调用ImmCreateIMCC函数获取IMCC的句柄,这个IMCC可以是INPUTCONTEXT结构的成
员(hCompStr、hCandInfo、hGuideLine、hPrivate或者hMsgBuf)。
ImmDestroyIMCC清除输入上下文的一个部件。

⑶怎样使用输入上下文

下面的实例显示了怎样使用输入上下文

LPINPUTCONTEXT lpIMC;
LPCOMOSITIONSTRING lpCompStr;
HIMCC hMyCompStr;

if (hIMC) { // It is not NULL context.
lpIMC = ImmLockIMC(hIMC);

if (!lpIMC) {
MyError( "Can not lock hIMC");
return FALSE;
}

// Use lpIMC->hCompStr.
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);
}

四、生成消息

IME需要生成IME消息。当IME开始转换时,IME必须生成WM_IME_STARTCOMPOSITION
消息,如果IME改变了写作字符串,IME必须生成WM_IME_COMPOSITION消息,IME引
发的事件导致生成消息给与输入上下文进行交互的窗口。IME基本上使用
ImeToAsciiEx函数参数提供的lpdwTransKey缓冲区生成消息,当ImeToAsciiEx函数
被调用时IME存储消息到lpdwTransKey缓冲区中,不过即使ImeToAsciiEx函数没有
被调用,IME也可以生成消息给使用输入上下文的消息缓冲区与输入上下文交互的
窗口。输入上下文有一个内存块的句柄作为消息缓冲区,IME存储消息到被消息缓
冲区句柄提供的内存块中,以后IME调用ImmGenerateMessage函数,
ImmGenerateMessage函数发送保存在消息缓冲区中的消息到适当的窗口。

1、在ImeToAsciiEx函数中使用消息缓冲区

下面的实例显示了怎样通过传递缓冲区到ImeToAsciiEx函数生成消息:

UINT ImeToAsciiEx(uVirKey, uScanCode, lpbKeyState, lpdwTransBuf,
fuState , hIMC )
{
DWORD dwMyNumMsg = 0;

.
.
.

// Set the messages that the IME needs to generate.
*lpdwTransBuf++ = (DWORD) msg;
*lpdwTransBuf++ = (DWORD) wParam;
*lpdwTransBuf++ = (DWORD) lParam;

// Count the number of the messages that the IME needs to generate.

dwMyNumMsg++;
.
.
.

return dwMyNumMsg;
}

系统提供lpdwTransBuf参数指定的缓冲区,IMEToAsciiEx函数可以一次存储所有的
消息到该缓冲区中,缓冲区的第一个双字给出存储在缓冲区中的消息个数。如果
ImeToAsciiEx函数需要生成比这个给定的个数更多的消息,函数可以存储所有的消
息到输入上下文的hMsgBuf域中,然后函数ImeToAsciiEx返回消息个数。当
ImeToAsciiEx函数的返回值大于lpdwTransBuf中指定的值时,系统不从
lpdwTransBuf中取出消息,系统查找作为ImeToAsciiEx函数参数传递的输入上下文
中的hMsgBuf域。

2、使用消息缓冲区

下面的实例显示了怎样使用消息缓冲区:

MyGenerateMesage(HIMC hIMC, UINT msg, WPARAM wParam, LPARAMlParam)
{
LPINPUTCONTEXT lpIMC;
HGLOBAL hTemp;
LPDWORD lpdwMsgBuf;
DWORD dwMyNumMsg = 1;

// Lock the input context.
lpIMC = ImmLockIMC(hIMC);
if (!lpIMC)
// Error!

// re-allocate the memory bloack for the message buffer.
hTemp = ImmReSizeIMCC(lpIMC->hMsgBuf,
(lpIMC->dwNumMsgBuf + dwMyNumMsg) * sizeof(DWORD) * 3);
if (!hTemp)
// Error!

lpIMC->hMsgBuf = hTemp;

// Lock the memory for the message buffer.
lpdwMsgBuf = ImmLockIMCC(lpIMC->hMsgBuf);
if (!lpdwMsgBuf)
// Error!

lpdwNumMsgBuf += 3 * lpIMC->dwNumMsgBuf.
// Set the number of the messages.
lpIMC->dwNumMsgBuf += dwMyNumMsg;

// Set the messages that the IME needs to generate.
*lpdwMsgBuf++ = (DWORD) msg;
*lpdwMsgBuf++ = (DWORD) wParam;
*lpdwMsgBuf++ = (DWORD) lParam;

// Unlock the memory for the message buffer and the input context.

ImmUnlockIMCC(lpIMC->hMsgBuf);
ImmLockIMC(hIMC);

// Call ImmGenerateMessage function.
ImmGenerateMessage(hIMC);
}

3、WM_IME_COMPOSITION消息

当IME生成WM_IME_COMPOSITION消息时,IME指定lParam参数为GCS位。GCS位的意义
是COMPOSITIONSTRING结构中的有效成员,即使IME没有更新,成员目前仍然有效,
IME也会设置GCS位。

为IME定义服务

当IME生成WM_IME_COMPOSITION消息时,IME可能会立刻改变字符串、属性以及子句
信息。IME使用下列定义:

GCS_COMP
GCS_COMPREAD
GCS_RESULT
GCS_RESULTREAD

五、关于ImeSetCompositionString函数

1、ImeSetCompositionString函数能力

如果IME没有ImeSetCompositionString函数能力,IME将不能在IMEINFO结构中指定
任何SCS能力。如果IME可以处理ImeSetCompositionString函数,IME设置
SCS_COMPSTR位。如果IME可以通过写作字符串生成解释(本文中的“解释”是单词
“reading”的直译,真正意义可能是“原始输入的”,例如输入的汉语拼音字母
字符串,下同)字符串,IME可以设置SCS_CAP_MAKEREAD位。
如果IME有SCS_CAP_COMPSTR能力,ImeSetCompositionString函数将被调用,IME从
应用程序获取新的写作字符串并生成WM_IME_COMPOSITION消息。
如果IME有SCS_CAP_MAKEREAD能力,IME可以通过写作字符串建立解释字符串。

2、关于SCS_SETSTR

如果ImeSetCompositionString函数的dwIndex参数值为SCS_SETSTR,IME可以清除
hIMC中的COMPOSITIONSTR结构中所有的域。
如果IME需要,IME可以更新候选信息并生成候选消息IMN_OPENCANDIDATE、
IMN_CHANGECANDIDATE或者IMN_CLOSECANDIDATE。
如果ImeSetCompositionString函数的lpRead参数有效,IME应该通过lpRead参数中
的解释字符串建立写作字符串,另外IME为新的写作字符串和lpRead参数中的解释
字符串建立属性和子句信息,IME生成lParam参数为(GCS_COMP|GCS_COMPREAD)的
WM_IME_COMPOSITION消息。有时IME需要自动确定建立上述信息,这种情况下,
IME可以生成lParam参数以(GCS_RESULT|GCS_RESULTREAD)代替GCS_COMPxxx的消
息。
如果ImeSetCompositionString函数的lpComp参数有效,IME应该通过lpComp参数中
的写作字符串建立写作属性和子句信息,IME生成lParam参数为GCS_COMP的
WM_IME_COMPOSITON消息。如果IME有SCS_CAP_MAKEREAD能力,IME应该同时建立解
释字符串,IME生成lParam参数为(GCS_COMP|GCS_COMPREAD)的
WM_IME_COMPOSITION消息。有时IME需要自动确定建立上述信息,这种情况下,
IME可以生成lParam参数以(GCS_RESULT|GCS_RESULTREAD)代替GCS_COMPxxx的消
息。
如果lpRead参数和lpComp参数同时有效,IME应该建立写作字符串和解释字符串,
这时IME不需要完全按照lpRead参数和lpComp参数。如果IME不能建立应用程序指定
的lpRead参数和lpComp参数之间的关系,IME应该修正写作字符串,IME为新的写作
字符串和lpRead参数指定的解释字符串建立属性和子句信息,IME生成lParam参数
为(GCS_COMP|GCS_COMPREAD)的WM_IME_COMPOSITION消息。有时IME需要自动完成
建立上述信息,这种情况下,IME可以生成lParam参数以(
GCS_RESULT|GCS_RESULTREAD)代替GCS_COMPxxx的消息。

3、关于SCS_CHANGEATTR

SCS_CHANGEATTR只影响属性信息,IME不应该更新写作字符串、写作字符串的子句
信息、写作字符串的解释以及写作字符串的解释子句信息。
首先IME检查新的属性并判断新的属性是否可用,然后IME设置属性到hIMC中的
COMPOSITIONSTRING结构中,最后IME生成WM_IME_COMPOSITION消息。
如果需要,IME可以更新候选信息并生成候选消息IMN_OPENCANDIDATE、
IMN_CHANGECANDIDATA、IMN_CLOSECANDIDATE。
IME不能确定写作字符串。
如果ImeSetCompositionString函数的lpRead参数有效,IME使用lpRead参数中的新
属性。IME也应该为当前写作字符串建立写作字符串的新属性,这时子句信息不被
修改。
写作字符串、属性、子句信息、解释字符串、解释属性和解释子句信息必须有效。
IME生成lParam参数为(GCS_COMP|GCS_COMPREAD)的WM_IME_COMPOSITION消息,如
果IME不能接受lpComp参数中的新属性,IME不需要生成任何消息并返回FALSE。
如果ImeSetCompositionString函数的lpComp参数有效,IME使用lpComp参数中的新
属性,这时子句信息不被修改。
如果IME有SCS_CAP_MAKEREAD能力,并且解释字符串有效,IME应该为当前写作字符
串的解释建立写作字符串的解释的新属性。
如果lpRead参数和lpComp参数同时有效,并且如果IME能够接受新的信息,IME设置
新的信息到hIMC中的COMPOSITION结构中并生成lParam参数为(
GCS_COMP|GCS_COMPREAD)的WM_IME_COMPOSITION消息。

4、关于SCS_CHANGECLAUSE

SCS_CHANGECLAUSE影响写作字符串和写作字符串的解释的字符串和属性。
如果需要,IME可以更新候选信息并生成候选消息IMN_OPENCANDIDATE、
IMN_CHANGECANDIDATA、IMN_CLOSECANDIDATE。
IME不能确定写作字符串。
如果ImeSetCompositionString函数的lpRead参数有效,IME使用lpRead参数中的解
释子句信息。IME必须修正写作字符串的解释的属性,IME可以更新写作字符串、属
性和写作字符串的子句信息,IME生成lParam参数为(GCS_COMP|GCS_COMPREAD)的
WM_IME_COMPOSITION消息。
如果ImeSetCompositionString函数的lpComp参数有效,IME使用新的子句信息。
IME必须修正写作字符串和写作字符串的属性,IME可以更新解释属性和解释的子句
信息,IME生成lParam参数为(GCS_COMP|GCS_COMPREAD)的WM_IME_COMPOSITION消
息。
如果lpRead参数和lpComp参数同时有效,并且如果IME能够接受新的信息,IME设置
新的信息到hIMC中的COMPOSITION结构中并生成lParam参数为(
GCS_COMP|GCS_COMPREAD)的WM_IME_COMPOSITION消息。

六、软键盘

1、关于软键盘

一些IME有特殊的解释字符,例如一个IME可能使用注音符号作为解释字符(这里指
台湾中文版Windows 95,即CWin95中的注音符号,PWin95中可能指汉语拼音字母或
者音调符号——译者注),另一个IME使用了一些字根符号(原文单词是“
radials”,但实际可能是“radicals”——译者注)作为解释字符,IME可以提供
一个软键盘显示这些特殊解释字符使得用户不必逐键记忆解释字符。
IME需要根据不同的变换状态改变键表示的解释字符,使用软键盘可以通知用户键
的改变。在选择候选字时,IME可以只显示那些选择键给用户。

2、使用软键盘

IME可能需要为软键盘创建一个更好的用户界面,或者可能需要系统预定义的软键
盘,如果IME需要使用系统预定义的软键盘,IME需要在调用ImeInquire函数时将
IMEINFO结构的fdwUICaps成员指定为UI_CAP_SOFTKBD。
IME可以调用ImmCreateSoftKeyboard函数为软键盘创建窗口,还可以调用
ImmShowSoftKeyboard函数显示或者隐藏软键盘。软键盘窗口是UI窗口的一个组件
,所以软键盘窗口应该附属于UI窗口。
IME可能需要决定是否在无论何时焦点移走的情况下删除窗口,软键盘可能占有一
些系统资源(可能需要释放——译者注)
软键盘有不同的类型,一种类型可能是为特定的国家或者特定的目的设计的。为每
一种类型的软键盘改变解释字符的方式可能不同,有两种改变解释字符的方式:使
用IMC_SETSOFKBDSUBTYPE或者IMC_SETSOFKBDDATA。不同类型的软键盘有不同的窗
口过程并存在不同的用户界面给用户。

七、IME接口

在Windows 95中,IME与设备驱动程序一样是动态链接库(DLL),输入法管理器(
IMM)应该处理所有安装的IME。因为IME在运行时是可以改变的,不需要重新启动
系统,IMM有一个结构用于维护每一个IME的所有入口点。
IME函数列表是所有远东版本Windows 95公共IME功能函数的描述,这些函数不应该
在应用程序中直接调用。

UI窗口中的IMM函数

下面是可以在UI窗口中调用的IMM函数:

ImmGetCompositionWindow
ImmSetCompositionWindow
ImmGetCandidateWindow
ImmSetCandidateWindow
ImmGetCompositionString
ImmSetCompositionString
ImmGetCompositionFont
ImmSetCompositionFont
ImmGetNumCandidateList
ImmGetCandidateList
ImmGetGuideLine
ImmGetConversionStatus
ImmGetConversionList
ImmGetOpenStatus
ImmSetConversionStatus
ImmSetOpenStatus
ImmNotifyIME
ImmCreateSoftKeyboard
ImmDestroySoftKeyboard
ImmShowSoftKeyboard

为了防止原数据丢失,得此做一个备份
原文地址:http://blog.sina.com.cn/s/blog_5729b5010100q5xn.html 作者:糊涂茶

这是我翻译的windows2000 DDK中IME输入法编程的英文资料(前10页),没有翻译完,不再翻译了。这个资料的很多内容和网上发布的MSDN中windows95下的IME输入法编程资料相同,原版英文资料后面的内容更多的是程序上的东西,不是原理上的,只有程序员才需要,一般也能看懂,所以就不译了。

Win32多语言IME概述--------为IME开发者提供。

版本1.41 1999.04.01

这个文档介绍了如何为win95/98/nt/2000开发一个IME程序的基本知识。它也是一个IME开发者可以使用的win32 下的多语言IME- API参考资料,

下面是讲述的主题:

Overview:概述

IME User Interface --IME用户接口

IME Input Context --IME输入上下文

Generating Messages—产生消息

ImeSetCompositionString—ime程序设置编码字符串

Soft Keyboard—软键盘

Reconversion—撤销和复原能力支持

IME Menu Functions—IME菜单函数

IME Help File—IME帮助文件

Windows NT/2000 Issues—winNT/2000专门议题

IME File Format and Data Structures --IME文件格式和数据结构

一、Overview:概述

从win95/winNT 4.0开始,IME程序作为一个动态链接库(DLL)被提供使用,不同于远东版本的win3.1中的IME程序。每一个IME作为一个多语言的键盘方案使用。为了区别于win3.1,新的win32多语言输入法管理器(IMM)和输入法(IME)在操作系统的构造体系中加入了下面的高级特性:

· 作为一个支持多语言的环境部件运行。

· 为每一个应用程序的任务提供多重输入上下文。(一个任务可以有多个线程,IME是按线程分配的,有多少个线程就可以有多少个IME,输入上下文是按任务提供的,为了支持任务中的多个线程和IME,输入上下文必须可以提供多重的服务)

· 为每一个活动的应用程序线程保持一个活动的IME

· 通过消息循环给应用程序发送消息(消息运行秩序没有被破坏)

· 为IME感知程序和不感知程序都提供强大的IME支持。

为了充分完整利用这些高级特性,每一个应用程序都需要支持新的win32 IMM/IME应用程序接口。

为了最大程度上兼容现有的win95/NT4.0中的IME程序,win98/win2000中的IME在设计上没有改变过多过深。无论如何,为了提供更好的系统综合性能和支持更多的智能IME程序,新的特性已经被增加进来。

注意:

IME开发者在DDK中必须使用immdev.h,它是imm.h或者其它开发工具的超集
Win98 / 2000中的新 IMM/IME特性

在win98/2000中的IMM/IME的架构体系中包含了win95/winNT4.0的设计。无论如何,为了支持智能IME开发和IME的智能感知(integration),IMM/IME中的一些东西已经被改变。

新的IME函数允许应用程序和IMM/IME直接通信,这些函数包括:

ImmAssociateContextEx

ImmDisableIME

ImmGetImeMenuItems

新的函数允许IME和(IMM、应用程序)直接通信,这些函数包括:

ImmRequestMessage

ImeGetImeMenuItems

撤销和复原能力支持

这是一个新的IME特性,允许你撤销和复原已经被插入到应用程序文件中的字符串,这个函数将帮助智能IME程序获得更多的关于转换结果的信息,并且改善转换的精度和改进程序的执行。这个特性需要应用程序和IME二者协作完成。

增加了IME菜单项到系统提供的输入上下文菜单图标中,这个新特性提供了一个IME把它自己的菜单放置到系统提供的输入上下文菜单条和应用程序中的功能。

IME的新旗位标志

下面的新位支持新的模式:

IME_CMODE_FIXED

IME_SMODE_CONVERSATION

IME_PROP_COMPLETE_ON_UNSELECT

改进应用程序对IME的编辑能力的控制。

通过两个新消息:EM_SETIMESTATUS and EM_GETIMESTATUS,应用程序可以管理IME的状态,达到控制IME编辑的目的。

改变IME的图标和揭示消息

通过三个新消息: INDICM_SETIMEICON, INDICM_SETIMETOOLTIPS, INDICM_REMOVEDEFAULTMENUITEMS,IME能在系统的任务栏中改变系统的图标和提示消息。

两个新的IMR消息

IMR_QUERYCHARPOSITION and IMR_DOCUMENTFEED 帮助IME和应用程序得到位置和文档消息。

· 对64位程序和消息的适应

两个新消息(TRANSMSG and TRANSMSGLIST) 被增加进IMM32.它们被 输入法上下文INPUTCONTEXT 和ImeToAsciiEx函数使用,接收IME转换消息 .

· IME_PROP_ACCEPT_WIDE_VKEY

这个新属性被增加进win2000,所以IME可以操作发送到输入法API函数中的带有Unicode的字符。ImeProcessKey 和ImeToAsciiEx 两个API函数也被修改为可以操作带有Unicode的字符。这个夹带的Unicode字符能被应用程序和手写版程序通过在输入队列中放入Unicode字符串使用。
Win32 IME 构成

一个新的Win32 IME必须提供两个组件。一个是IME转换接口,一个是IME用户接口。IME转换接口作为一组IME模块中的出口函数被提供,这些函数被IMM调用。IME用户接口以windows窗口的形式被提供,这些窗口接受消息并且提供IME的用户接口。
IME 感知应用程序

现在新Win32 IME架构的一个主要的高级特性是它在应用程序和IME之间提供了更好的逻辑联系。后面有一个例子说明一个应用程序是如何和IME关联在一起的。

不感知IME的应用程序

这种应用程序从来不打算控制IME,无论如何,就象它们访问DBCS字符一样,用户可以使用IME输入任何DBCS字符给应用程序。

IME 半感知程序

这种应用程序的特点是会控制IME的多种输入上下文和编码窗口,比如打开或者关闭它们,但是它不会显示IME的任何用户接口。

IME 完全感知程序

这种应用程序的特点是要完全响应用户通过IME给出的任何显示信息。

在win95/NT 4.0或者更高的版本中,操作系统将为IME不感知应用程序也提供IME支持,操作系统将为它提供一个默认IME窗口和默认输入上下文。

一个半感知的应用程序将建立自己的IME窗口,也会调用一个应用程序IME窗口,使用预先定义的系统IME类,它可以操作或者不操作属于它自己的由操作系统提供给它的输入上下文

一个完全的IME感知程序将自己操作输入上下文并且把不通过使用IME窗口的输入上下文给出的任何信息显示出来。
IME 用户接口

IME用户接口包括IME窗口、UI窗口、UI窗口的组件。
特性

每一个IME类是一个预先定义的全局类,它们输出一些IME中的用户接口组件,每一个IME类的标准特征是同样的,被其它程序共同控制。它的windows实例能通过使用CreateWindowEx函数建立。作为静态控制,IME类窗口自己不能响应用户的输入,但是可以接收各种各样的控制信息去实现完整的IME用户接口行为。每一个应用程序可以建立它自己的IME窗口,通过使用IME类或者通过调用ImmGetDefaultIMEWnd函数取得默认的IME窗口。区别于win3.1,每一个要控制IME的应用程序和这些窗口操作(一个完全感知IME的应用程序),现在能实现下面的优良功能:

新的IME包含了候选列表窗口。每一个应用程序能有它自己的UI窗口实例,所以用户可以在使用IME的任何操作过程中停下来去选择另一个应用程序。在win3.1中的日文输入法中,用户必须首先退出之前的应用程序,然后去选择另一个应用程序。

自从IME用户接口窗口被包含进应用程序的窗口句柄以来,IME可以为应用程序提供一些默认功能。例如:这个功能可以为IME窗口提供自动响应、自动跟踪窗口建立的位置、为每一个应用程序提供规定格式的指示消息。

尽管系统只提供了一种IME类,但是有两种类型的IME窗口。一个是通过系统提供的DefWindowProc函数为IME不感知程序建立的窗口。DefWindowProc函数建立的IME用户接口被所有的IME不感知程序的线程窗口共享,并且被在这个程序文件中的默认IME窗口调用。另一种窗口通过IME感知应用程序建立,并且被应用程序IME窗口调用。
默认的应用程序IME窗口

在线程初始化的时候,系统建立一个默认的IME窗口,它自动的被提供给每一个线程。然后这个窗口为每一个IME不感知程序操作任何IME用户接口。

当IME或者IMM产生WM_IME_xxx messages消息的时候,每一个IME不感知程序传递消息给DefWindowProc。然后,DefWindowProcB发送需要的消息给默认的IME窗口,为每一个IME不感知程序提供默认的IME用户接口功能。每一个IME感知程序对它打算不拦截处理的IME消息也可以使用这个窗口。每一个应用程序在需要的时候,都可以使用它自己的IME窗口。
IME 类

Win32系统在系统中提供了一个IME类。这个类是通过作为用户的预先编辑类定义。系统的IME类,操作全部的IME中的UI,并且操作传给IME和应用程序的全部控制消息,也包括操作IMM函数。通过使用这个类,一个应用程序能建立它自己的IME用户接口。这个系统IME类,它自己不会被任何IME类替代,只是作为一个预先定义的类被保存。

这个类有一个实际上操作WM_IME_SELECT消息的windows程序。这个消息获取最新被选择的IME在注册表中的hKL值。这个系统IME类重新获取存放在hKL中的每一个IME,找到其中被选择的这个IME类名。使用这个名字,系统IME类建立一个正确的UI窗口,并且激活这个IME程序。
从IME中产生的UI类

在这个设计中,每一个IME都希望向操作系统注册它自己的UI类。每一个IME提供这个UI类,用来响应实现IME需要的特殊功能。当IME附加在进程上的时候,IME可以注册这个供它自己使用的类。这发生在使用DLL_PROCESS_ATTACH参数调用DllEntry函数的时候。然后IME必须在lpszClassName参数中设置UI类名,这个参数是ImeInquire函数的第二个参数

这个UI类应该用CS_IME类型注册到样式字段中,以让每一个应用程序可以通过IME类使用它。这个UI类名(包括空字符)可以由最多16个字符组成.在未来的版本中可能会增加字符个数。

UI类的cbWndExtra参数长度必须是2 * sizeof(LONG)。WndExtra这个参数的用途是由操作系统定义的。(例如还有IMMGWL_IMC 、IMMGWL_PRIVATE)

IME在应用程序中运行的时候,IME可以注册任何类和建立任何窗口,

下面的例子显示了如何注册IME的用户接口类:

BOOL WINAPI DLLEntry (

HINSTANCE    hInstDLL,

DWORD        dwFunction,

LPVOID       lpNot)

{

switch(dwFunction)

{

case DLL_PROCESS_ATTACH:

hInst= hInstDLL;

        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;

        wc.style          = CS_MYCLASSFLAG | CS_IME;

        wc.lpfnWndProc    = MyCompStringWndProc;

        wc.cbClsExtra     = 0;

        wc.cbWndExtra     = cbMyWndExtra;

        wc.hInstance      = hInst;

        wc.hCursor        = LoadCursor( NULL, IDC_ARROW );

        wc.hIcon          = NULL;

        wc.lpszMenuName   = (LPSTR)NULL;

wc.lpszClassName = (LPSTR)szUICompStringClassName;

wc.hbrBackground = NULL;

if( !RegisterClass( (LPWNDCLASS)&wc ) )

return FALSE;

break;

case DLL_PROCESS_DETACH:

UnregisterClass(szUIClassName,hInst);

UnregisterClass(szUICompStringClassName,hInst);

break;

}

return TRUE;

}

UI Window

IME类的IME窗口通过应用程序或者操作系统建立(实际上IME类是操作系统内置自带的)。当IME窗口建立的时候,由IME自身提供的UI窗口被创建,并且被IME窗口拥有。

每一个UI窗口包含当前的输入上下文。当UI窗口收到一个WM_IME_xxx消息的时候,这个输入上下文能通过调用GetWindowLong(IMMGWL_IMC)函数得到。UI窗口可以查阅输入上下文并且操作消息。除了在处理WM_CREATE消息以外,在UI窗口程序过程的任何时候,任何消息中调用GetWindowLong(IMMGWL_IMC)都是有效的。

UI窗口的cbWndExtra参数不能为IME提供附加数据。当IME需要使用窗口实例的附加字节的时候,UI窗口使用带有IMMGWL_PRIVATE 参数的SetWindowLong 函数和GetWindowLong with函数. IMMGWL_PRIVATE 参数为窗口的实例提供了一个LONG型附加变量。当UI窗口需要多于LONG型附加变量的数据供自己私有使用时,UI窗口可以在IMMGWL_PRIVATE参数区域放入一个内存句柄(在IMMGWL_PRIVATE参数后加一个句柄作参数)。UI窗口程序能使用操作系统提供的默认窗口过程函数DefWindowProc,但是UI窗口不能向DefWindowProc传递任何WM_IME_xxx消息。即使WM_IME_xxx消息没有被UI窗口过程使用,UI窗口也不能把它传递给DefWindowProc。

下面的例子示范了如何为UI窗口分配和使用一块私有的内存区。

LRESULT UIWndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

HIMC hIMC;

HGLOBAL hMyExtra;

switch(msg){

case WM_CREATE:

// Allocate the memory block for the window instance.

hMyExtra = GlobalAlloc(GHND,size_of_MyExtra);

if (!hMyExtra)

MyError();

// Set the memory handle into IMMGWL_PRIVATE

//在这里增加了一个句柄(LONG)hMyExtra作为第3个参数,OS就自动把它放进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);

}

}

UI窗口必须通过查阅当前选择到的输入上下文执行所有的操作任务。当一个应用程序窗口被激话,UI窗口接收到一个包含输入法上下文的消息,UI窗口然后使用这个输入上下文工作。因此,输入上下文必须包含所有通过UI窗口显示的编码窗口、状态窗口等需要的信息

UI窗口查阅输入上下文,但是不能修改它。无论何时,如果UI窗口想要修改输入上下文,它应该调用IMM函数。因为输入上下文被IMM管理,当输入上下文被改变的时候,IMM应该跟随着IME一起被通知。

例如,当用户单击鼠标时,UI窗口偶尔需要改变输入上下的转换模式。在这个点上,UI窗口应该调用ImmSetConversionMode函数,ImmSetConversionMode函数创建一个WM_IME_NOTIFY消息通知给NotifyIME函数和 UI窗口。如果UI窗口要改变转换模式的显示,UI窗口应该等待WM_IME_NOTIFY消息。
UI窗口的部件

UI窗口通过查阅当前的输入上下文,能注册和显示编码窗口、状态窗口。 UI窗口的部件的类型必须包括CS_IME位。一个UI窗口实例,从当前的输入上下文获取关于编码字符串、字体、坐标位置的信息。

当一个应用程序窗口获得焦点的时候,系统给这个窗口提供输入上下文,并且设置当前的输入上下文给UI窗口。系统然后发送一个WM_IME_SETCONTEXT 消息和输入上下文的句柄给应用程序。应用程序然后传递这个消息给UI窗口。如果当前的输入上下文被其它的输入上下文替换,UI窗口应该重绘编码窗口。任何时候当前的输入上下文被改变,UI窗口会显示出一个正确的编码窗口。因此,IME的运行状态是有保障的。

一个UI窗口能建立一个子窗口或者弹出窗口显示它的状态、编码字符串,或者候选列表。无论如何,这些窗口必须被UI窗口所有,并且是作为限制能力的窗口(disabled windows)被建立。通过IME建立的任何窗口不应该获得焦点。
IME 输入上下文

每一个窗口关联着一个IME输入上下文。IMM使用这个输入上下文维持IME的状态、数据等等,并用它在IME和应用程序间通信。
默认的输入上下文

通过默认,系统为每一个线程建立了一个默认输入上下文。所有线程的IME不感知窗口共享这个输入上下文。
应用程序建立的输入上下文

一个应用程序窗口可以把它的窗口句柄关联到一个输入上下文,去维持IME的任何状态,包括中间的编码字符串。一个应用程序一次只能把输入上下文关联到一个窗口句柄,无论何时,当这个窗口被激活的时候,系统自动选择这个输入上下文。用这种方式,应用程序从复杂的进出焦点进程中解脱出来。
使用输入上下文

当一个应用程序或者系统建立一个新的输入上下文,系统会准备好新输入上下文和它IMC(IMCC)部件。这些部件包括:hCompStr, hCandInfo, hGuideLine, hPrivate, hMsgBuf。基本上,IME不需要建立输入上下文和输入上下文的部件。IME能改变它们的大小和锁定他们得到它们的指针。
访问输入上下文(HIMC)

当一个应用程序访问输入上下文,IME必须调用ImmLockIMC函数得到输入上下文的指针。ImmLockIMC函数增加IMM对IMC的锁定计数。ImmUnlockIMC函数减少IMM对IMC的锁定计数。
访问输入上下文的部件(HIMCC)

当一个应用程序访问输入上下文的部件,IME必须调用ImmLockIMCC函数得到输入上下文部件IMCC的指针。ImmLockIMCC函数增加IMM对IMC的锁定计数。ImmUnlockIMCC函数减少IMM对IMC的锁定计数。ImmReSizeIMCC函数能重设IMCC为在输入法参数中指定的大小。

有时候,IME需要建立一个新的输入上下文部件。IME可以调用ImmCreateIMCC函数这样做。销毁一个新的输入上下文部件,IME可以调用ImmDestroyIMCC函数。

下面的例子显示了如何访问IMCC,并且改变部件的大小。

LPINPUTCONTEXT lpIMC;

LPCOMOSITIONSTRING lpCompStr;

HIMCC hMyCompStr;

if (hIMC) // It is not NULL context.

{

lpIMC = ImmLockIMC(hIMC);

if (!lpIMC)

{

MyError( “Can not lock hIMC”);

return FALSE;

}

// Use lpIMC->hCompStr.

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);

}
产生消息

IME需要产生消息。当一个IME启动转换过程(输入编码得到汉字的过程),IME必须产生WM_IME_STARTCOMPOSITION(开始写作)消息。如果IME改变了写作的字符,IME必须产生WM_IME_COMPOSITION(写作)消息。

IME产生消息的方式有两个:一个是通过ImeToAsciiEx提供的lpdwTransKey缓冲区,另一个是通过调用ImmGenerateMessage函数。
使用lpTransMsgList 产生消息

由IME创始的事件在实现中被视作为给和输入上下文关联在一起的窗口产生消息。基本上,IME使用ImeToAsciiEx函数提供的lpTransMsgList参数产生消息,当ImeToAsciiEx函数被调用的时候,IME把消息放进lpTransMsgList缓冲区。

在ImeToAsciiEx函数中被指定的lpTransMsgList缓冲区是由操作系统提供的。这个函数能够把所有消息一次性放置到这个缓冲区。放置的消息数量是由缓冲区的第一个双字节给定的。无论如何,如果ImeToAsciiEx函数要产生比给定的数字更多的消息,ImeToAsciiEx函数可以把所有的消息放置入输入上下文中的hMsgBuf成员中,然后 ImeToAsciiEx函数返回存放的消息数量。

当ImeToAsciiEx函数的返回值大于lpTransMsgList的指定值,系统不会从lpTransMsgList中取出消息。代替的是,操作系统查找输入上下文中的hMsgBuf中存放的消息,此时,系统只会把lpTransMsgList中的数据当作ImeToAsciiEx函数的一个参数传递,而不看作是IME产生的消息。

下面是实现ImeToAsciiEx函数的代码例子

UINT
ImeToAsciiEx(
uVirKey,
uScanCode,
lpbKeyState,
lpTransMsgList,
fuState,
hIMC
)

{

DWORD dwMyNumMsg = 0;

. . .

// Set the messages that the IME wants to generate.

pTransMsgList->TransMsg[0].message =msg;

pTransMsgList->TransMsg[0].wParam = wParam;

pTransMsgList->TransMsg[0].lParam = lParam;



// Count the number of the messages that the IME wants to generate.

dwMyNumMsg++;

. . .

return dwMyNumMsg;

}

使用消息缓冲区产生消息

即使ImeToAsciiEx函数没有被调用,通过使用输入上下文中的消息缓冲区hMsgBuf,IME程序仍然能产生消息给和输入上下文关联在一起的窗口。这个消息缓冲区作为一个内存句柄操作,并且IME把消息放入这个内存区。IME然后调用ImmGenerateMessage函数,把放在这个内存区的消息发送给正确的窗口。

下面是实现ImmGenerateMessage函数的代码例子

MyGenerateMesage(HIMC hIMC, UINT msg, WPARAM wParam, LPARAMlParam)

{

LPINPUTCONTEXT lpIMC;

HGLOBAL hTemp;

    LPTRANSMSG lpTransMsg;

DWORD dwMyNumMsg = 1;

// Lock the Input Context.

lpIMC = ImmLockIMC(hIMC);

if (!lpIMC) ….. // Error!

// re-allocate the memory block for the message buffer.

hTemp = ImmReSizeIMCC(lpIMC->hMsgBuf, (lpIMC->dwNumMsgBuf + dwMyNumMsg) * sizeof(TRANSMSG));

if (!hTemp) ….// Error!

lpIMC->hMsgBuf = hTemp;

// Lock the memory for the message buffer.

lpTransMsg = ImmLockIMCC(lpIMC->hMsgBuf);

if (!lpTransMsg) …. // Error!

// Set the messages that the IME wants to generate.

lpTransMsg[lpIMC->dwNumMsg].message = msg;

lpTransMsg[lpIMC->dwNumMsg].wParam = wParam;

lpTransMsg[lpIMC->dwNumMsg].lParam = lParam;

// Set the number of the messages.

lpIMC->dwNumMsgBuf += dwMyNumMsg;

// Unlock the memory for the message buffer and the Input Context.

ImmUnlockIMCC(lpIMC->hMsgBuf);

ImmLockIMC(hIMC);

// Call ImmGenerateMessage function.

ImmGenerateMessage(hIMC);

}

为了防止原数据丢失,得此做一个备份
原文地址:https://code.google.com/archive/p/windows-config/wikis/Win32IME.wiki 作者:未知

一些术语

IME: Input Method Editor/Engine, 输入法编辑器, 引擎
IMM: Input Method Manager, 输入法管理器
Comp: Composition String, 一般是用户输入的字串, 比如拼音输入香'字要打xiang', 这个`xiang'就是用户输入的Comp. 你可以告诉IMM当前的Comp是什么, 这样如果应用能自己显示Comp/Cands的话它就自己显示; 你也可以不告诉IMM, 这样你自己负责显示就行了.
Cands: Candidates, 候选词组
Commit: 提交上屏

其他一些软键盘啊, GuideLine啊之类的对我的输入法没什么用, 我就全部无视了.
简介

Win32下的输入法编程概括地来说就是要写一个DLL. 这个DLL要实现并在.def文件中指定输出M$指定的一些API. Win32在装载你的输入法DLL时会检查是不是每个API都能查询到, 如果不是的话, 这个输入法就不会被成功的装载.

建议下载2600.1106版本的win32 DDK, 里面有区位输入法的源程序, 我的影舞笔就是参考这个程序写的. 可以看看里面的wingb.def文件, 总共输出了将近20个API. 其实大部分都没什么用, 直接套就行了, 重要的也就5~6个.

``` $cat wingb.def LIBRARY WINGB

EXPORTS ImeConversionList ImeConfigure ImeDestroy ImeEscape ImeInquire ImeProcessKey ImeSelect ImeSetActiveContext ImeSetCompositionString ImeToAsciiEx NotifyIME

        ImeRegisterWord
        ImeUnregisterWord
        ImeGetRegisterWordStyle
        ImeEnumRegisterWord

        UIWndProc
        StatusWndProc
        CompWndProc
        CandWndProc
事实上win32 DLL还有一个隐含的输出函数, 就是DLL的入口函数, 一般都是名为DllMain的一个函数, 但是在区位输入法里这个函数的名字是ImeDllInit. 你可以在你的makefile (或类似于vc6的.dsp/vc789的.vcproj等文件)里指定入口函数的名字.

初始化的大概的顺序是:

    DllMain里注册窗口类
    ImeInquire里告诉imm你的ime消息窗口的类名
    imm根据这个类名创建你的ime消息窗口
    你的消息窗口的回调函数被调用, 消息是wm_create

至此, 用户就可以开始使用你的输入法了, 这之后有意义的顺序是大概这样的:

    用户按下一个键
    ImeProcessKey返回true, 表示输入法想处理这个键
    ImeToAsciiEx被调用
    此函数创建一些ime消息, 如开始/结束输入法编辑, 设置comp串, cand串, 显示comp窗口, 显示状态栏窗口, 提交等
    相应的窗口被创建, 显示,
    如果当前的应用程序是懂输入法的, 它自己也会显示comp串, 前提是你的ImeToAsciiEx需要告诉它comp串是什么, 如果你只想自己来显示comp串的话, 那么这种程序是不会显示的

我为了自己编程方便, 就没有通知应用程序自己去显示comp和cands. 同时也是因为我觉得实在是没有必要.
DllMain

这个函数肯定是最重要的, 一个DLL没有这个入口函数的话就不是DLL了. 在DDK的区位输入法代码里有一个sources的文件, 里面有一行:

DLLENTRY=ImeDllInit

你如果用别的build系统, 比如Visual Studio或者mingw, 你就应该自己配置你的入口函数是哪个.

这个函数在dll load的时候需要初始化你的输入法里要用到的全局变量, 以及注册win32的几个窗口类. 在区位输入法里注册了四个窗口类, UI, Status, Comp, Cand. 其中UI窗口是一个纯消息窗口, 也是win32 IME必须要求的一个窗口. 这个窗口的类名会在ImeInquire里传给win32 IME以便让win32 IME知道它应该去跟谁通讯. 我的影舞笔输入法把Comp和Cand的窗口合并为一个了.

如果这个函数返回false的话那这个DLL就会load失败, 当前尝试load你的输入法的这个程序就没法用你的输入法了. 所以在这个函数里你可以干一些很``酷'' 的事情, 比如, 在测试阶段, 你可以指定只有notepad才能成功load你的输入法, 通过GetModuleFileName你可以得到当前调用的程序的路径, 如果不是notepad, 那就不让它用你的输入法. 然后呢, 你在win32的控制面板→区域设置里指定你当前正在测试开发的输入法为默认的输入法, 这样你一打开notepad, 就可以开始测试你的输入法了, 而不需要按一下输入法切换键才能开始测. 虽然只是省下按一个键, 但是也是值得的, 因为相信你会按很多次的. 而这时候其他的程序不会受影响, 你可以随便杀死notepad.exe, 做下一轮的开发, 测试迭代.

(win32下一个DLL被load了的话, 是不允许替换这个dll文件的. 所以当你发现一个bug, 做了修正, 你没法把build出来的这个新的dll拷到系统路径里, 必须先把所有的load了这个dll的程序杀死. 如果你的输入法不是默认的, 那你每次都要按一下切换键才能开始测试; 如果它是默认的但是你不把除了notepad的其他程序排除的话, 你每次都要杀死很多程序, 比如explorer.exe等. 尤其是如果你只能手工一个一个的删的话, 很快你会疯掉的. 你甚至都不知道哪个程序load了你的输入法. 只能一个一个的猜? 如果你知道sysinternal的process explorer的话, 那你还可以用一下它的查找功能).

还有一个特别有用的功能是, 即使你的输入法还有bug, 但是如果这个bug只是针对某个程序的话(或者说某个程序有bug, 但只针对你的输入法:-), 你可以把这个程序排除在外. 比如, Cygwin下的X窗口程序都是由xwin.exe来画窗口的, 这些窗口都不能处理win32的输入法, 但是win32的输入法切换键又能把输入法的状态栏给切出来, 很明显没什么意义, 我就把xwin.exe在我的输入法里排除了. 又比如, 所有的DOS窗口的输入法处理都是由一个叫conime.exe的程序处理的, 这个程序好像会对我的输入法提很非分的要求, 我干脆就把它也拒之门外:-) 以后我就打定主意在终端窗口里再也不用输入法了, 呵呵! (造成这个的原因是前面提到的conime是个懂''输入法的应用, 它太懂''了, 它要求你必须设置comp串/cands告诉它知道, 你还不能自己显示! 我怀疑这个应用的imm是不是压根就不会帮你创建那个ime消息窗口. 一句话, 它太霸道了).

做这样的选择, 我的生活会更简单.
ImeInquire

这个函数是除了DllMain后第一个会被win32 IMM调用的函数. IMM通过调用这个函数知道你的输入法有什么特性. 比如, 除了按键消息外, 你是不是还想处理键放开的消息. 以下是我的影舞笔的此函数代码(注释版) ``` BOOL WINAPI ImeInquire(LPIMEINFO lpImeInfo, LPTSTR lpszWndCls, DWORD dwSystemInfoFlags) { if (!lpImeInfo) { //简单出错处理 return FALSE; }

lpImeInfo->dwPrivateDataSize = 0; //IMM会根据这个值自动为你的输入法分配一块内存, 你可以用它来保存一些你的context数据. 我嫌这玩意儿太"聪明"了, 不用之.

lpImeInfo->fdwProperty = IME_PROP_KBD_CHAR_FIRST | IME_PROP_UNICODE | IME_PROP_IGNORE_UPKEYS | IME_PROP_SPECIAL_UI;

    // IME_PROP_KBD_CHAR_FIRST 是说IMM调用你的ImeProcessKey和ImeToAsciiEx函数之前是不是把按键消息的char值算出来, 在第一个整型参数的前两个字节里传给你, 其实你也可以自己算的
    // 如果你不设这个标志的话那第一个参数就只有低位的两个字节有意义 (好像是所按的键的虚拟键值).

    // IME_PROP_UNICODE, 意义应该很明显了, 一般肯定得设上

    // IME_PROP_IGNORE_UPKEYS, 就是上文说的要不要处理键放开的消息. 不设这个标志就是要处理, 设了就是不处理(IMM针对键放开的消息就不会来调你的ImeProcessKey和ImeToAsciiEx了).

    // IME_PROP_SPECIAL_UI, 不记得什么意思了, 可以上google查一下.

lpImeInfo->fdwConversionCaps =
    IME_CMODE_NATIVE | IME_CMODE_NOCONVERSION;
lpImeInfo->fdwSentenceCaps = 0;
lpImeInfo->fdwUICaps = UI_CAP_ROT90;
lpImeInfo->fdwSCSCaps = SCS_CAP_COMPSTR | SCS_CAP_MAKEREAD;
lpImeInfo->fdwSelectCaps = (DWORD) 0;

    // 这之上的这段代码是什么意思我也不明白, 但是这样写对我的影舞笔就够用了, 所以我也懒得去弄明白.

lstrcpy(lpszWndCls, get_ui_class_name().c_str());

    // 这里你要把你的UI窗口的类名拷到IMM传给你的输出参数里.

    return (TRUE);

    // 一定要返回true, 没试过这里返回false会怎样. 

} ```

LPIMEINFO的定义可以在ddk的immdev.h里查到.
ImeProcessKey

win32 IMM在收到一个键盘消息之后, 会先问一下这个函数, 你的IME是不是想处理这个键, 如果你在这个函数里返回true, 意思就是你想处理, 那么imm就会接着调下一个函数ImeToAsciiEx, 否则它就会自己处理这个键盘消息.
ImeToAsciiEx

这个函数的返回值有点意思. 返回值应该是你这个函数的这次调用一共给win32产生的多少个消息. 比如用户输入了一个完整的五笔编码, 希望提交他/她选中的候选词了, 你就把要提交的数据(一个unicode字符串)写到输入法上下文的提交字串的内存句柄中, 再把 (WM_IME_COMPOSITION, 0, GCS_COMP|GCS_RESULT|GCS_RESULTREAD) 这样一个消息添加到输入法上下文的消息内存中, 把要返回多少个消息加一.

以下是输入法上下文结构的定义:

``` typedef struct tagINPUTCONTEXT { HWND hWnd; BOOL fOpen; POINT ptStatusWndPos; POINT ptSoftKbdPos; DWORD fdwConversion; DWORD fdwSentence; union { LOGFONTA A; LOGFONTW W; } lfFont; COMPOSITIONFORM cfCompForm; CANDIDATEFORM cfCandForm[4]; HIMCC hCompStr; HIMCC hCandInfo; HIMCC hGuideLine; HIMCC hPrivate; DWORD dwNumMsgBuf; HIMCC hMsgBuf; DWORD fdwInit; DWORD dwReserve[3]; } INPUTCONTEXT

UIWndProc

这个函数里要处理IME消息. 其实UI窗口根本没有UI, 没有图形! 这个窗口是一个纯消息窗口, 你不会收到wm_paint的消息. 所以我直接把区位输入法里处理wm_paint的回调函数删了. 我觉得把这个窗口命名为ImePureMsgWnd更合适一些.

话说回来, 这个IME第一重要的窗口是谁创建的呢? 答案是IMM. 你在区位输入法的代码里不会看到这个窗口被CreateWindowEx. 在DllMain里你会注册这个窗口的类, 在ImeInquire里你会把这个窗口的类名传给IMM. 之后你就会收到这个窗口的创建消息了. 说明肯定是IMM负责创建了这个窗口.

这个函数根据收到的消息要负责创建Comp, Status等窗口, 移动这些窗口的位置, 等等.
CompWndProc 和 StatusWndProc

这两个窗口函数最重要的当然是负责画窗口了.
其他不怎么重要的API

ImeConfigure 这个函数就是按理你应该弹一个对话框给用户配置你的输入法的地方了, 像我们这种高级电脑用户, 对这种功能直接无视.
ImeConversionList 这个函数是让另一个输入法来反查一个汉字在你的这个输入法里是怎么打出来的. 比如区位输入法可以指定用微软拼音来反查一个汉字的拼音. 在区位输入法的unicode模式下按9999'';输入香'', 输入完后还会继续显示编辑窗, 内容是``xiang''. 这么无厘头的功能, 无视!
ImeDestroy 没什么好说的,
ImeEscape 也没什么好说的, 没什么鸟用. 以下是我的代码:
ImeSelect Google出来的文档是说在这个函数里初始化或析构你的私有数据, 好吧, 我前面已经说了, 我没什么私有数据, 所以这个函数也可以简化了.
ImeSetActiveContext
ImeRegisterWord
ImeUnregisterWord
ImeGetRegisterWordStyle
ImeEnumRegisterWord
ImeSetCompositionString
NotifyIME

可以上这儿去看文档. 这些函数都没有什么用.
一些注意事项

win32输入法编程的陷阱还是挺多的, 搞不好会很迷惑.

一定要用版本管理工具(废话), 但是用了版本管理工具还不一定够, 在前期开发的时候, 要时不时地重启一下机器, 有时候测着没问题, 重启一次就不行了. 如果改动量大的话就会不知道是哪个版本引进的问题.
ime消息窗口的类名不能随便换. win32的IMM好像装载过一次以后就会把你这个输入法的各个窗口的类名给记下来, 你要是换了的话下次就装载不上了, 必须重启一次机器. 靠, 发现这个问题当时花了我很多时间.
某种情况下winlogon.exe不能被排除在外. 如果你的输入法开发的差不多了, 你把它设成默认的输入法, 准备以后一直用它了, 嗯, 用的好好的, 一重启, 嘿, 用不了了. 这是因为winlogon是你的第一个用户, 而这个进程好像比较特别, 如果它load这个默认输入法失败的话, windows就会认为这个输入法有问题. 所以你想把winlogon排除在外的话记得重启前要把另一个输入法换成默认的再重启.
出现以上两个问题时你也可以不重启, 只要把你的.dll换一个名字, 在注册表里换一个注册键.

往注册表里添的时候内容大概是这样的: (E0330804的0804比较重要, 这代表这是中文输入法, 更关键的是你的资源文件.rc里面也有0804, 如果这个不匹配的话这个ime也是load不了的. 前面的e033可以随便换).
``` Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\E0330804] "Ime File"="ywb.dll" "Layout File"="kbdus.dll" "Layout Text"="Chinese (Simplified) - YWB"
```
影舞笔的运行方法

编译用VS2008, 同时还要求安装了python3.1, 安装路径必须是C:/python31, 同时你必须把代码co到Q:\gcode\scim-cs下, 如果没有Q:盘可以用subst.exe挂一个. 当然也可以自己改一下源代码.

为了防止原数据丢失,得此做一个备份
原文地址:http://input.foruto.com/sbxlm/Technique/IME95.htm 作者:戴石麟

Windows 95 输入法编辑器
翻译:戴石麟译自微软的MSDN DDK

关于Windows 95的多语言IME(输入法编辑器)

在Windows 95中,IME以动态连接库(DLL)的形式提供,与Windows 3.1东方版的IME不同,每个IME作为一个多语言键盘布局来运行。与Windows 3.1 IME相比,Windows 95多语言IME具有下述优势:

作为多语言环境的一个组件来运行
为每个应用程序任务提供多个输入场景
为每个应用程序线程提供一个活动IME
通过应用程序的消息循环(消息顺序不变)为其提供信息
为对IME不敏感和对IME敏感的应用程序均提供强大的支持

为了充分利用IME的优势,应用程序需要支持Windows 95 IME应用程序I/F。

本文描述Windows 95 IME体系的应用程序I/F。

IME的结构

Windows 95 IME必须提供两个组件:IME转换接口和IME用户界面。IME转换接口以一套从IME模块输出的函数提供。这些函数由IMM调用。IME用户界面以一套窗口提供。这些窗口接收消息并提供IME的用户界面。

IME敏感的应用程序

有以下类型应用程序:

IME不敏感的应用程序:这种类型的应用程序绝不打算控制IME。但是,如果应用程序接受DBCS字符,用户可以用IME向应用程序输入任何DBCS字符。
IME半敏感的应用程序:这种类型的应用程序通常控制IME的各种场景,如打开和关闭、工作窗口等,但是它不为IME显示任何用户界面。
IME全敏感的应用程序:这种类型的应用程序通常显示IME给它的任何信息。

在Windows 95中,一个对IME不敏感的应用程序由一个默认的IME窗口和一个默认的输入场景支持。

IME半敏感的应用程序用预定义的"IME"类创建自己的IME窗口,可以处理也可以不处理自己的输入场景。

IME全敏感的应用程序自己处理输入场景。它显示输入场景给出的任何必要的信息。它不使用IME窗口。

IME用户界面

IME用户界面包括IME窗口、用户界面(UI)窗口和UI窗口的组件。

特点

IME类是预定义的全局窗口类,它实现IME的用户界面部分。IME类有许多与预定义的公共控件窗口相同的特征。IME窗口实例可用CreateWindowEx函数来创建。象静态控件一样,IME类窗口自身不对用户输入作出反应;代之以接收各类控件消息来实现整个IME用户界面。应用程序可以用IME类创建自己的IME窗口,也可以用ImmGetDefaultIMEWnd函数来获得默认的IME窗口。创建自己的IME窗口或使用默认的IME窗口的应用程序称为IME敏感的应用程序,可以获得下述益处(与Windows 3.1中类似应用程序相比):

包含候选列表窗口,每个应用程序可有自己的用户界面窗口实例,所以用户可以在任何操作的中途停下来,将输入焦点改变到另一应用程序。在Windows 3.1日文版中,在改变到另一应用程序时用户必须放弃操作。
因为IME用户界面窗口拥有应用程序的窗口句柄,因此它可以为应用程序提供默认的行为。例如,它可以随应用程序移动而自动移动,自动跟踪窗口的光标位置,指出每一应用程序的状态等。

虽然系统仅提供一个IME类,但仍有两种IME窗口。一种类型由系统为IME不敏感的应用程序创建。DefWindowsProc函数为这种窗口处理消息。DefWindowsProc函数的IME用户界面为一个线程中所有IME不敏感的窗口所共享。在本文中,它叫做"默认IME窗口"。其它窗口由IME敏感的应用程序创建。在本文中,由IME敏感的应用程序创建的窗口叫做"应用程序IME窗口"。

默认与应用程序IME窗口

线程初始化时,系统创建默认IME窗口;也就是说,默认IME窗口自动赋予线程。默认IME窗口为IME不敏感的应用程序提供IME用户界面。当IME或IMM生成一条IME消息(WM_IME_*)时,对IME不敏感的应用程序传递一条消息到DefWindowProc函数。DefWindowProc将所需的消息送到默认IME窗口以为应用程序提供默认IME用户界面。IME敏感的应用程序在不从IME挂接消息时也使用默认IME窗口。应用程序仅在需要的时候使用自己的应用程序IME窗口。

IME类

IME类是Windows 95-FE格式的预定义窗口类,就象Edit是预定义类一样。预定义IME类处理IME的整个用户界面,处理来自IME和应用程序(包含IMM函数)的所有控制消息。应用程序用IME类创建自己的IME用户界面。系统IME类不会被任何IME替换。

与IME类相联系的窗口类处理WM_IME_SELECT消息。这条消息包括新选择的IME的键盘布局。IME类使用键盘布局来获取由每个IME定义的类名。IME类使用类名为当前活动IME创建用户界面窗口。

来自IME的UI类

每个IME都必须向系统注册自己的用户界面(UI)类。UI类提供特定的IME功能。IME在附着进程时注册自己的类;也就是说,用DLL_PROCESS_ATTACH调用DllEntry函数时。IME在调用ImeInquire函数时必须指明UI类名。UI类应当以CS_IME风格注册,以便每个应用程序都能使用该UI类。UI类名(包括空结束符)可达16个TCHAR字符。此限制可能会在Windows的未来版本中得到扩展。

当注册用户界面类时,你应当指明八个字节的额外窗口数据(即,将WNDCLASSEX结构的cbWndExtra成员设置为2*sizeof(LONG)。这额外的窗口数据由系统使用。

IME在为应用程序完成任务时可注册任何类和创建任何窗口。

以下例子显示如何注册IME窗口类:

BOOL WINAPI DLLEntry (

HINSTANCE    hInstDLL,

DWORD        dwFunction,

LPVOID       lpNot)

{

switch (dwFunction) {

    case DLL_PROCESS_ATTACH:

        hInst= hInstDLL;



        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;



        wc.style          = CS_MYCLASSFLAG | CS_IME;

        wc.lpfnWndProc    = MyCompStringWndProc;

        wc.cbClsExtra     = 0;

        wc.cbWndExtra     = cbMyWndExtra;

        wc.hInstance      = hInst;

        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);

        wc.hIcon          = NULL;

        wc.lpszMenuName   = (LPSTR) NULL;

        wc.lpszClassName  = (LPSTR) szUICompStringClassName;

        wc.hbrBackground  = NULL;



        if(!RegisterClass((LPWNDCLASS)&wc))

            return FALSE;



        break;



    case DLL_PROCESS_DETACH:

        UnregisterClass(szUIClassName,hInst);

        UnregisterClass(szUICompStringClassName,hInst);

        break;

}

return TRUE;

}

UI窗口

IME类的IME窗口由应用程序或由系统创建。当IME窗口创建时,由IME本身提供的UI窗口被IME窗口创建并拥有。每个UI窗口都有一个当前输入场景。你可以在UI窗口收到一条IME消息(WM_IME_*)时,通过调用GetWindowLong函数并指明IMMGWL_IMC索引值来获取输入场景。UI窗口可以参照此输入场景并处理消息。UI窗口可以在除对WM_CREATE消息作出反应时以外的任何时候获取输入场景。

IME决不能更改UI窗口的额外窗口数据。如果你的窗口实例需要额外窗口数据,你可以用SetWindowLong和GetWindowLong函数带IMMGWL_PRIVATE参数。IMMGWL_PRIVATE参数提供对UI窗口实例额外数据的一个LONG值的访问,你可将一内存块的句柄存入IMMGWL_PRIVATE区域。

UI窗口过程可以使用DefWindowProc函数,但是UI窗口不能将IME消息传递给DefWindowProc。即使一条IME消息未被UI窗口过程处理,UI窗口也不能将它传递给DefWindowProc。

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);



}

}

UI窗口在当前选定的输入场景中完成动作。当窗口激活时,UI窗口收到一条提供当前输入场景的消息。在此之后,UI窗口在当前选定的输入场景中运行。输入场景必须提供UI窗口显示工作窗口、状态窗口等所需的所有信息。

UI窗口参照输入场景,但它不必自己刷新该场景。当UI窗口需要刷新输入场景时,它应当调用IMM函数,因为输入场景是由IMM管理的。当输入场景改变时,IMM和IME都收到通知。

例如,有时当鼠标点击时,UI窗口需要改变输入场景的转换模式。要设置转换模式,UI窗口调用ImmSetConversionMode函数来为NotifyIME生成一条通知并将WM_IME_NOTIFY消息送至UI窗口。如果UI窗口要改变转换模式的显示,UI窗口应当等待WM_IME_NOTIFY消息。

UI窗口的组件

UI窗口可以参照当前输入场景来注册和显示工作窗口和状态窗口。UI窗口组件的类风格必须包含CS_IME。UI窗口实例从当前输入场景接收诸如工作字串、字型、位置之类的信息。上应用程序的一个窗口获得焦点时,系统收到为窗口所拥有的输入场景并将当前输入场景设置给UI窗口。系统将带有输入场景句柄的WM_IME_SETCONTEXT消息送到应用程序,应用程序将此消息传递到UI窗口。如果当前输入场景被替换,UI窗口应当重画工作窗口。每当当前场景变化,UI窗口都会显示正确的工作窗口,确保IME的状态。

UI窗口可以创建自己的字窗口或弹出窗口来显示其状态、工作字串或候选名单。这些窗口必须为UI窗口所拥有,并创建为失效窗口。IME创建的任何窗口都不应当获得焦点。

输入场景

默认输入场景

系统通过默认给每个线程一个输入场景。该场景为该线程所有对IME不敏感的窗口共享。

将输入场景与窗口相关联

应用程序的窗口将其窗口句柄与输入场景相关联来维持IME状态,包括当前工作字串。一旦应用程序将输入场景与窗口句柄相关联,系统在窗口激活时将自动选择该场景。利用此特征,应用程序将不需象在Windows 3.1下那样进行复杂的获得、放弃焦点的处理。

使用输入场景

当应用程序或系统创建新的输入场景时,系统准备新的输入场景。新的输入场景已经包括IMCC,即由hCompStr、hCandInfo、hGuideLine、hPrivate和hMsgBuf组成的IMC的组件。IME基本上不需创建输入场景和输入场景的组件。IME可以通过锁定它们来改变其大小并能获得指向组件的指针。

访问HIMC

要访问输入场景,IME必须调用ImmLockIMC来获得指向输入场景的指针。ImmLockIMC递增IMC的IMM锁定记数,而ImmUnlockIMC则递减它。

访问HIMCC

要访问输入场景的一个组件,IME必须调用ImmLockIMCC来获得指向IMCC的指针。ImmLockIMCC递增IMCC的IMM锁定记数,而ImmUnlockIMC则递减它。ImmReSizeIMCC可以将IMCC调整至特定的大小。

有时,IME可能需要自己创建输入场景的一个组件。在这种情况下,IME可以调用ImmCreateIMCC函数并获得IMCC的句柄。该IMCC可以是INPUTCONTEXT结构的成员(hCompStr、hCandInfo、hGuideLine、hPrivate或hMsgBuf)。

ImmDestroyIMCC销毁输入场景的一个组件。

怎样使用输入场景

以下例子显示如何使用输入场景:

LPINPUTCONTEXT lpIMC;

LPCOMOSITIONSTRING lpCompStr;

HIMCC hMyCompStr;

if (hIMC) { // It is not NULL context.

lpIMC = ImmLockIMC(hIMC);



if (!lpIMC) {

    MyError( "Can not lock hIMC");

    return FALSE;

}



// Use lpIMC->hCompStr.

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);

}

生成消息

IME需要生成IME消息。当IME开始一次回话时,IME必须生成WM_IME_STARTCOMPOSITION消息。如果IME改变工作字符串,IME必须生成WM_IME_COMPOSITION消息。从IME来的事件是通过向与输入场景相关联的窗口生成消息来实现的。IME基本上是利用ImeToAsciiEx函数的参数所提供的lpdwTransKey缓冲区来生成消息的。当ImeToAsciiEx被调用时,IME将消息放进lpdwTransKey缓冲区。即使ImeToAsciiEx未被调用,IME还是能够利用输入场景的消息缓冲区向与输入场景相关联的窗口生成消息。输入场景以内存块句柄的形式拥有消息缓冲区。IME将消息放进由消息缓冲区句柄提供的内存块中。然后,IME调用ImmGenerateMessage。ImmGenerateMessage函数将存储于消息缓冲区里的消息发送到适当的窗口。

为了防止原数据丢失,得此做一个备份
原文地址:https://blog.csdn.net/pkfish/article/details/7339909 作者:pkfish

·基于IME的输入法的安装、更新及卸载

安装

了解了系统是怎样保存输入法的信息后,要安装一个输入法就是很简单的事情了,我们只要准备好一个输入法ime文件,把它放好在某个位置后,再手动往上一点里提到的注册表位置中添加必要的项后输入法安装就算完成了。不过看着为每个已安装输入法分配的那串8位标识字符串(即输入法专属的键盘布局标识),是否觉得以编程手段实现输入法安装还是有点烦人呢?其实系统有为我们提供更简单的输入法安装方法,这个就是ImmInstallIME函数:

函数名


ImmInstallIME

功能


把输入法ime文件注册到系统中,让系统可以识别并调用该输入法,经此函数注册成功后,该输入法会直接出现在语言栏中(函数为我们在HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Keyboard Layouts和HKEY_CURRENT_USER\\Keyboard Layout\\Preload两处地方都创建了相关信息)

函数原型
ANSI版本:HKL  WINAPI ImmInstallIMEA(LPCSTR lpszIMEFileName, LPCSTR lpszLayoutText)

UNICODE版本:HKL  WINAPI ImmInstallIMEW(LPCWSTR lpszIMEFileName, LPCWSTR lpszLayoutText)

参数
LPCSTR lpszIMEFileName:输入参数,传入输入法ime文件的文件名(如果传入的是带路径的文件名,则函数会把路径部分清除,仅提取出文件名部分,输入法文件也可采用其它扩展名)此文件名将作为输入法项中键名为“Ime File”的值

LPCSTR lpszLayoutText:输入参数,作为注册表输入法项中键名为“Layout Text”的值,该键的意义上面已有说明

返回值
HKL:返回的为键盘布局标识,也即注册表中表示该输入法项的8位16进制数(如“0x00060804”,可把HKL这个类型直接作为一个32位无符号数来看待),如果注册失败,则返回0

使用准备
使用此函数必须引用系统头文件imm.h,且在工程属性的链接器->输入->附加依赖项中要加入对“imm32.lib”文件的依赖

其它
ImmInstallIME为对两个版本函数的编译器级别调用封装,根据当前编程环境的选择(UNICODE/非UNICODE),会调用相应的版本

调用失败
这里只说明两种常见的调用失败可能:一种是调用本函数时未把要注册的输入法ime文件复制到系统system32(win2000及以上)/system(win9x/me)目录下,另一种是输入法ime文件本身存在问题,关于后一点,在后面[创建一个空壳输入法]部分会提到。

使用ImmInstallIME注册成功后,可以自己进注册表的“HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Keyboard Layouts”中看看函数为我们创建的输入法项。

个人建议的输入法安装步骤如下:

1、复制输入法ime文件到系统system32(win2000及以上)/system(win9x/me)目录下;

2、调用ImmInstallIME函数注册输入法文件;

3、进行输入法安装的其它操作,比如要把输入法用到的其它辅助文件复制到用户指定的目录下,在注册表中写入输入法的一些配置信息。对于ImmInstallIME函数成功注册输入法文件后返回的输入法键盘布局标识号,个人比较建议在注册表中找个地方保存起来,这样卸载输入法的时候就方便多了(像我是在注册表的“HKEY_LOCAL_MACHINE\Software”下创建了一个子项,把这个返回的标识号作为这个子项的一个键值保存下来,而输入法配置的一些属性信息也同样保存在这个子项下面)。

卸载

在下面谈卸载输入法前,要先重申一点:在系统语言栏上见不到该输入法并不表明该输入法已卸载了,只有在语言栏设置的添加里也无法找到该输入法(还要注意添加里的语言筛选只会显示与选定语言相符的输入法)时,该输入法才算真正被卸载了,否则只能说该输入法在语言栏上被禁用了,但系统仍保存着其注册信息,随时可以通过语言栏设置里的添加把其重新启用。
要实现输入法的卸载,我们必须清理掉系统注册输入法时产生的信息,而这个工作我们只能自己去完成(系统没提供类似ImmInstallIME的函数供我们清理时用),通常如果在安装时没有把输入法的键盘布局标识,则这里的卸载就比较头疼了,只能从所有已安装输入法项的某些键中(如“Ime File”、“Layout Text”)去区分出哪个是自己要卸载的输入法(除非你一开始就打算让自己的输入法成为流氓软件,否则安装时保存好输入法键盘布局标识还是很有必要的)。虽然输入法的卸载里注册表要自己清理,但一般还是要用到UnloadKeyboardLayout这个函数来帮我们清理掉语言栏的残留信息,这个函数的说明如下:

函数名
UnloadKeyboardLayout

功能
把特定的输入法从语言栏上卸下来,只有该输入法在语言栏中处于启用状态此函数才有效

函数原型
BOOL UnloadKeyboardLayout(HKL hkl)

参数
HKL hkl:输入参数,要卸下的输入法键盘布局标识号

返回值
BOOL:函数执行失败返回0,也即FALSE值,否则返回非0值

使用准备
函数定义在winuser.h头文件中,如果包含了windows.h头文件,则该头文件也被包含了。

在链接上,其代码实现保存在User32.lib/User32.dll中,一般创建C++的窗口工程或控制台工程里所默认包含的库中就已包含这些文件,不需要额外写入附加依赖项中

调用失败
目前只发现该输入法未在语言栏中启用(未显示在语言栏中)时会返回调用失败,但要注意的是调用成功也可能留有问题,下面的注意事项有说明。

注意事项
要注意的是,这个函数卸下的效果比较特别(在winxp下是如此,其它系统的表现是否会有所不同不清楚),其卸下后语言栏中该输入法的位置还是会显示着输入法名称,但图标则变成了缺省输入法的图标(缺省输入法指当某输入法无效时系统默认替换的输入法,缺省输入法不同于语言栏设置里的默认输入语言。如果系统未刷新及时,也可能还是显示着已卸下的输入法的图标)。此时尝试调用该输入法会发现已经没任何效果。造成这个原因是因为函数仅清理了系统当前的输入法状态信息,但注册表“HKEY_CURRENT_USER\\Keyboard Layout\\Preload”项中相应的键值并未被清理,要把语言栏中这个异常项清理掉,只要在调用本函数后接着把注册表该位置的相应键值清除掉,则语言栏上该输入法的一栏就会被系统删掉了。

如果选择先清除掉注册表相应项,再调用本函数,则也会存在问题,详细分析看下面。

个人建议的输入法卸载步骤如下:

1、读出安装时已保存好的输入法键盘布局标识号;

2、调用UnloadKeyboardLayout函数把输入法从语言栏中卸下(即使该输入法没在语言栏中被启用,调用此函数也不会存在任何问题);

3、遍历注册表“HKEY_CURRENT_USER\Keyboard Layout\Preload”项下的所有键值,如果找到值为要卸载输入法的标识号(因为值是字符串格式的,所以这里要用字符串比较,但可以不区分大小写)的键,则把该键删除掉,删除后记得把键名调整一下,使得键名里的数字是连续的,不然重启系统后可能输入法就乱了;

4、删除注册表“HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Keyboard Layouts”下要卸载输入法的子项

5、删除输入法ime文件(如果尚有程序在使用该输入法,则输入法文件将无法删除),其它辅助文件及各种配置信息等等。

卸载步骤中的第2和第3步在实践中发现,反过来操作后产生的效果与仅执行UnloadKeyboardLayout相似,这说明了UnloadKeyboardLayout函数执行时会先通过“HKEY_CURRENT_USER\Keyboard Layout\Preload”项判断要卸下的输入法是否已被启用,如果发现没被启用,则不会产生卸下的动作(但要注意函数返回仍然为TRUE!!)。但这并不代表该项还能用,因为注册表中表明该项输入法是什么的键已经不存在,所以此时语言栏中该输入法项已经废掉,只是无法被清除掉而已,要想手动清除掉这个因操作次序失当造成的残留,可自己在语言栏的设置中删除掉该项,或者直接重启系统也能把该项清除掉。

更新

,关于输入法的更新,简单来看就只是输入法ime文件及其它有改动文件的覆盖就可以了。不过如果输入法正在使用中,则文件覆盖会失败。要注意的是:即使当前系统下所有程序都没在直接打开该输入法,也不代表输入法就没被使用。输入法的加载是与应用程序进程绑一块的,如果某个应用程序A曾经打开过输入法B(触发了在应用程序A进程中加载输入法B),即使后来应用程序A使用的输入法切换成C了,但只要应用程序A的进程不关闭,输入法B都不会被释放,此时进行输入法B的ime文件更新同样会失败的。

,另还有一种比较特殊的情况,是即使输入法ime文件更新成功了,但可以运行时会失败,这个在后面[编写输入法的Hello程序]时会提到。

注册表操作API 部分(安装卸载时多会用到)

RegOpenKeyEx —— 打开指定注册表路径的项(路径及项名不区分大小写)

RegCloseKey —— 关闭特定已打开的注册表项(项名不区分大小写)

RegCreateKeyEx —— 创建新的注册表项(项名不区分大小写)

RegDeleteKey —— 删除特定注册表项(项名不区分大小写)

RegSetValueEx —— 在特定注册表项下创建或修改特定键值

RegQueryValueEx —— 获取特定注册表项下特定键的值及其它键信息

RegEnumValue —— 枚举特定注册表项下的键值

RegDeleteValue —— 删除特定注册表项下的特定键

创建一个空壳输入法

,本部分要讲述的是创建一个可以在语言栏中显示的输入法文件,以说明系统对一个可注册的输入法文件的一些基本要求。该输入法文件不能被调用(所以说那是一个空壳输入法)。

下面的操作步骤是基于Visual Studio 2005 的,其它的IDE创建类似的工程即可:

1、先创建一个空的win32 dll项目(在新建项目那选择win32项目,然后在随后打开的向导中把应用程序类型设置为Dll,再把[空项目]一项选中;

2、往建好的项目里新建一个资源文件(.rc),然后往这个资源文件中添加一个Version资源;

3、打开这个Version资源,里面有三处信息是要注意的,其中两处要把值改对:

FILETYPE —— 默认的是“VFT DLL”,但这里必须改选成“VFT DRV”,否则调用ImmInstallIME时会返回调用失败;

FILESUBTYPE —— 默认的是“VFT2 UNKNOWN”,但这里必须改选成“VFT2 DRV INPUTMETHOD”,否则同样会导致调用ImmInstallIME函数失败;

FileDescription —— 当输入法在语言栏中显示时,这项里的值将作为该输入法在语言栏上显示的文本信息,此信息可与输入法名称不相同。

4、如果只完成前3步编译程序,编译器会告诉说缺少mian函数。现在往项目中创建一个cpp文件,往里面填入Dll的main函数:

bool __stdcall DllMain ()

{

return true;

}

5、编译程序,生成的dll文件就可以拿去安装了(也可以按输入法文件的扩展名要求,把编译好的文件的扩展名改成ime,或直接在项目中修改生成文件的文件名),作为测试,做个简单的安装即可:把这个生成的文件复制到系统system32(win2000及以上)/system(win9x/me)目录下,然后再调用ImmInstallIME函数即可。

,假如现在要被注册的输入法文件的文件名为“IME_Test.ime”,输入法名称想设置为“测试显示”,把该文件复制到指定目录后,就可以这样调用安装函数:ImmInstallIME(_T("IME_Test.ime"),_T("测试显示"))。然后就可以在语言栏那看看是不是已经有自己的输入法了(此时不要尝试调用该输入法!!调用它只会导致错误)。不过此时看到的输入法其图标为系统缺省输入法的图标,这是由于制作输入法文件时没添加图标(icon)资源。

6、回到输入法的项目中,这次在资源文件中添加一个图标资源,随便弄个图案出来后,重新编译程序。把新的输入法文件直接覆盖原来的文件(此时应该能直接覆盖),更新就完成了。不过此时看语言栏该输入法的图标有可能并没有更新(这与输入法信息的加载在单个进程中只会加载一次有关),最简单的方法是打开一个新的应用程序(必须是会创建新的进程出来的程序),在这个应用程序窗口为活动窗口的前提下查看语言栏中的输入法,此时应该就能看到更新后的图标了。

补充说明一点:对输入法文件图标的识别中,系统只会把资源中的首个图标资源作为该输入法的图标,对于图标的大小和色彩数好像系统都没做限制(显示时会自动调整图标尺寸)。

7、最后就是清理这个空壳输入法了,清理过程参考前面说的卸载步骤即可。

IME与输入法的交互

,IME与输入法的交互主要有两种方式:事件和接口。接口指的是IME要求输入法文件实现并十多个入口函数,除此以外,IME还要求输入法文件为输入法注册一个特殊的窗口类,IME有需要通知输入法处理的事件时则通过向这个窗口类发送该事件消息实现。

,IME要求输入法文件实现的接口函数有如下这些:

ImeInquire、ImeSelect、ImeProcessKey、ImeToAsciiEx、NotifyIME、ImeSetActiveContext、ImeConfigure、ImeSetCompositionString、ImeConversionList、ImeEnumRegisterWord、ImeRegisterWord、ImeUnregisterWord、ImeGetRegisterWordStyle、ImeEscape、ImeGetImeMenuItems、ImeDestroy。

,对所有函数的详细说明在后面会附上,而在讲述编写Hello World输入法时也会提到部分函数的用法。虽然接口函数很多,但实际上并非所有函数都需要实现其功能。