Win下的输入法(IME)编程(2)

为了防止原数据丢失,得此做一个备份
原文地址: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输入法时也会提到部分函数的用法。虽然接口函数很多,但实际上并非所有函数都需要实现其功能。

留下评论