三笔输入法 开发过程记录

算法 专栏收录该内容
28 篇文章 0 订阅

imm.h header

https://docs.microsoft.com/en-us/windows/win32/api/imm/

 

1、确保应用 Imm32.lib

       Project->Settings->Link->General 在 Object/library modules中添加 imm32.lib

2、如果 IMM.H 和 INDICML.H 的位置在项目中,应在 additional include directories 中指定。

       Project->Settings->C/C++->Preprocessor 在 Additional include directories 中设置 ./

3、在模块定义文件(.def)中包含(EXPORTS)下列 IME 函数

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

                UIWndProc
                CompWndProc
                CandWndProc
                StatusWndProc

4、指定用于调试的外部程序

       Project->Settings->Debug,在 Executable for debug session 中设置用于调试的应用程序路径及名称。例如c:/windows/notepad.exe

5、将输出定位到SYSTEM32。

       Project->Settings->Debug,在 Output file name 中设置输出名称 c:/windows/system32/*.ime(*.ime用具体的名称代替)。

6、添加注册表项

   在 HKEY_LOCAL_MACHINE/SYSTEM/ControlSet001/Control/Keyboard Layouts 下新建项(如E00F0804,804:简体中文;411:日文),分别创建三个字符串值项: IME file, Layout File, Layout Text。

   IME file:输入法应用程序名称(如 wubi.ime)

   Layout File:键盘布局(如 KBDUS.DLL)

   Layout Text:在输入法列表中显示的名称

7、从控制面板中添加你的输入法到输入法列表中。

8、在VS中设置断点,按下F5,在输入法列表中选择你的输入法。
————————————————

ImeConversionList                   将字符或字符串转换成目标字符
ImeConfigure                            配置当前ime参数函数
ImeDestroy                           退出当前使用的IME
ImeEscape
ImeInquire                             ;启动并初始化当前IME输入法
ImeProcessKey                     ;IME输入键盘事件管理函数
ImeSelect                              ;初始化
ImeSetActiveContext              ;设置当前的输入处于活动状态
ImeToAsciiEx                        ;编码转换函数
NotifyIME                              ;IME通知管理函数
ImeRegisterWord                   ;向输入法字典注册字符串
ImeUnregisterWord
ImeGetRegisterWordStyle
ImeEnumRegisterWord
ImeSetCompositionString

 

IME

IME实际上是一种键盘布局,它由用户界面和转换接口两部分组成。它负责将用户输入编码转换为目标字符串后发送到应用程序,并提供用户查看、选择候选字词等界面。

根据与输入法的交互关系来分,Windows中的应用程序可以被分成:不识别 IME 的应用程序、部分识别 IME 应用程序和完全识别 IME 的应用程序。

不识别 IME 的应用程序在运行的时候,不会感知用户当前是否打开或者切换IME。它们就象从键盘输入字符一样接收来自 IME 的最终输入字符。例如Windows附带的记事本程序就是一个典型的不识别IME的应用程序。

部分识别 IME 应用程序往往希望控制 IME 的行为:打开IME、关闭 IME或者配置 IME UI 窗口。这种应用程序也可通过特定的 IMM 消息取得 IME 转换字符串,但是它们本身不显示任何IME用户界面。例如有的应用程序中当获取用户身份证号码的控件获取焦点时,中文输入法会自动关闭,就是一个典型的部分识别IME应用。

完全识别 IME 的应用程序负责通过 IME 绘制 IME UI 窗口(状态、输入码以及候选字词窗口)。这些应用程序可以完全自定义每个窗口的外观,包括在屏幕上的位置以及用于在窗口中显示字符的字体和字体样式。通过这种方法,对文本输入依赖性很高的复杂程序(如文字处理器)就能为用户提供更透明的字符输入法。

2.Windows IME接口

IME输入法的扩展名是ime,例如:Windows附带的全拼输入法的IME文件是winpy.ime,但是其实质上是一个DLL文。它由系统负责运行、调用和加载,IME文件通常存储在Windows的系统文件夹中。IME输入法中大约需要实现15个接口函数供系统调用,下面对它们加以简要说明,详细的参数描述可以参见附录。

1)ImeInquire

此接口函数在用户选择输入法时最先由IMM调用,IMM通过该函数获得该输入法的有关信息,函数应返回IME的初始化信息,设置当前输入法的各项属性,以及当前输入法的用户界面窗口类名称等。

2)ImeConfigure 

此接口函数将在用户通过控制面板或系统图标设置输入法属性时被IMM调用,在此函数中可以显示属性设置对话框,为用户配置输入法提供配置窗口界面。

3)ImeProcessKey

此接口函数由输入法在处理键盘事件时调用,判断输入法是否需要处理一个用户键入的键值。如果不需要则返回FALSE,并将该键值直接发给应用程序,否则返回TRUE,然后IMM将会调用ImeToAsciiEx进行处理。

4)ImeToAsciiEx

该函数的工作是对输入的按键进行处理,只有当ImeProcessKey返回TRUE时才会调用这个接口函数。该函数主要根据输入法当前的运行状态进行处理,可以调用转换引擎,或者进行标点、符号的转换。

5)ImeSelect

当输入法被选择或者选出时,系统将调用本函数。

6)ImeSetActiveContext

当输入法被激活或者搁置的时候,系统将调用本函数。

7)NotifyIME

系统或者应用程序通过本函数根据参数的值改变输入法的当前状态。例如:显示或隐藏候选字词窗口、选定了一个候选字词、修改输入码串的内容。

8)ImeDestroy

当输入法从内存中被卸载时,将调用本函数。

9)ImeConversionList

本函数用于将一个指定的输入码转为为结果串,通常用于被部分识别IME的应用程序和完全识别IME的应用程序调用。在大多数输入法中可以无需实现该函数而直接返回。

10)ImeEscape

应用程序可以通过调用本函数来直接访问一个输入法的特定功能。一般而言这些功能应该是无法通过其他的IMM 函数调用来实现。这么做的主要目的是为了支持特定语种的函数或者IME 的私有函数。

11)ImeSetCompsitionString

本函数可以直接设置输入法的输入码,通常用于被部分识别IME的应用程序和完全识别IME的应用程序调用,在大多数输入法中该函数可以无需实现而直接返回。

12)ImeRegisterWord ­

本函数用于向输入法码本中添加一个字或词组,大多数输入法中该函数可以无需实现而直接返回。

13)ImeUnregisterWord

本函数用于在输入法码本中删除一个字或词组,大多数输入法中该函数可以无需实现而直接返回。

14)ImeGetRegisterWordStyle

本函数用于获取输入法码本中字或词组条目的形式,大多数输入法中该函数可以无需实现而直接返回。

15)ImeEnumRegisterWord

本函数用于在输入法码本中枚举字或词条,大多数输入法中该函数可以无需实现而直接返回。

 转自:http://blog.csdn.net/jhycjhyc/article/details/6578570

取值五笔加加微软拼音3.0搜狗拼音说明
NoControl首次调出后按一次ctrl+space才能正确使用 中西标点或全半角字符继承上次设置调出后默认为英文输入状态  调出后默认为西文标点 英文输入时为半角字符调出后默认为英文输入状态  调出后默认为西文标点 英文输入时为半角字符不建议使用
On调出后默认为汉字输入状态 中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置 
Off 调出后默认为汉字输入状态 中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置 
Disable调出后默认为英文输入状态中西标点或全半角字符继承上次设置调出后默认为英文输入状态 调出后默认为西文标点 英文输入时为半角字符调出后默认为英文输入状态调出后默认为西文标点 英文输入时为半角字符

推荐使用,适合

于密码输入

AlphaFull调出后默认为汉字输入状态 中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置 
Alpha调出后默认为汉字输入状态 中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置 
HangulFull调出后默认为汉字输入状态 中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置调出后默认汉英文输入、中西标点或全半角字符继承上次设置

推荐使用,适

于中文文字

编辑

Hangul调出后默认为汉字输入状态 中西标点或全半角字符继承上次设置调出后默认为汉字输入状态  调出后默认为西文标点 英文输入时为半角字符调出后默认为汉字输入状态  调出后默认为西文标点 英文输入时为半角字符

推荐使用,适

合于数据信

息录入

Close 首次调出后按一次ctrl+space才能正确使用 中西标点或全半角字符继承上次设置调出后默认为汉字输入状态  调出后默认为西文标点 英文输入时为半角字符调出后默认为汉字输入状态  调出后默认为西文标点 英文输入时为半角字符不建议使用

输入法编辑器(IME)编程指南


  以下是IME编程中需要用到的几项基本组成元素:

  •  IME函数 

  •  IME消息 

  •  IME命令 

  •  IME结构

  •  IME常量

IME函数


  本节列出了所有IME函数。
函数说明
EnumInputContext由应用程序定义的,提供给ImmEnumInputContext函数用来处理输入环境的一个回调函数。
EnumRegisterWordProc由应用程序定义的,结合ImmEnumRegisterWord函数一起使用的一个回调函数。
ImmAssociateContext建立指定输入环境与窗口之间的关联。
ImmAssociateContextEx更改指定输入环境与窗口(或其子窗口)之间的关联。
ImmConfigureIME显示指定的输入现场标识符的配置对话框。
ImmCreateContext创建一个新的输入环境,并为它分配内存和初始化它。
ImmDestroyContext销毁输入环境并释放和它关联的内存。
ImmDisableIME关闭一个线程或一个进程中所有线程的IME功能。
ImmDisableTextFrameService关闭指定线程的文本服务框架(TSF)功能--虽然这里把它列了出来,但建议程序员最好不要使用这个函数。
ImmEnumInputContext获取指定线程的输入环境。
ImmEnumRegisterWord列举跟指定读入串、样式和注册串相匹配的注册串。
ImmEscape对那些不能通过IME API函数来访问的特殊输入法程序提供兼容性支持的一个函数。
ImmGetCandidateList获取一个候选列表。
ImmGetCandidateListCount获取候选列表的大小。
ImmGetCandidateWindow获取有关候选列表窗口的信息。
ImmGetCompositionFont获取有关当前用来显示按键组合窗口中的字符的逻辑字体的信息。
ImmGetCompositionString获取有关组合字符串的信息。
ImmGetCompositionWindow获取有关按键组合窗口的信息。
ImmGetContext获取与指定窗口相关联的输入环境。
ImmGetConversionList在不生成任何跟IME有关的消息的情况下,获取输入按键字符组合或输出文字的转换结果列表。
ImmGetConversionStatus获取当前转换状态。
ImmGetDefaultIMEWnd获取缺省IME类窗口的句柄。
ImmGetDescription复制IME的说明信息到指定的缓冲区中。
ImmGetGuideLine获取出错信息。
ImmGetIMEFileName获取跟指定输入现场相关联的IME文件名。
ImmGetImeMenuItems获取注册在指定输入环境的IME菜单上的菜单项。
ImmGetOpenStatus检测IME是否打开。
ImmGetProperty获取跟指定输入现场相关联的IME的属性和功能。
ImmGetRegisterWordStyle获取跟指定输入现场相关联的IME所支持的样式列表。
ImmGetStatusWindowPos获取状态窗口的位置。
ImmGetVirtualKey获取跟IME处理的键盘输入消息相关联的初始虚拟键值。
ImmInstallIME安装一个IME。
ImmIsIME检测指定的输入现场是否有和它相关的IME。
ImmIsUIMessage检查IME窗口消息并发送那些消息到特定的窗口。
ImmNotifyIME通知IME有关输入环境状态已改变的消息。
ImmRegisterWord注册一个输出文字到跟指定输入现场相关联的IME的字典中去。
ImmReleaseContext销毁输入环境并解除对跟它相关联的内存的锁定。
ImmSetCandidateWindow设置有关候选列表窗口的信息。
ImmSetCompositionFont设置用来显示按键组合窗口中的字符的逻辑字体。
ImmSetCompositionString设置按键组合字符串的字符内容、属性和子串信息。
ImmSetCompositionWindow设置按键组合窗口的位置。
ImmSetConversionStatus设置当前转换状态。
ImmSetOpenStatus打开或关闭IME功能。
ImmSetStatusWindowPos设置状态窗口的位置。
ImmSimulateHotKey在指定的窗口中模拟一个特定的IME热键动作,以触发该窗口相应的响应动作。
ImmUnregisterWord从跟指定输入环境相关联的IME的字典中注销一个输出文字。

IME消息

以下列出IME中用到的消息。

WM_IME_CHAR(IME得到了转换结果中的一个字符)

WM_IME_COMPOSITION(IME根据用户击键的情况更改了按键组合状态)

WM_IME_COMPOSITIONFULL(IME检测到按键组合窗口的区域无法继续扩展)

WM_IME_CONTROL(由应用程序直接向IME发出控制请求)

WM_IME_ENDCOMPOSITION(IME完成了对用户击键情况的组合)

WM_IME_KEYDOWN(检测到“键盘上的某键被按下”的动作,同时在消息队列中保留该消息)

WM_IME_KEYUP(检测到“键盘上的某键已弹起”的动作,同时在消息队列中保留该消息)

WM_IME_NOTIFY(IME窗口发生了改变)

WM_IME_REQUEST(通知:IME需要应用程序提供命令和请求信息)

WM_IME_SELECT(操作系统将改变当前IME)


WM_IME_SETCONTEXT(输入焦点转移到了某个窗口上)

WM_IME_STARTCOMPOSITION(IME准备生成转换结果)

IME命令


以下列出IME中用到的命令(控制消息)。

IMC_CLOSESTATUSWINDOW(隐藏状态窗口)

IMC_GETCANDIDATEPOS(获取候选窗口的位置)

IMC_GETCOMPOSITIONFONT(获取用来显示按键组合窗口中的文本的逻辑字体)

IMC_GETCOMPOSITIONWINDOW(获取按键组合窗口的位置)

IMC_GETSTATUSWINDOWPOS(获取状态窗口的位置)

IMC_OPENSTATUSWINDOW(显示状态窗口)

IMC_SETCANDIDATEPOS(设置候选窗口的位置)

IMC_SETCOMPOSITIONFONT(设置用来显示按键组合窗口中的文本的逻辑字体)

IMC_SETCOMPOSITIONWINDOW(设置按键组合窗口的样式)

IMC_SETSTATUSWINDOWPOS(设置状态窗口的位置)

IMN_CHANGECANDIDATE(IME通知应用程序:候选窗口中的内容将改变)

IMN_CLOSECANDIDATE(IME通知应用程序:候选窗口将关闭)

IMN_CLOSESTATUSWINDOW(IME通知应用程序:状态窗口将关闭)

IMN_GUIDELINE(IME通知应用程序:将显示一条出错或其他信息)

IMN_OPENCANDIDATE(IME通知应用程序:将打开候选窗口)

IMN_OPENSTATUSWINDOW(IME通知应用程序:将创建状态窗口)

IMN_SETCANDIDATEPOS(IME通知应用程序:已结束候选处理同时将移动候选窗口)

IMN_SETCOMPOSITIONFONT(IME通知应用程序:输入内容的字体已更改)

IMN_SETCOMPOSITIONWINDOW(IME通知应用程序:按键组合窗口的样式或位置已更改)

IMN_SETCONVERSIONMODE(IME通知应用程序:输入内容的转换模式已更改)

IMN_SETOPENSTATUS(IME通知应用程序:输入内容的状态已更改)

IMN_SETSENTENCEMODE(IME通知应用程序:输入内容的语句模式已更改)

IMN_SETSTATUSWINDOWPOS(IME通知应用程序:输入内容中的状态窗口的位置已更改)

IMR_CANDIDATEWINDOW(通知:选定的IME需要应用程序提供有关候选窗口的信息)

IMR_COMPOSITIONFONT(通知:选定的IME需要应用程序提供有关用在按键组合窗口中的字体的信息)

IMR_COMPOSITIONWINDOW(通知:选定的IME需要应用程序提供有关按键组合窗口的信息)

IMR_CONFIRMRECONVERTSTRING(通知:IME需要应用程序更改RECONVERTSTRING结构)

IMR_DOCUMENTFEED(通知:选定的IME需要从应用程序那里取得已转换的字符串)

IMR_QUERYCHARPOSITION(通知:选定的IME需要应用程序提供有关组合字符串中某个字符的位置信息)

IMR_RECONVERTSTRING(通知:选定的IME需要应用程序提供一个用于自动更正的字符串)

IME编程中需要用到的数据结构


这里列了所有在使用输入法编辑器函数和消息时需要用到的数据结构。

CANDIDATEFORM(描述候选窗口的位置信息)

CANDIDATELIST(描述有关候选列表的信息)

COMPOSITIONFORM(描述按键组合窗口的样式和位置信息)

IMECHARPOSITION(描述按键组合窗口中的字符的位置信息)

IMEMENUITEMINFO(描述IME菜单项的信息)

RECONVERTSTRING(定义用于IME自动更正功能的字符串)

REGISTERWORD(描述一个要注册的读入信息或文字内容)

STYLEBUF(描述样式的标识符和名称)

 

 

protected override void WndProc(ref Message m) 
    int msg = m.Msg;
    if (msg == WM_IME_SETCONTEXT) //associated IME with our UserControl
       {
         if (!m.WParam.Equals(IntPtr.Zero))
          {
           bool flag = ImmAssociateContextEx(m.HWnd, IntPtr.Zero, 16);
           IntPtr hIMC = ImmGetContext(m.HWnd);
           flag = ImmSetOpenStatus(hIMC, true);
           flag = ImmReleaseContext(m.HWnd, hIMC);
          }
        }
    }
    else
    if (msg == WM_IME_CHAR) //Intercept Message to get Unicode Char
      {
        KeyPress(this, new KeyPressEventArgs(Strings.ChrW(m.WParam.ToInt32())));
        return;
      }
    }
   base.WndProc(ref m);
}

 

激活窗口关联的当前输入法:

::ImmAssociateContextEx(gamewnd, NULL, IACE_DEFAULT);

关闭窗口关联的当前输入法:

::ImmAssociateContextEx(gamewnd, NULL, NULL);

ImeConversionList:根据输入上下文的有关内容,将一个字符或者字符串转换成远东字符结果列表或者另一个字符串。
此函数的作用是在输入串和结果串之间进行变换以便于进行重新转换,所以在此函数中不应该产生任何相关的输入法编辑器消息;

 


BOOL WINAPI ImeConfigure(HKL hKL,HWND hWnd, DWORD dwMode, LPVOID lpData)
这是最后一个需要注意的接口,在显示输入法属性配置时会Windows会调用这个接口.
#ImeConfigure:此接口函数将在用户通过控制面板或系统图标设置输入法属性时被输入法管理器调用。在此函数中可以显示属性设置对话框,供用户配置输入法的可选项;

 


ImeDestroy:结束输入法编辑器的工作;

 


ImeEscape:应用程序通过调用这个函数可以直接访问某个输入法编辑器的特定功能,这些功能通常无法通过其他的IMM函数调用实现。这么做的目的主要是为了支持特定语种的函数或者IME的私有函数;

 


/**********************************************************************/
/* ImeInquire() */
/**********************************************************************/
BOOL WINAPI ImeInquire(LPIMEINFO lpImeInfo, LPTSTR lpszWndCls, DWORD dwSystemInfoFlags)
这个函数是除了DllMain后第一个会被win32 IMM调用的函数.
IMM通过调用这个函数知道你的输入法有什么特性. 比如,除了按键消息外,你是不是还想处理键放开的消息.
#ImeInquire:此接口函数在用户选择某个输入法时最先由输入法管理器调用,以获得该输入法的有关信息。函数应返回输入法编辑器的初始化信息,在IMEINFO结构中设置当前输入法的各项属性,以及当前输入法的用户界面窗口类名称;
 

 
 
/***********************************************************************/
/*系统调用这个接口来判断IME是否处理当前键盘输入 */
/*HIMC hIMC:输入上下文 */
/*UINT uKey:键值 */
/*LPARAM lKeyData: unknown */
/*CONST LPBYTE lpbKeyState:键盘状态,包含256键的状态 */
/*return : TRUE-IME处理,FALSE-系统处理 */
/*系统则调用ImeToAsciiEx,否则直接将键盘消息发到应用程序 */
/**********************************************************************/
BOOL WINAPI ImeProcessKey(HIMC hIMC,UINT uKey,LPARAM lKeyData,CONST LPBYTE lpbKeyState)
观察注释,您可以看到在个接口是用来判断用户敲击的哪个键需要处理,哪个键又应该交给系统自己处理.
如果输入法需要自己处理用户输入的键,则在这个接口中返回true,否则返回false.
#ImeProcessKey:此接口函数由输入法管理器在处理键盘事件时调用。在此函数中对键盘事件进行预处理,根据此函数的返回值,系统确定对于特定的输入上下文来说此键盘事件是否应送交输入法编辑器进行处理。如果返回TRUE,
则表示应该先把键盘消息传送到输入法编辑器进行处理,所以输入法管理器会紧接着继续调用ImeToAsciiEx函数;
如果返回FALSE,说明此消息不需要输入法编辑器进行处理,输入法管理器将其直接送到应用程序

 

 
/**********************************************************************/
/* ImeSelect() */
/* Return Value: */
/* TRUE - successful, FALSE - failure */
/**********************************************************************/
BOOL WINAPI ImeSelect(HIMC hIMC,BOOL fSelect)
在这个接口中,系统通知输入法当前是否打开了输入法输入.
一般输入法启动时会调用一次,在一些软件(如EmEditor)中提供打开与关闭输入法的功能就是通过这个接口实现的.
如果打开输入法,一般会在这个接口中做一些数据的初始化工作.
#ImeSelect:此接口函数在用户打开或关闭输入法时被调用。在此函数中对输入法上下文内容进行初始化或恢复释放。在实现某个输入法编辑器的时候,可能需要对输入法上下文中包含的内容项进行扩充,扩充后的输入法上下文称为此输入法的私有上下文。在打开输入法时,函数的fSelect参数为TRUE,需要完成对标准的输入法上下文的扩充工作,申请追加内存,预设各个内容项的初值;在关闭输入法时,fSelect参数为FALSE,需要释放为私有上下文申请的附加资源;

 


ImeSetActiveContext:如果在某个窗口中打开了输入法编辑器,那么此接口函数会在应用程序窗口获得或失去输入焦点时被调用。在此函数中可以获取当前的输入法上下文并通知输入法编辑器用户界面窗口组件,令其刷新显示;

 


ImeSetCompositionString:根据参数中给出的数据,修改写作字符串。这个函数将向输入法编辑器发送一条WM_IME_COMPOSITION.

 


/****************************************************************************************************************/
/* function:应用程序调用这个接口来进行输入上下文的转换,输入法程序在这个接口中转换用户的输入 */
/* UINT uVKey:键值,如果在ImeInquire接口中为fdwProperty设置了属性IME_PROP_KBD_CHAR_FIRST,则高字节是输入键值*/
/* UINT uScanCode:按键的扫描码,有时两个键有同样的键值,这时需要使用uScanCode来区分 */
/* CONST LPBYTE lpbKeyState:键盘状态,包含256键的状态 */
/* LPDWORD lpdwTransKey:消息缓冲区,用来保存IME要发给应用程序的消息,第一个双字是缓冲区可以容纳的最大消息条数 */
/* UINT fuState:Active menu flag(come from msdn) */
/* HIMC hIMC:输入上下文 */
/* return : 返回保存在消息缓冲区lpdwTransKey中的消息个数 */
/****************************************************************************************************************/
UINT WINAPI ImeToAsciiEx (UINT uVKey,UINT uScanCode,CONST LPBYTE lpbKeyState,LPDWORD lpdwTransKey,UINT fuState,HIMC hIMC)
这个接口可以说是输入法最重要的部分,程序员需要在这个接口中实现编码与重码的转换,转换完成或者显示在编码窗口及重码窗口,或者发送到应用程序.
由于在这个接口中没有传入窗口句柄,如何通知输入法程序的窗口更新显示呢?当然我们可以使用全局变量,在此我个人推荐的方法是使用IME消息(没有什么道理),您将消息类型参数保存到lpdwTransKey指示的缓冲区中,User.exe会根据消息类型做相应的处理并传递到UIWnd这个窗口中.
那么如何输入文字呢?要输入文字需要3个消息配合使用,分别是WM_IME_STARTCOMPOSITIONWM_IME_COMPOSITION和WM_IME_ENDCOMPOSITION,它们分别指示开始输入编码,输入编码或者结果(视参数而异)及编码输入完成.
在开始编写输入法的时候,为了省事,我的输入法在用户确定要输入一个重码时才连续调用这3个消息以向编码器中输入文字.由于WM_IME_STARTCOMPOSITION和WM_IME_ENDCOMPOSITION需要成对使用,这种方法可以确保它们配对.
最初这种方式工作得很好,但是后来发现在一些软件中出现兼容性问题如智能五笔在遨游中就存在这个问题,在遨游中的地址栏中打开智能五笔,当需要使用回退键来删除错误输入的编码时,会发现删除的不是编码窗口中的编码而是编辑器中的文字这是因为类似遨游这类软件主动接管了按键输入如处理一些控制键,当它发现这些控制键不在WM_IME_STARTCOMPOSITION和WM_IME_ENDCOMPOSITION这两个消息之间时就自己处理控制键而不是先交给User.exe了.
因此正确的流程应该是在开始输入编码时发送WM_IME_STARTCOMPOSITION,输入结束后发送WM_IME_ENDCOMPOSITION消息.
#ImeToAsciiEx:根据输入法上下文的内容,使用输入法编辑器的转换引擎产生转换结果,将相应的字符消息放入指定缓冲区中。返回值是产生消息的条数,如果此数量大于消息缓冲区的长度,系统转而从输入法上下文的hMsgBuf项中读取消息的内容。该函数和ImeProcessKey函数一起构成了键盘输入方式下输入法编辑器转换引擎的主体;

 

 
NotifyIME:系统或(IME有意识)应用程序通知输入法编辑器根据参数修改输入法编辑器的当前状态。比如:显示/隐藏候选窗口,选定某个候选项,更新候选窗口页起始位置和页尺寸,更新输入上下文内容,修改写作串内容等等;

 


ImeRegisterWord:向输入法编辑器的词典里增加一个新词;

 


ImeUnregisterWord:把某个词从此输入法编辑器的词典里去掉;

 


ImeGetRegisterWordStyle:取得本输入法编辑器支持的词风格的列表;

 


ImeEnumRegisterWord:列出符合给定条件的所有字符串。

 

/**********************************************************************/
/* UIWndProc() */
/* IME UI window procedure */
/**********************************************************************/
LRESULT WINAPI UIWndProc(HWND hUIWnd, UINT message,WPARAM wParam, LPARAM lParam)
这是一个非常重要的接口,基本上一它负责各种消息的传递.一般您需要在这个接口中根据不同的消息类型,实现输入法窗口(如编码窗口,重码窗口,状态栏窗口)的显示隐藏及更新等操作.
这个接口实现的功能可能非常复杂,视情况而异,在此就不做更加深入的说明了.在使用时可以参见示例工程.

 


StatusWndProc:状态窗口的窗口函数.
CompWndProc:写作窗口的窗口函数.
CandWndProc:候选窗口的窗口函数.

 

 

 

 

Windows下创建基于IMM的输入法

 发表于 2017-02-18 |  分类于 随笔笔记 

基于IMM框架的输入法是一个按照系统要求导出十几个函数的DLL文件,这十几个导出函数的原型定义于imm.h文件,系统定义了它们的调用时机,比如切出输入法、按键处理等。接下来就从最核心的要求开始,逐步分析基于IMM的输入法实现步骤。

要确保输入法能运转起来,需要具备四个条件:

  1. 是一个DLL文件
  2. 依照imm.h导出十几个函数
  3. 有一个UI窗口
  4. 有一系列扩展窗口
  5. 执行特定的安装过程

能最小化运转不代表能正常交互,只是技术路径上跑通了。像一个完备的输入法那样正常交互起来还要具备写作窗、候选窗,还要处理从拼音到汉字、词的转化。本节先介绍最小化运转的实现细节。

DLL文件

  1. 要在DllMainDLL_PROCESS_ATTACH分支下注册UI窗体类,系统IMM框架会负责创建该窗体,并通过向该窗体发消息让输入法知道什么时候该显示/隐藏写作窗,什么时候该显示/隐藏候选窗。关于UI窗口在下文还有详细介绍。
  2. 要在资源文件(*.rc)中设置几个key:
    FILEOS = 0x4L
    FILETYPE = VFT_DRV
    FILESUBTYPE = VFT2_DRV_INPUTMETHOD
    Block Header = 中文(简体,中国)(0x0404b0) 选择Language为Chinese(Simplified PRC)即为此设置。
  3. 包含一个icon图标,该图标用于在系统语言栏显示创建的输入法。

导出函数

引入imm.h文件

这些函数原型定义在imm.h,需要注意该文件有两份定义,一份来自DDK,一份来自SDK。开发输入法时应使用DDK版本。这个文件,在不同的Windows DDK版本中命名发生过变化,最早是imm.h,在win2kDDK的某个版本中就变成了immdev.h,后来的Windows DDK一直沿用immdev.h

使用imm.h会导致和SDK版本重名,确实不方便。这要求包含该文件的时候必须这么写:

1
2
3
4
5
# define NOIME
#include <windows.h>
#include "imm.h"
#define _DDKIMM_H_
...

 

windows.h必须写在imm.h前面,因为imm.h依赖windows.h,可是在windows.h中又包含了SDK版本的imm.h

1
2
3
4
5
6
// windows.h
...
#ifndef NOIME
#include <imm.h>
#endif
...

 

因此,我们在#include <windows.h>前必须先定义NOIME,让windows.h中的imm.h无效。
如果使用immdev.h,则不存在头文件命名冲突的问题。不过我在编写输入法时,还是使用老版本的imm.h,因为不确定使用immdev.h能否完全与老的Windows系统兼容。

定义十五个导出函数

关于这些函数的介绍,可以参见win2kddk,这个版本的DDK在Ntddk/src/ime/docs目录下有两份输入法开发文档,这也是为数不多的微软发布的输入法开发官方文档。我们只挑最必须的文件重点介绍。

ImeInquire

该函数在输入法首次切出时被调用,负责处理输入法的初始化,它返回一个IMEINFO结构体以及输入法的UI窗体类名。这里的实现过程为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
BOOL WINAPI ImeInquire(LPIMEINFO lpImeInfo, LPTSTR lpszUIClass, LPCTSTR lpszOptions)
{
  // 如果宿主进程为Winlogon,则直接退出
  if((DWORD_PTR)lpszOptions & IME_SYSINFO_WINLOGON )
    return FALSE;

  lpImeInfo->dwPrivateDataSize  = 0; //sizeof(t_uiExtra);

  lpImeInfo->fdwProperty  = IME_PROP_COMPLETE_ON_UNSELECT | 
    IME_PROP_SPECIAL_UI | IME_PROP_CANDLIST_START_FROM_1 |
    IME_PROP_UNICODE | IME_PROP_KBD_CHAR_FIRST;                 // 输入法属性

  lpImeInfo->fdwConversionCaps  = IME_CMODE_SYMBOL | 
    IME_CMODE_SOFTKBD | IME_CMODE_FULLSHAPE;                    // 转换模式
  lpImeInfo->fdwSentenceCaps    = IME_SMODE_NONE;               // 句子模式
  lpImeInfo->fdwUICaps          = UI_CAP_SOFTKBD| UI_CAP_2700;  // UI标记
  lpImeInfo->fdwSCSCaps         = 0x00000000;
  lpImeInfo->fdwSelectCaps      = 0x00000000;

  // 窗体类名
  _tcscpy_s(lpszUIClass, MAX_CLASSNAME_UI, UIWnd::GetUIWndClassName());

  return TRUE;
}

 

系统通过该函数返回的窗体类名创建输入法UI窗体。

ImeProcessKey

每当产生一个按键操作,IMM会调用该函数,输入法根据按键预判断是否要处理,如果处理返回TRUE;否则返回FALSE。这里我们只处理字符A~Z以及回车、空格和ESC:

1
2
3
4
5
6
7
8
9
10
11
12
13
BOOL WINAPI ImeProcessKey(HIMC hImc, UINT unVirtKey, DWORD unScanCode, CONST LPBYTE achKeyState)
{
  ImcHandle imcHandle(hImc);
  Comp* pComp = imcHandle.GetComp();
  LPTSTR szCompString = pComp->GetCompString();
  if (unVirtKey >= 0x41 && unVirtKey <= 0x5A) {
    return TRUE;    // 从A到Z
  }
  if (_tcslen(szCompString) > 0 &&(unVirtKey == VK_RETURN || unVirtKey == VK_SPACE || unVirtKey == VK_ESCAPE)) {
    return TRUE;  // 当有写作串且当前按键为回车、空格或ESC
  }
  return FALSE;
}

 

ImeToAsciiEx

如果经过上一步的输入法预判断,需要处理,IMM则继续调用该函数进入处理逻辑;如果不需要处理,则不会调用该函数,而是直接把按键以WM_KEYDOWN/WM_KEYUP的形式发给应用程序。这里的处理分三个步骤:

  1. 处理按键,通常要追加当前的输入内容进入写作串;
  2. 完成转换,这是输入法最核心的部分,根据写作串里的拼音转成汉字;
  3. 完成界面的更新,只需要组装相应的消息到lpdwTransBuf指向的数组中,IMM会把这些消息发送给输入法UI窗口,由UI窗口继续完成界面的处理。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    
    UINT WINAPI ImeToAsciiEx(UINT unKey, UINT unScanCode, CONST LPBYTE achKeyState, LPDWORD lpdwTransBuf, UINT fuState, HIMC hImc)
    { 
      
      ImcHandle imcHandle(hImc);
      Comp* pComp = imcHandle.GetComp();
      LPTSTR szCompString = pComp->GetCompString();
      COMPOSITIONSTRING& compCore = pComp->GetCore();
      size_t ccOriginCompLen = _tcslen(szCompString);
    
      if (HIWORD(unKey) >= 'a' && HIWORD(unKey) <= 'z') {
        TCHAR szKey[2] = { HIWORD(unKey), 0 };
        _tcscat_s(szCompString, Comp::c_MaxCompString, szKey);  // 将字符追加到写作串
        compCore.dwCompStrLen = (DWORD)_tcslen(szCompString);
      }
    
      const DWORD dwBufLen = *lpdwTransBuf;
      lpdwTransBuf += sizeof(size_t) / sizeof(DWORD);
      UINT cMsg = 0;
      LPTRANSMSG lpTransMsg = (LPTRANSMSG)lpdwTransBuf;
      if(ccOriginCompLen == 0){  // 没有写作串
        if(HIWORD(unKey) >= 'a' && HIWORD(unKey) <= 'z'){
          lpTransMsg[0].message = WM_IME_STARTCOMPOSITION;  // 打开写作窗
          lpTransMsg[0].wParam = 0;
          lpTransMsg[0].lParam = 0;
          cMsg++;
    
          lpTransMsg[1].message = WM_IME_COMPOSITION; // 更新写作窗
          lpTransMsg[1].wParam = 0;
          lpTransMsg[1].lParam = GCS_COMPSTR | GCS_CURSORPOS | GCS_COMPATTR;
          cMsg++;
    
          lpTransMsg[2].message = WM_IME_NOTIFY;      // 打开候选窗
          lpTransMsg[2].wParam = IMN_OPENCANDIDATE;
          lpTransMsg[2].lParam = 1;
          cMsg++;
    
          lpTransMsg[3].message = WM_IME_NOTIFY;      // 更新候选窗
          lpTransMsg[3].wParam = IMN_CHANGECANDIDATE;
          lpTransMsg[3].lParam = 1;
          cMsg++;
          return cMsg;
        }
      }else{ // _tcslen(szCompString) > 0     // 有写作串
        if(HIWORD(unKey) >= 'a' && HIWORD(unKey) <= 'z'){
          // 更新写作窗
          ...
          // 更新候选窗
          ...
          return cMsg;
        }else if(HIWORD(unKey) == VK_RETURN || HIWORD(unKey) == VK_SPACE){ // 回车或空格
          LPTSTR szResultString = pComp->GetResultString();
          _tcscpy_s(szResultString, Comp::c_MaxResultString, szCompString); // 将写作串拷入结果串
          compCore.dwResultStrLen = (DWORD)_tcslen(szResultString);
          memset(szCompString, 0, sizeof(TCHAR) * Comp::c_MaxCompString);   // 清空写作串
          compCore.dwCompStrLen = 0;
          // 更新写作窗
          ...
          // 关闭写作窗
          ...
          // 关闭候选窗
          ...
          return cMsg;
        }else if(HIWORD(unKey) == VK_ESCAPE){ // ESC
          memset(szCompString, 0, sizeof(TCHAR) * Comp::c_MaxCompString);
          compCore.dwCompStrLen = 0;
          // 更新写作窗
          ...
          // 关闭写作窗
          ...
          // 关闭候选窗
          ...
          return cMsg;
        }
      }
      return cMsg;
    }
    

以上就是输入法最最关键的三个导出函数,即使一个丰满的输入法,主要逻辑也是在这几个函数中,尤其是ImeProcessKeyImeToAsciiEx中。
特别注意
在DDK的immdev.h中定义的数据类型LPTRANSMSGLIST是错误的,它定义缓冲区长度TRANSMSGLIST::uMsgCount的类型为UINT,其实该字段的长度在32位和64位系统下不一样,32位下是4字节,64位下是8字节。因此在上面代码中有:

1
lpdwTransBuf += sizeof(size_t) / sizeof(DWORD);

 

UI窗口

概述

输入法可分为框架层和逻辑层,框架层定义了控制流、数据流的流转路径,以及控制类型。比如,当一个按键被按下,首先由导出函数ImeProcessKey、ImeToAsciiEx处理,之后再通过IMM消息通知UI窗口,这些都属于控制流。通知UI窗口的消息则属于控制类型,比如显示/隐藏写作窗、显示/隐藏候选窗。以上这些都是框架层的工作,而在业务层则负责处理具体收到显示/隐藏写作窗的时候怎么显示,显示在哪,等等。

通常我们能看到的输入法窗口都属于业务逻辑层的范畴,而不是框架层。它们都是UI窗口的子窗口,你可以根据自己业务逻辑的需要决定创建多少个子窗口,并决定怎么显示它们。

UI窗口是框架层和业务层的桥梁——框架层把消息发送给UI窗口,由它决定要不要告诉业务层的子窗体,也由它来控制要不要显示或隐藏这些窗体。

注册窗体类

既然是Windows窗体,必然包含注册窗体类、创建窗体、执行窗体函数这三个关键步骤。注册窗体类是在DllMain函数的DLL_PROCESS_ATTACH分支中完成,在DLL_PROCESS_DETACH分支完成注销窗体类。和普通的自定义窗体类不同在于style字段和cbWndExtra字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void UIWnd::RegisterUIWndClass(HINSTANCE hInstance)
{
  WNDCLASSEX wc = { 0 };
  wc.cbSize = sizeof(WNDCLASSEX);
  wc.style = CS_IME;  // 注意1
  wc.lpfnWndProc = UIWndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 2 * sizeof(LONG_PTR); // 注意2:系统要存放IMMGWL_IMC 和 IMMGWL_PRIVATE
  wc.hInstance = hInstance;
  wc.hCursor = NULL;
  wc.hIcon = NULL;
  wc.lpszMenuName = NULL;
  wc.lpszClassName = GetUIWndClassName();
  wc.hbrBackground = NULL;
  wc.hIconSm = NULL;
  ATOM atomUi = RegisterClassEx(&wc);
  mhInstance = hInstance;
}

 

创建窗体

输入法UI窗体和普通自定义窗体最不同之处在于窗体的创建,自定义窗体要通过调用CreateWindow(...)函数来完成创建,但输入法UI窗体则是由系统负责创建的。该函数的第一个参数要传入窗体类名,系统怎么知道窗体类名的呢?前面已经讲过:在导出函数ImeInquire那里。

窗体函数

窗体函数中要注意:不要让WM_IME_xxx类的消息交给DefWindowProc函数来处理,因为它会把这类消息再交给输入法窗口,这会导致死循环。对于不处理的WM_IME_xxx消息,返回0即可。

与普通用户窗体的不同之处在于他要处理一系列的WM_IME_xxx消息,以响应来自ImeToAsciiEx的消息——显示或隐藏写作窗、候选窗、状态栏以及更新它们。本文我们先不着急引入这些窗口,你会看到当敲字母键的时候没有反应,而实际上输入法把这些字母“吃掉”了,再按空格或回车会一次上屏,按ESC则清空“吃掉”的字母,进入重新输入的状态。

扩展窗口

扩展窗口包括写作窗、候选窗、状态栏等。这些窗口需要具备一些共同的特征:

  1. 关于窗体类风格,需要指定CS_IME标记。但我觉得其实只有输入法UI窗口用该风格,其余的扩展窗口应该不用,因为它们的创建是由开发者创建的。
  2. 在创建输入法窗体的时候,必须指定窗体风格为WS_DISABLED。这是因为输入法窗体不能接受输入焦点,否则就又会激活输入法,输入逻辑就嵌套了。
  3. 这些窗口可以在输入法UI窗口的WM_CREATE函数中完成注册和创建。
  4. 这些窗口里要现实的内容需要作为IMCC在IMC中创建。IMC中有一些默认的IMCC是不需要在此创建的,比如:
    1
    2
    3
    4
    5
    6
    7
    8
    
    typedef struct tagINPUTCONTEXT { 
      ...
      HIMCC   hCompStr; 
      HIMCC   hCandInfo; 
      HIMCC   hGuideLine; 
      HIMCC   hPrivate; 
      ...
    } INPUTCONTEXT, *PINPUTCONTEXT, NEAR *NPINPUTCONTEXT, FAR *LPINPUTCONTEXT;
    

ImeSelect函数中首次获得IMC,你可以尝试获得这些IMCC的尺寸,发现是非0的,说明这些IMCC已经存在了。但是为了适配自己的业务逻辑,我在ImeSelect中初始化IMC,调用ImmReSizeIMCC(m_pContext->hCompStr, sizeof(Comp));,用自己的Comp类替换掉了原先的hCompStr。

安装

输入法的安装要做两件事:1、将ime文件拷贝到Windows/System32目录下;2、调用ImmInstallIME注册该输入法。
需要注意,在64位机器下,应该为32位和64位生成两份ime文件,这样在64位和32位的应用程序里才能分别切出对应的输入法。64位ime文件放在Windows/System32下,32位放在Windows/SysWOW64下。

通常这些活是由NSIS脚本干的,此处我写了一个ImeInstall程序来做,注意:因为要往Windows/System32下写文件,该程序必须具备管理员权限,因此在CMake文件中添加如下链接选项:

1
SET_TARGET_PROPERTIES(ImeInstaller PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false'")

 

本文代码已提交至WinImmImeSample-MiniIme

  • TSF(Text Services Framework)和输入法
  • 时间: 2017-07-25    阅读数: 3110
  • 1.TSF输入接口

    IMM-IME架构成熟,稳定,易于实现,在Windows中被广泛使用,甚至在Linux曾大量使用的中文输入接口SCIM中也可以看到IMM-IME的影子。但是由于IMM-IME在操作权限管理上有些欠缺,输入法文件的扩展名是IME,但是实质是DLL,使得恶意软件通过该接口挂接到应用程序成为可能。为了提高安全性、规范性,Microsoft设计了基于COM(Component Object Model)的TSF(Text Services Framework)接口。

    TSF是文本服务框架的简称,基于TSF的每一个输入法IME都是COM组件。图1显示了TSF与应用程序之间的关系, TSF管理器由操作系统提供并且不可被替换,它是应用程序和IME之间的中介。基于安全的考虑,IME也不能直接和应用程序打交道,TSF将输入事件传达给IME并在用户选择字符后接受从 IME 返回的输入字符。与IMM-IME类似,TSF中的IME同样可以由第三方开发。

    Windows 8/8.1和10开始提供了Metro界面和传统桌面两种工作模式,在传统桌面的工作模式中同时支持IMM-IME和TSF两种架构,但在Metro界面中就只支持TSF架构的输入法了,因此基于TSF的输入法必将淘汰IMM-IME结构的输入法。

    支持TSF的应用程序可以在不用感知文本设备的前提下,接受从不同文本设备(键盘、手写、语音等)来源的文本输入。此外,TSF的一个重要优势是对输入法权限的管控程度远远高于IMM-IME,大大有利于系统的安全性。

    图1 TSF架构

    2.COM (Component Object Class

    COM提供了一种在不同的应用程序和语言之间共享二进制代码的规范,COM本身的设计是和具体程序设计语言无关,因此不依赖任何语言层面的特征。使用COM编写的二进制模块必须适应与支持一些特定的数据结构,COM规范同样也定义COM对象在内存中应该如何被组织与安排。

    COM基本术语

    1)接口interface:接口可以看成是包含一组待实现方法的类。接口名通常以I开头,在C++中,接口就是包含纯虚函数的抽象类。

    2)组件对象类coclass:它的全称是component object class,coclass用来实现接口。一个COM对象是coclass类在内存中的一个实例。

    3) 组件服务器COM Server:含有一个或多个coclass的二进制模块,可以是DLL文件也可以是EXE文件。

    4)Registration:向Windows注册、登记COM Server的位置和入口。

    5)Unregistration:是Registration的逆过程,从Windows中移除掉这个登记。

    6)GUID:它的全称是globally unique identifier,是一个128位的无符号数,用于唯一标记接口和组件对象类,有时GUID也被称为UUID(universally unique identifier)。一个coclass的class ID称为CLSID;一个interface的interfaceID称为IID。

    基接口(base interface):IUnknown

    每一个COM接口都要从IUnknown接口继承,IUnknown接口提供了两个非常重要的特性:生存期控制和接口查询。IUnknown接口是所有COM接口的根。该接口用C++描述如下:

    class IUnknow

    {

    public:

          virtual  HRESULT    _stdcall Queryface(const IID& iid,void **ppv) = 0;

          virtual  ULONG       _stdcall AddRef() = 0;

          virtual  ULONG       _stdcall  Release() = 0;

    };

    可见这个接口有三个方法: QueryInterface、AddRef和Release。QueryInterface用于在获取某个COM的一个接口指针后,查询该COM的其它接口指针。AddRef用于增加引用计数,Release用于减少引用计数。

    3.物理键盘与软键盘

    由于Windows 8/10的手持设备通常没有物理键盘,因此触控键盘成为了TSF输入法必要组成部分。Windows 8的TSF触摸键盘支持七种布局,其中三种属于经典布局,四种属于触摸优化布局。经典布局的外观和行为和物理键盘更加相似,四种优化布局是分别针对朝鲜语、日语、简体中文和繁体中文而设计的。图2就是在简体中文优化布局下的数字触控键盘。

    图2 简体中文优化布局下的数字触控键盘

    ITfFnGetPreferredTouchKeyboardLayou接口的函数GetLayout是一个虚函数,可以通过重写该函数实现为TSF输入法指定触控键盘布局,但是TSF不允许用户自定义自己的触控键盘布局。表1列出了GetLayout指定触控键盘的参数,具体细节可以到MSDN中查询。

    表1 七种触控键盘布局

    布局定义

    语系

    TKBL_CLASSIC_TRADITIONAL_CHINESE_PHONETIC

    0x0404

    CHT

    TKBL_CLASSIC_TRADITIONAL_CHINESE_CHANGJIE

    0xF042

    CHT

    TKBL_CLASSIC_TRADITIONAL_CHINESE_DAYI

    0xF043

    CHT

    TKBL_OPT_JAPANESE_ABC

    0x0411

    JPN

    TKBL_OPT_KOREAN_HANGUL_2_BULSIK

    0x0412

    KOR

    TKBL_OPT_SIMPLIFIED_CHINESE_PINYIN

    0x0804

    CHS

    TKBL_OPT_TRADITIONAL_CHINESE_PHONETIC

    0x0404

    CHT

    实验原理

    4.TSF输入接口

    TSF设计了十余个接口,表2中列出了最主要的输入接口,编写TSF的输入法需要继承并实现这些接口。

    表62 TSF输入法接口

    接口

    说明

    ITfTextInputProcessorEx

    TSF管理器通过该接口激活或冻结输入法。

    ITfThreadMgrEventSink

    用于接收线程管理器的事件通知。

    ITfKeyEventSink

    输入法用来接收和拦截键盘与焦点通知。

    ITfCompositionSink

    处理输入码编辑被终止的情况。

    ITfDisplayAttributeProvider

    TSF管理器通过该接口获取独立显示对象

    ITfActiveLanguage-
       ProfileNotifySink

    输入法被选择。

    ITfThreadFocusSink

    线程获取或者失去焦点

    ITfFunctionProvider

    用于枚举提供的函数对象。

    ITfFnGetPreferredTouch-
       KeyboardLayout

    用于选择输入法所要使用的虚拟键盘的布局。

    ITfTextEditSink

    支持实现包含读写权限的编辑Session。

    TSF输入法的类需要继承上述的接口,并对接口中的必要函数进行定制与实现。下面对其中最重要的ITfKeyEventSink进行简要介绍,表3列出了该接口的主要函数以及说明。

    表3 ITfKeyEventSink接口的函数说明

    函数

    函数说明

    OnSetFocus

    当输入法得到或者失去键盘焦点时自动调用

    OnPreservedKey

    当发生系统保留键事件。

    OnTestKeyDown 

    当一个键盘KeyDown事件发生时,调用本函数来判断输入法是否需要一个按键。

    OnKeyDown

    如果OnTestKeyDown   认为需要,则调用本函数。

    OnTestKeyUp

    与OnTestKeyDown类似

    OnKeyUp

    与OnKeyDown类似

    值得说明的是:虽然一次按键会发生KeyDown和KeyUp消息,但是输入法通常只要选择处理其中一类消息。例如纵横输入法就是重点实现了OnTestKeyDown和OnKeyDown两个函数。不同的输入法运行机制类似,但是对于按键的处理各有不同,因此每种输入法都需要对这两个函数深度定制。

    5.TSF输入法部署

    目前,64位的计算机已经成为主流,但是为了和32位的程序兼容,Windows 8/10 X64中也允许安装与运行32位程序。为了保证用户能够便捷地在不同程序中使用输入法,因此需要编译输入法的X64和X86的两个版本并发布到用户计算机中,因为64位的应用程序只能使用64位的输入法,32位的程序必须使用32位的输入法。

    64位的输入法可以部署到用户计算机的Windows安装文件夹下的System32子文件夹,32位的版本部署到Windows安装文件夹下的SysWOW64子文件夹。

    6.TSF输入法注册

    由于TSF的输入法是一个COM组件,因此注册的方法和COM组件一样,实质是在注册表HKEY_CLASSES_ROOT中建立类似如下的键:

    [HKEY_CLASSES_ROOT\CLSID\{D2291A80-84D8-4641-9AB2-BDD1472C846B}]

    @=纵横汉字输入法

    [HKEY_CLASSES_ROOT\CLSID\{D2291A80-84D8-4641-9AB2-BDD1472C846B}\InProcServer32]

    @=C:\\WINDOWS\\system32\\CKCIme.dll

    ThreadingModel=Apartment

    {D2291A80-84D8-4641-9AB2-BDD1472C846B}是一个用于区分COM组件的全球唯一ID,开发人员可以根据需要自己需要定制与修改。

    32位版本的输入法在注册表注册信息如下:

    [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{D2291A80-84D8-4641-9AB2-BDD1472C846B}]

    @=纵横汉字输入法

    [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{D2291A80-84D8-4641-9AB2-BDD1472C846B}\InProcServer32]

    @=C:\\WINDOWS\\SysWow64\\CKCIme.dll

    ThreadingModel=Apartment

    但是Microsoft不建议用户直接写注册表注册输入法,而是希望大家通过调用系统提供的ITfInputProcessorProfileMgr的RegisterProfile方法进行注册,该方法原型如下:

    HRESULT RegisterProfile(

      REFCLSID rclsid,     //输入法服务的唯一CLSID号

      LANGID   langid,   //语系ID

      REFGUID  guidProfile,

      const WCHAR    *pchDesc,

      ULONG    cchDesc,

      const WCHAR    *pchIconFile, //图标文件名

      ULONG    cchFile,

      ULONG    uIconIndex,  //图标索引号

      HKL      hklSubstitute,

      DWORD    dwPreferredLayout,    //未使用,必须填0

      BOOL     bEnabledByDefault,

      DWORD    dwFlags

    );

    7.输入法的权限问题

    这里的权限主要包括网络权限和文件系统访问权限。TSF输入法的权限取决于使用它的用户以及应用程序,在应用启动的时候,TSF为用户当前选择的IME加载COM组件。当IME加载到Windows应用商店应用时,它受与该应用相同的权限限制。例如:如果使用TSF输入法的应用程序不具有Internet访问权,则TSF输入法也不可以访问 Internet。因此一些IMM-IME输入法的自动通过网络升级码本数据的功能在TSF中需要重新设计。

    另外大多数输入法都需要使用码本文件,码本存储了一个输入法的输入码和机内码对应关系。桌面应用程序通常对文件系统的访问权限相对高,而基于应用商店的应用对计算机文件系统的访问权限非常有限,因此为了保证码本文件总能被合法访问,建议存储到系统的“Program Files”文件夹中的子文件夹。IMM-IME输入法经常提供的在输入时自动或者手动为用户添加自定义词组的功能因为需要写文件权限,在TSF中也可能会失败。

     

     

    输入法编程指南
    原创Augusdi 发布于2009-10-08 23:51:00 阅读数 1356  收藏
    展开
    输入法编程指南(根据msdn翻译)
     
    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支持应用程序,具有以下优点(与对应的Windows3.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 
    ————————————————
     

    WM_IME_CHAR 与WM_CHAR的区别
    原创清水迎朝阳 发布于2012-06-20 16:24:39 阅读数 15254  收藏
    展开
     

          要理解二者的区别,首先需要清楚:   我们通过键盘所打的字,并不都是全部通过输入法后,转交给程序的。

          也就是说: 我们用键盘打的字有些是不经过输入法直接传送到程序中,如1、2、3这样的数字 还有ABC英文字母,回车 空格等

                                 有些是经过输入法转交给程序的,如中文

          明白这点后,WM_IME_CHAR与WM_CHAR的区别就容易理解了。

          需要说明的是: 数字和英文字母 你可以不通过输入法直接输入,也可以通过输入法进行输入

         

           WM_IME_CHAR: 所有经由输入法产生的字符都会产生WM_IME_CHAR消息。

                                              DefWindowProc会将WM_IME_CHAR转换为WM_CHAR消息

     

           WM_CHAR:            未经输入法而直接送人程序中的字符会响应WM_CHAR消息。

     

    说明:

    对于 Unicode 窗口,WM_IME_CHAR 和 WM_CHAR 没有区别,wParam 都是一个 WCHAR,即输入的字符。

    对于非 Unicode (DBCS) 窗口,WM_IME_CHAR 的 wParam 即由输入法生成的一个字符。这个字符既有可能是单字节字符也有可能是双字节字符。如果是单字节字符,那么和 WM_CHAR 没什么区别;如果是一个双字节字符,那么 wParam 高 8 位为 Leading byte,低 8 位为 Continuation Byte。

    所有经由输入法产生的字符都会产生 WM_IME_CHAR 消息而不是 WM_CHAR,但 DefWindowProc 会把 WM_IME_CHAR 转换为相应的一个或两个 WM_CHAR 消息。

    例如:

    不开输入法输入「9」 → 收到 WM_CHAR (0×0039)
    打开输入法输入「9」 → 收到 WM_IME_CHAR (0×0039) → 收到 WM_CHAR (0×0039)
    打开输入法输入「笨」 → 收到 WM_IME_CHAR (0xB1BF) → 收到 WM_CHAR (0x00B1) → 收到 WM_CHAR (0x00BF)
     

     

     

     

    编程实现:

     

          对于WM_CHAR   MFC提供了其响应函数OnChar(),  但对于WM_IME_CHAR 并没有提供其响应函数,需要我们自己去写

         下例程序显示了如何处理键盘字符消息的过程:

     

    1)定义全局数组 ,存放键盘输入的字符

        wchar_t m_ImeChar[2];
           常用字的unicode占用2个字节

            而冷僻字的unicode需要占用4个字节

           因此,  定义4自己的数组 存放字


    2)重载窗口处理过程

        virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
     

    WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch(message)
        {
        case WM_IME_CHAR:
            //对特殊字符 进行预处理
            if(PreTreat(WM_IME_CHAR,wParam,lParam)) break;
     
            //其他字符 放入数组中
            if(m_ImeChar[0]>=0xD800 && m_ImeChar[0]<=0xDBFF)
            {
                m_ImeChar[1]=(wchar_t)wParam;
            }
            else
            {
                m_ImeChar[0]=(wchar_t)wParam;
                if(m_ImeChar[0]>=0xD800 && m_ImeChar[0]<=0xDBFF)
                    break;
            }    
            OnImeChar(m_ImeChar);
            break;
        default: 
            break;
        }
        return CScrollView::WindowProc(message, wParam, lParam);
    }

     

    说明:

          unicode编码       U+0000 ~ U+FFFF                为基本多语言平面(Basic MultilingualPlane,简记为BMP)

                                        U+10000 ~ U+10FFFF        为16个辅助平面

         常用字都处在BMP内,占2个字节;而冷僻字则在BMP之外,占四个字节。

     

         常用字、冷僻字区别方法: 

                                                          BMP内,从U+D800到U+DFFF之间的Code Point区段是永久保留不映射到字符

                                                          BMP之外占用四个字节 前两个字节为高位字节,后两个字节为低位字节

                                                         前两个字节的范围为:0xD800..0xDBFF

                                                         后两个字节的范围为:0xDC00..0xDFFF

           由此可见,BMP之内的字符 和BMP之外的字符没有交集。

           因此,

              if(m_ImeChar[0]>=0xD800 && m_ImeChar[0]<=0xDBFF)

                  如果满足这个条件,则表示的是占用4个字节的冷僻字

           对于冷僻字,系统会发送两次WM_IME_CHAR消息,第一次传送其高位字节,第二次传送其低位字节

     

           当运行到OnImeChar(m_ImeChar);时, m_ImeChar存放的为真实的字(不管是常用字还是冷僻字,此时都正确保存在了此数组中)

     

          

    3)对数组中接受的字 进行处理

     

        void OnImeChar(wchar_t* Str);
     

    OnImeChar(wchar_t* Str)
    {
        。。。
    }        

    4)使用OnChar响应未经输入法输入的字符

    OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
    {
        switch(nChar)
        {
        case 0x0D:                                                    //回车键
            if(PreTreat(0x0D,NULL,NULL)) break;
     
            OnEnterKeyDown();
            Update();
            break;
        case 0x08:                                                    //退格键
            if(PreTreat(0x08,NULL,NULL)) break;
     
            if(m_paSelElem.GetSize()>0) OnDelElemsel();
            else OnBackSpaceKeyDown();
            Update();
            break;
        case 0x20:                                                    //空格键
            if(PreTreat(0x20,NULL,NULL)) break;
     
            OnSpaceKeyDown();
            Update();
            break;    
        case 0x09:                                                    //TAB键
            if(PreTreat(0x09,NULL,NULL)) break;
     
            OnTabKeyDown();
            Update();
            break;
        default:
            break;
        }
     
        if(m_paSelElem.GetSize()>0)
        {
            CleanSelElem();
        }
     
        CScrollView::OnChar(nChar, nRepCnt, nFlags);
    }
     


     

    处理原则:

                     对于WM_IME_CHAR消息,则交由WM_IME_CHAR响应函数处理

                     对于不经输入法的字符消息,则交由WM_CHAR进行处理
     

输入法 编程分析

翻译清水迎朝阳 发布于2011-10-18 14:26:23 阅读数 6598  收藏

展开

 

 

 

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

 

第一步: 对窗口进行注册

 

 
  1. BOOL WINAPI DllMain (HINSTANCE hInstDLL,

  2. DWORD dwFunction,

  3. LPVOID lpNot)

  4. {

  5. switch(dwFunction)

  6. {

  7. case DLL_PROCESS_ATTACH:

  8. g_hInst = hInstDLL;

  9.  
  10. if (!RegisterIMEClass(g_hInst)) return FALSE;

  11. LoadMB(hInstDLL);

  12. break;

  13.  
  14. case DLL_PROCESS_DETACH:

  15. UnregisterIMEClass(g_hInst);

  16. DestroyMB();

  17. break;

  18.  
  19. case DLL_THREAD_ATTACH:

  20. break;

  21.  
  22. case DLL_THREAD_DETACH:

  23. break;

  24. }

  25. return TRUE;

  26. }


 

 

 
  1. static BOOL RegisterIMEClass( HANDLE hInstance )

  2. {

  3. WNDCLASSEX wc;

  4.  
  5. // register class of UI window.

  6. wc.cbSize = sizeof(WNDCLASSEX);

  7. wc.style = CS_PY | CS_IME;

  8. wc.lpfnWndProc = UIWndProc;

  9. wc.cbClsExtra = 0;

  10. wc.cbWndExtra = 2 * sizeof(LONG);

  11. wc.hInstance = hInstance;

  12. wc.hCursor = LoadCursor( NULL, IDC_ARROW );

  13. wc.hIcon = NULL;

  14. wc.lpszMenuName = (LPTSTR)NULL;

  15. wc.lpszClassName = UICLASSNAME;

  16. wc.hbrBackground = NULL;

  17. wc.hIconSm = NULL;

  18.  
  19. if( !RegisterClassEx( (LPWNDCLASSEX)&wc )) return FALSE;

  20.  
  21. // register class of composition window.

  22. wc.cbSize = sizeof(WNDCLASSEX);

  23. wc.style = CS_PY | CS_IME;

  24. wc.lpfnWndProc = CompWndProc;

  25. wc.cbClsExtra = 0;

  26. wc.cbWndExtra = 0;

  27. wc.hInstance = hInstance;

  28. wc.hCursor = LoadCursor( NULL, IDC_ARROW );

  29. wc.hIcon = NULL;

  30. wc.lpszMenuName = (LPTSTR)NULL;

  31. wc.lpszClassName = COMPCLASSNAME;

  32. wc.hbrBackground = NULL;

  33. wc.hIconSm = NULL;

  34.  
  35. if( !RegisterClassEx( (LPWNDCLASSEX)&wc )) return FALSE;

  36.  
  37.  
  38. // register class of candadate window.

  39. wc.cbSize = sizeof(WNDCLASSEX);

  40. wc.style = CS_PY | CS_IME;

  41. wc.lpfnWndProc = CandWndProc;

  42. wc.cbClsExtra = 0;

  43. wc.cbWndExtra = 0;

  44. wc.hInstance = hInstance;

  45. wc.hCursor = LoadCursor( NULL, IDC_ARROW );

  46. wc.hIcon = NULL;

  47. wc.lpszMenuName = (LPTSTR)NULL;

  48. wc.lpszClassName = CANDCLASSNAME;

  49. wc.hbrBackground = NULL;

  50. wc.hIconSm = NULL;

  51.  
  52. if( !RegisterClassEx( (LPWNDCLASSEX)&wc )) return FALSE;

  53.  
  54. // register class of status window.

  55. wc.cbSize = sizeof(WNDCLASSEX);

  56. wc.style = CS_PY | CS_IME;

  57. wc.lpfnWndProc = StatusWndProc;

  58. wc.cbClsExtra = 0;

  59. wc.cbWndExtra = 0;

  60. wc.hInstance = hInstance;

  61. wc.hCursor = LoadCursor( NULL, IDC_ARROW );

  62. wc.hIcon = NULL;

  63. wc.lpszMenuName = (LPTSTR)NULL;

  64. wc.lpszClassName = STATUSCLASSNAME;

  65. wc.hbrBackground = NULL;

  66. wc.hIconSm = NULL;

  67.  
  68. if( !RegisterClassEx( (LPWNDCLASSEX)&wc )) return FALSE;

  69.  
  70. return TRUE;

  71. }

  72. static void UnregisterIMEClass( HANDLE hInstance )

  73. {

  74. UnregisterClass(UICLASSNAME,g_hInst);

  75. UnregisterClass(COMPCLASSNAME,g_hInst);

  76. UnregisterClass(CANDCLASSNAME,g_hInst);

  77. UnregisterClass(STATUSCLASSNAME,g_hInst);

  78. }


 

 

第二步 设置窗口处理过程:

 

UI用户界面窗口

 
  1. LRESULT WINAPI UIWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

  2. {

  3. HIMC hIMC = {0};

  4. LPINPUTCONTEXT lpIMC = NULL;

  5. LONG lRet = 0L;

  6. // 为什么代码没有用CreateWindowEx创建UI窗口

  7. // 由IME默认窗口 通过ImeInquire函数 确定当前输入法使用的UI用户界面窗口类后,IME默认窗口会自动创建UI窗口,并将IME消息发送给UI窗口, 进而通过UIWndProc处理这些消息

  8. //

  9. g_hUIWnd = hWnd;

  10. hIMC = (HIMC)GetWindowLong(hWnd,IMMGWL_IMC);

  11.  
  12. if (!hIMC)

  13. {

  14. if (IsIMEMessage(message)) return 0;

  15. }

  16.  
  17. switch (message)

  18. {

  19. case WM_CREATE:

  20. SetCandWindowPos(-1, -1);

  21. CreateCandWindow(hWnd);

  22. break;

  23.  
  24. case WM_DESTROY:

  25. if (IsWindow(GetStatusWnd()))

  26. {

  27. DestroyWindow(GetStatusWnd());

  28. }

  29.  
  30. if (IsWindow(GetCandWnd()))

  31. {

  32. DestroyWindow(GetCandWnd());

  33. }

  34. break;

  35.  
  36. case WM_IME_SETCONTEXT:

  37. if (wParam)

  38. {

  39. if (hIMC)

  40. {

  41. lpIMC = ImmLockIMC(hIMC);

  42. if (lpIMC)

  43. {

  44. ShowCandWindow();

  45. }

  46. else

  47. {

  48. if (IsWindow(GetCandWnd()))

  49. {

  50. ShowWindow(GetCandWnd(), SW_HIDE);

  51. }

  52. }

  53. ImmUnlockIMC(hIMC);

  54. }

  55. else

  56. {

  57. if (IsWindow(GetCandWnd()))

  58. {

  59. ShowWindow(GetCandWnd(), SW_HIDE);

  60. }

  61. }

  62. }

  63. break;

  64.  
  65. case WM_IME_STARTCOMPOSITION:

  66. break;

  67.  
  68. case WM_IME_COMPOSITION:

  69. ShowCandWindow();

  70. //移动脱字符时用,将组合框、候选框通由状态条处理的关键一步

  71. break;

  72.  
  73. case WM_IME_ENDCOMPOSITION:

  74. if (IsWindow(GetCandWnd()))

  75. {

  76. ShowWindow(GetCandWnd(), SW_HIDE);

  77. }

  78. break;

  79.  
  80. case WM_IME_COMPOSITIONFULL:

  81. break;

  82.  
  83. case WM_IME_SELECT:

  84. break;

  85.  
  86. case WM_IME_CONTROL:

  87. break;

  88.  
  89. case WM_IME_NOTIFY:

  90. lRet = NotifyHandle(hIMC, hWnd,message,wParam,lParam);

  91. break;

  92.  
  93. default:

  94. return DefWindowProc(hWnd,message,wParam,lParam);

  95. }

  96. return lRet;

  97. }


 

Composition窗口过程

 

 
  1. LRESULT WINAPI CompWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

  2. {

  3. switch (message)

  4. {

  5. case WM_PAINT:

  6. case WM_SETCURSOR:

  7. case WM_MOUSEMOVE:

  8. case WM_LBUTTONUP:

  9. case WM_RBUTTONUP:

  10. break;

  11. default:

  12. if (!IsIMEMessage(message))

  13. {

  14. return DefWindowProc(hWnd,message,wParam,lParam);

  15. }

  16. break;

  17. }

  18. return 0L;

  19. }


candadate window窗口处理过程

 

 
  1. LRESULT WINAPI CandWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

  2. {

  3. switch (message)

  4. {

  5. case WM_PAINT:

  6. PaintCandWindow(hWnd);

  7. break;

  8.  
  9. case WM_SETCURSOR:

  10. case WM_MOUSEMOVE:

  11. case WM_LBUTTONUP:

  12. case WM_RBUTTONUP:

  13. break;

  14.  
  15. default:

  16. if (!IsIMEMessage(message))

  17. {

  18. return DefWindowProc(hWnd,message,wParam,lParam);

  19. }

  20. break;

  21. }

  22. return 0L;

  23. }


 

status window窗口 处理过程

 

 
  1. LRESULT WINAPI StatusWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

  2. {

  3. HDC hPainthDC = NULL;

  4. HDC hDC = NULL;

  5. PAINTSTRUCT ps = {0};

  6.  
  7. hDC = GetDC(hWnd);

  8.  
  9. switch (message)

  10. {

  11. case WM_CREATE:

  12. g_hBmpStatus = LoadBitmap(GetInstance(), MAKEINTRESOURCE(STATUSBITMAP));

  13. break;

  14.  
  15. case WM_PAINT:

  16. hPainthDC = BeginPaint(hWnd, &ps);

  17. PaintStatusWindow(hPainthDC, g_dwImeStatus);

  18. EndPaint(hWnd, &ps);

  19. break;

  20.  
  21. case WM_SETCURSOR:

  22. GetWindowRect(hWnd, &g_drc);

  23.  
  24. if (!SetDragRgn(hWnd, message, wParam, lParam, &g_ptdif, &g_drc, &g_DragFlg))

  25. {

  26. HIMC hIMC;

  27. DWORD dwImePos = 0;

  28.  
  29. SetCursor(LoadCursor(NULL, IDC_ARROW));

  30. dwImePos = CheckButtonPos(hWnd, lParam);

  31. if (hIMC = GethIMC())

  32. {

  33. SetIMEStatus(dwImePos, 0);

  34. SetIMEOpenStatus(hIMC);

  35. ImmUnlockIMC(hIMC);

  36. }

  37. }

  38.  
  39. if (HIWORD(lParam) == WM_LBUTTONDOWN)

  40. {

  41. SetCapture(hWnd);

  42. g_fCaptrue = TRUE;

  43. }

  44.  
  45. break;

  46.  
  47. case WM_LBUTTONDOWN:

  48. break;

  49.  
  50. case WM_MOUSEMOVE:

  51. SetDragRgn(hWnd, message, wParam, lParam, &g_ptdif, &g_drc, &g_DragFlg);

  52. break;

  53.  
  54. case WM_LBUTTONUP:

  55. if (!SetDragRgn(hWnd, message, wParam, lParam, &g_ptdif, &g_drc, &g_DragFlg))

  56. {

  57. PaintStatusWindow(hDC, g_dwImeStatus);

  58. }

  59.  
  60. if (g_fCaptrue)

  61. {

  62. ReleaseCapture();

  63. g_fCaptrue = FALSE;

  64. }

  65. break;

  66.  
  67. case WM_DESTROY:

  68. DeleteObject(g_hBmpStatus);

  69. break;

  70.  
  71. case WM_MOVE:

  72. break;

  73.  
  74. default:

  75. if (!IsIMEMessage(message))

  76. {

  77. return DefWindowProc(hWnd, message, wParam, lParam);

  78. }

  79. break;

  80. }

  81.  
  82. ReleaseDC(hWnd, hDC);

  83. return 0;

  84. }


 

注册 了 UI窗口 写作窗口 候选窗口 状态窗口

 

         但我在分析输入法源程序时发现:程序只CreateCandWindow、CreateStatusWindow 创建了候选窗口和状态窗口 这两个窗口。

         我明白  写作窗口放在在候选窗口中处理了,但不明白 为什么没有创建UI窗口,因为UI窗口负责处理输入法消息,如果没有创建此窗口,那怎么会调用此窗口的窗口过程来处理输入法消息呢?

 

        又查了许多资料,结合源码分析,终于知道了原因所在:  当选择某个输入法时,IMM 首先调用ImeInquire  用以获得输入法相关信息。其中就包括输入法的用户界面UI窗口类名,从而据此自动创建用户界面窗口,完成初始化等工作。

 

 

     下面是IME转换界面15个接口函数的实现,在路路通源程序的基础上,给其添加了一些说明

 

 

 
  1. ///

  2. //

  3. // 函 数:1

  4. //

  5. //

  6. //

  7. //

  8. //

  9. //

  10. // 参 数:

  11. //

  12. // 作 用:刚选择某输入法时,IMM首先调用此函数用以获得IME相关信息

  13. //

  14. // 返回值:

  15. //

  16. // 备 注:

  17. //

  18. /

  19.  
  20. BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo,LPTSTR lpszUIClass,LPCTSTR lpszOption)

  21. {

  22. if (!lpIMEInfo) return (FALSE);

  23.  
  24. lpIMEInfo->dwPrivateDataSize = 0;

  25. lpIMEInfo->fdwProperty = IME_PROP_KBD_CHAR_FIRST |

  26. #ifdef _UNICODE

  27. IME_PROP_UNICODE |

  28. #endif

  29. IME_PROP_SPECIAL_UI |

  30. IME_PROP_END_UNLOAD; //会让输入法随应用程序的退出而退出

  31.  
  32. lpIMEInfo->fdwConversionCaps = IME_CMODE_FULLSHAPE | IME_CMODE_NATIVE;

  33. lpIMEInfo->fdwSentenceCaps = IME_SMODE_NONE;

  34. lpIMEInfo->fdwUICaps = UI_CAP_2700;

  35. lpIMEInfo->fdwSCSCaps = 0;

  36. lpIMEInfo->fdwSelectCaps = SELECT_CAP_CONVERSION;

  37. _tcscpy(lpszUIClass, UICLASSNAME);

  38.  
  39. return TRUE;

  40. }

  41.  
  42.  
  43.  
  44. ///

  45. //

  46. // 函 数:2

  47. //

  48. // ,

  49. //

  50. //

  51. //

  52. //

  53. // 参 数: 在打开输入法时 fSelect为TRUE, 在关闭输入法时fSelect为FALSE

  54. //

  55. // 作 用: 在打开或关闭输入法时被调用,在此函数中对输入法上下文进行初始化或恢复释放。

  56. //

  57. // 返回值:

  58. //

  59. // 备 注:

  60. //

  61. /

  62.  
  63. BOOL WINAPI ImeSelect(HIMC hIMC, BOOL fSelect)

  64. {

  65. LPINPUTCONTEXT lpIMC;

  66. BYTE bKeyData[256] = {0};

  67.  
  68. if (!hIMC) return FALSE;

  69.  
  70. lpIMC = ImmLockIMC(hIMC);

  71.  
  72. if (!lpIMC) return FALSE;

  73.  
  74. lpIMC->fOpen = fSelect; //对输入法上下文进行初始化

  75. ImmUnlockIMC(hIMC);

  76.  
  77. if (fSelect) //打开输入法

  78. {

  79. //检测大写键状态

  80. GetKeyboardState(bKeyData);

  81. if (bKeyData[VK_CAPITAL] & 1) //切换键由低字节判断其按下还是释放

  82. {

  83. SetIMEStatus(IMEPOS_SET, VK_CAPITAL);

  84. SetIMEOpenStatus(hIMC);

  85. }

  86. }

  87. return TRUE;

  88. }

  89.  
  90.  
  91.  
  92. ///

  93. //

  94. // 函 数:3

  95. //

  96. // ,

  97. //

  98. //

  99. //

  100. //

  101. // 参 数:

  102. //

  103. // 作 用: 处理键盘消息: IMM通过此函数,对键盘消息进行分类筛选,一类可以直接发给应用程序,一类需要发送给IME进行转换

  104. //

  105. // 返回值: 返回值为FALSE,说明键盘消息被直接发送给了应用程序; 返回值为TRUE 说明键盘消息被发送给了IME,被发送IME后,IMM会立即调用ImeToAsciiEx对键盘消息进行转换。

  106. //

  107. // 备 注:

  108. //

  109. /

  110.  
  111. BOOL WINAPI ImeProcessKey(HIMC hIMC, UINT vKey, LPARAM lKeyData, CONST LPBYTE lpbKeyState)

  112. {

  113. BOOL fRet = FALSE;

  114. PrintfToStatus(_T("%x"), vKey);

  115.  
  116. fRet = ImeProcessKeyHandler(hIMC, vKey, lKeyData, lpbKeyState);

  117.  
  118. return fRet;

  119. }

  120.  
  121.  
  122.  
  123.  
  124.  
  125. ///

  126. //

  127. // 函 数:4

  128. //

  129. // ,

  130. //

  131. //

  132. //

  133. //

  134. // 参 数:

  135. //

  136. // 作 用: 输入法编程最重要部分----- 此函数将IMM传递过来的键盘消息转换为composition写作窗口中的字符串,然后再查找码表,更新候选窗口,最后选择某候选字符作为最终结果

  137. // 返回值:

  138. //

  139. // 备 注:

  140. //

  141. /

  142.  
  143. UINT WINAPI ImeToAsciiEx (

  144. UINT uVKey,

  145. UINT uScanCode,

  146. CONST LPBYTE lpbKeyState,

  147. LPDWORD lpdwTransKey,

  148. UINT fuState,

  149. HIMC hIMC)

  150. {

  151. LPINPUTCONTEXT lpIMC = NULL;

  152.  
  153. if (!(lpIMC = ImmLockIMC(hIMC))) return 0;

  154.  
  155. //如果键为释放状态,不作处理

  156. if (lpIMC->fOpen && !(uScanCode & 0x8000))

  157. {

  158. LPARAM lParam = ((DWORD)uScanCode << 16) + 1L;

  159. KeyDownHandler(uVKey, lParam);

  160. }

  161. ImmUnlockIMC(hIMC);

  162. return 0;

  163. }

  164.  
  165.  
  166.  
  167. ///

  168. //

  169. // 函 数:5

  170. //

  171. // ,

  172. //

  173. //

  174. //

  175. //

  176. // 参 数:

  177. //

  178. // 作 用: (在控制面板或其它方式中)设置输入法属性时被调用 可显示输入法属性设置对话框

  179. //

  180. // 返回值:

  181. //

  182. // 备 注:

  183. //

  184. /

  185.  
  186. BOOL WINAPI ImeConfigure(HKL hKL,HWND hWnd, DWORD dwMode, LPVOID lpData)

  187. {

  188. DialogBox(GetInstance(), MAKEINTRESOURCE(DIALOGCONFIG), hWnd, ConfigDialogProc);

  189. InvalidateRect(hWnd, NULL, FALSE);

  190. return TRUE;

  191. }

  192.  
  193.  
  194.  
  195. ///

  196. //

  197. // 函 数:6

  198. //

  199. // ,

  200. //

  201. //

  202. //

  203. //

  204. // 参 数:

  205. //

  206. // 作 用: 系统通知输入法编辑器根据参数修改输入法编辑器的当前状态。

  207. // 比如:显示/隐藏候选窗口,选定某个候选窗口,更新候选窗口页起始位置和页尺寸,更新输入上下文内容,修改写作串内容等

  208. //

  209. // 返回值:

  210. //

  211. // 备 注:

  212. //

  213. /

  214.  
  215. BOOL WINAPI NotifyIME(HIMC hIMC, DWORD dwAction, DWORD dwIndex, DWORD dwValue)

  216. {

  217. BOOL bRet = FALSE;

  218.  
  219. switch(dwAction)

  220. {

  221. case NI_OPENCANDIDATE:

  222. break;

  223. case NI_CLOSECANDIDATE:

  224. break;

  225. case NI_SELECTCANDIDATESTR:

  226. break;

  227. case NI_CHANGECANDIDATELIST:

  228. break;

  229. case NI_SETCANDIDATE_PAGESTART:

  230. break;

  231. case NI_SETCANDIDATE_PAGESIZE:

  232. break;

  233. case NI_CONTEXTUPDATED:

  234. switch (dwValue)

  235. {

  236. case IMC_SETCONVERSIONMODE:

  237. break;

  238. case IMC_SETSENTENCEMODE:

  239. break;

  240. case IMC_SETCANDIDATEPOS:

  241. break;

  242. case IMC_SETCOMPOSITIONFONT:

  243. break;

  244. case IMC_SETCOMPOSITIONWINDOW:

  245. break;

  246. case IMC_SETOPENSTATUS:

  247.  
  248. bRet = TRUE;

  249. break;

  250. default:

  251. break;

  252. }

  253. break;

  254.  
  255. case NI_COMPOSITIONSTR:

  256. switch (dwIndex)

  257. {

  258. case CPS_COMPLETE:

  259. break;

  260.  
  261. case CPS_CONVERT:

  262. break;

  263.  
  264. case CPS_REVERT:

  265. break;

  266.  
  267. case CPS_CANCEL:

  268. break;

  269.  
  270. default:

  271. break;

  272. }

  273. break;

  274.  
  275. default:

  276. break;

  277. }

  278. return bRet;

  279. }

  280.  
  281.  
  282.  
  283. ///

  284. //

  285. // 函 数:

  286. //

  287. // ,

  288. //

  289. //

  290. //

  291. //

  292. // 参 数:

  293. //

  294. // 作 用: 在输入串和结果串之间进行变换以便于可以重新转换。

  295. // 比如 输入串 a 结果串 啊 a---啊相互转换 所以,在此函数中不应该产生任何相关的输入法编辑器消息。

  296. // 返回值:

  297. //

  298. // 备 注:

  299. //

  300. /

  301. DWORD WINAPI ImeConversionList(HIMC hIMC, LPCTSTR lpSource, LPCANDIDATELIST lpCandList, DWORD dwBufLen, UINT uFlag)

  302. {

  303. return 0;

  304. }

  305.  
  306.  
  307. ///

  308. //

  309. // 函 数:

  310. //

  311. // ,

  312. //

  313. //

  314. //

  315. //

  316. // 参 数:

  317. //

  318. // 作 用: 结束输入法编辑器的工作

  319. //

  320. // 返回值:

  321. //

  322. // 备 注:

  323. //

  324. /

  325.  
  326. BOOL WINAPI ImeDestroy(UINT uForce)

  327. {

  328. return FALSE;

  329. }

  330.  
  331.  
  332. ///

  333. //

  334. // 函 数:

  335. //

  336. // ,

  337. //

  338. //

  339. //

  340. //

  341. // 参 数:

  342. //

  343. // 作 用: 应用程序可通过此函数,直接访问IME的特定功能,这些功能无法通过其他的IMM函数调用实现。

  344. //

  345. // 目 的: 为了支持特定语种的函数或者IME的私有函数

  346. // 返回值:

  347. //

  348. // 备 注:

  349. //

  350. /

  351.  
  352. LRESULT WINAPI ImeEscape(HIMC hIMC, UINT uSubFunc, LPVOID lpData)

  353. {

  354. return FALSE;

  355. }

  356.  
  357.  
  358.  
  359.  
  360.  
  361.  
  362.  
  363.  
  364.  
  365. ///

  366. //

  367. // 函 数:

  368. //

  369. // ,

  370. //

  371. //

  372. //

  373. //

  374. // 参 数:

  375. //

  376. // 作 用: 如果在某个窗口中打开了输入法编辑器,那么此接口函数会在应用程序窗口获得或失去输入焦点时被调用。

  377. // 在此函数中,可以获得当前输入法上下文,并通知IME用户窗口组件,令其刷新显示

  378. //

  379. // 返回值:

  380. //

  381. // 备 注:

  382. //

  383. /

  384. BOOL WINAPI ImeSetActiveContext(HIMC hIMC, BOOL fFlag)

  385. {

  386. return TRUE;

  387. }

  388.  
  389.  
  390.  
  391.  
  392.  
  393.  
  394. ///

  395. //

  396. // 函 数:

  397. //

  398. // ,

  399. //

  400. //

  401. //

  402. //

  403. // 参 数:

  404. //

  405. // 作 用: 向输入法编辑器的词典里增加一个新词

  406. //

  407. //

  408. // 返回值:

  409. //

  410. // 备 注:

  411. //

  412. /

  413.  
  414. BOOL WINAPI ImeRegisterWord(LPCTSTR lpRead, DWORD dw, LPCTSTR lpStr)

  415. {

  416. return FALSE;

  417. }

  418.  
  419.  
  420.  
  421. ///

  422. //

  423. // 函 数:

  424. //

  425. // ,

  426. //

  427. //

  428. //

  429. //

  430. // 参 数:

  431. //

  432. // 作 用: 把某个词从此输入法编辑器词典里去掉

  433. //

  434. // 返回值:

  435. //

  436. // 备 注:

  437. //

  438. /

  439.  
  440. BOOL WINAPI ImeUnregisterWord(LPCTSTR lpRead, DWORD dw, LPCTSTR lpStr)

  441. {

  442. return FALSE;

  443. }

  444.  
  445. ///

  446. //

  447. // 函 数:

  448. //

  449. // ,

  450. //

  451. //

  452. //

  453. //

  454. // 参 数:

  455. //

  456. // 作 用: 取得本输入法编辑器支持的词风格列表

  457. //

  458. // 返回值:

  459. //

  460. // 备 注:

  461. //

  462. /

  463.  
  464. UINT WINAPI ImeGetRegisterWordStyle(UINT nItem, LPSTYLEBUF lp)

  465. {

  466. return 0;

  467. }

  468.  
  469. ///

  470. //

  471. // 函 数:

  472. //

  473. // ,

  474. //

  475. //

  476. //

  477. //

  478. // 参 数:

  479. //

  480. // 作 用: 列出符合给定条件的所有字符串

  481. //

  482. // 返回值:

  483. //

  484. // 备 注:

  485. //

  486. /

  487.  
  488. UINT WINAPI ImeEnumRegisterWord(REGISTERWORDENUMPROC lpfn, LPCTSTR lpRead, DWORD dw, LPCTSTR lpStr, LPVOID lpData)

  489. {

  490. return 0;

  491. }

  492.  
  493.  
  494. ///

  495. //

  496. // 函 数:

  497. //

  498. // ,

  499. //

  500. //

  501. //

  502. //

  503. // 参 数:

  504. //

  505. // 作 用: 根据参数中给出的数据,修改写作字符串。此函数向编辑器发送一条WM_IME_COMPOSITION消息

  506. //

  507. // 返回值:

  508. //

  509. // 备 注:

  510. //

  511. /

  512.  
  513. BOOL WINAPI ImeSetCompositionString(HIMC hIMC, DWORD dwIndex, LPCVOID lpComp, DWORD dwComp, LPCVOID lpRead, DWORD dwRead)

  514. {

  515. return FALSE;

  516. }

  517.  


 

 

 

 

相关源码 在: http://download.csdn.net/detail/shuilan0066/3693695

IME输入法编程 第二章

2013年12月08日 ⁄ 综合 ⁄ 共 3482字 ⁄ 字号 小 中 大 ⁄ 评论关闭

 

 

 

 Windows9x系统提供的ime管理函数 2006-01-14 作者 不祥 Windows9x系统提供的ime管理函数

 

  
上一章,我们介绍了ime文件中必须设计的函数,这些函数要靠我们自己来设计。要完成这些函数的设计,需用到windows系统提供的管理函数(Input Method
Manager,简称IMM)。

IMM函数可以被IME函数使用,也可用于应用软件直接管理IME。

相关术语:

(1)input
method context 简称IMC--输入法相关部分,在这里解释为:相关连的应用程序(进程)

(2)component of the
input context 简称IMCC--IMC的部件,是INPUTCONTEXT
结构的成员。

一、IME中使用的IMM函数清单

ImmGetCompositionWindow
//取编码窗口信息

ImmSetCompositionWindow //设置编码窗口信息

ImmGetCandidateWindow //取选择窗口信息

ImmSetCandidateWindow
//设置选择窗口信息

ImmGetCompositionString
//取编码窗口的某一信息

ImmSetCompositionString
//设置编码窗口的某一信息

ImmGetCompositionFont //取编码字体

ImmSetCompositionFont
//设置编码字体

ImmGetNumCandidateList //取选择区中编码数

ImmGetCandidateList
//取选择区中编码

ImmGetGuideLine

ImmGetConversionStatus
//取当前输入法的状态(ACSII,SHAPE,FULL等)

ImmGetConversionList
//重新获得选择区转换表

ImmGetOpenStatus //取输入法打开属性

ImmSetConversionStatus
//设置输入法状态

ImmSetOpenStatus //设置输入法打开状态

ImmNotifyIME
//通报IME,输入法状态被改变

ImmGenerateMessage
//将我们的汉字串法发送到与当前输入法相关联的应用软件中
ImmRequestMessage
//向应用程序发送WM_IME_REQUEST

ImmLockIMC //获取当前IMC的INPUTCONTEXT结构信息,增加IMC
计数器
ImmUnlockIMC //释放IMC计数器
ImmGetIMCLockCount //取计数器值
ImmCreateIMCC
//创建INPUTCONTEXT结构的一个成员
ImmDestroyIMCC //删除IMC成员缓冲区
ImmLockIMCC
//取IMCC缓冲地址,同时使IMCC的计数器值增加
ImmUnlockIMCC //递减IMCC计数器
ImmReSizeIMCC
//重新设置IMC的成员的缓冲区大小
ImmGetIMCCSize //取IMC成员的缓冲区大小
ImmGetIMCCLockCount
//返回IMC计数器值
ImmGetHotKey //取输入法状态键,该函数供控制面板使用

ImmSetHotKey
//设置输入法的热键
ImmCreateSoftKeyboard //产生一个软键盘
ImmDestroySoftKeyboard
//销毁软键盘
ImmShowSoftKeyboard //显示或隐藏软键盘
二、IMM函数使用格式说明

1、BOOL WINAPI
ImmGenerateMessage( //将我们的汉字串法发送到与当前输入法相关联的应用软件中
HIMC hIMC
//与当前输入法相关联的应用软件的句柄,

//该结构的hMsgBuf项即为汉字串消息
)

成功为TRUE,否则为FALSE

2、LRESULT
WINAPI ImmRequestMessage( //向应用程序发送WM_IME_REQUEST

//只是用于w98及w2000
HIMC
hIMC, //与当前输入法相关联的应用软件的句柄
WPARAM wParam, //与WM_IME_REQUEST相关的wP
LPARAM
lParam
//与WM_IME_REQUEST相关的LP

//=IMR_COMPOSITIONWINOW

//=IMR_CANDIDATEWINDOW

//=IMR_COMPOSITIONFONT

//=IMR_RECONVERTSTRING

//=IMR_CONFIRMRECONVERTSTRING

//=IMR_QUERYCHARPOSITION

//=IMR_DOCUMENTFEED

)

3、LPINPUTCONTEXT
WINAPI ImmLockIMC( //获取当前IMC的INPUTCONTEXT结构信息,增加IMC 计数器
HIMC hIMC
//当前应用程序句柄
)

成功返回INPUTCONTEXT 结构指针,否则为NULL

4、BOOL WINAPI
ImmUnlockIMC( //释放IMC计数器
HIMC hIMC
//当前应用程序句柄
)

返回:如果IMC计数器被减少到0了,返回FALSE,否则为TRUE.

注意:ImmLockIMC与ImmUnlockIMC必须成对出现,必须是相同的HIMC

5、HIMCC
WINAPI ImmGetIMCLockCount( //取计数器值
HIMC hIMC
//当前应用程序句柄
)

如果成功返回HIMC的计数器值,否则为NULL.

6、HIMCC WINAPI
ImmCreateIMCC( //创建INPUTCONTEXT结构的一个成员
DWORD dwSize
//成员的缓冲区长度
)

如果成功返回IMC的成员句柄,否则为NULL

7、HIMCC WINAPI
ImmDestroyIMCC( //删除IMC成员缓冲区
HIMCC hIMCC
//被删除的IMC的成员
)

如果成功返回NULL,否则等于该HIMCC.

8、LPVOID WINAPI
ImmLockIMCC( //取IMCC缓冲地址,同时使IMCC的计数器值增加
HIMCC hIMCC //IMC成员句柄
)

If
the function is successful, the return value is the pointer for the IMC
component. Otherwise, the return value is NULL.

9、BOOL WINAPI
ImmUnlockIMCC( //递减IMCC计数器
HIMCC hIMCC
//IMC成员句柄
)

如果IMCC的计数器值为零,则返回 FALSE,否则为TRUE.

10、HIMCC WINAPI
ImmReSizeIMCC( //重新设置IMC的成员的缓冲区大小
HIMCC hIMCC, //IMC的成员句柄
DWORD dwSize
//新缓冲区大小
)

如果成功,返回新的HIMCC,否则为 NULL.

11、DWORD WINAPI
ImmGetIMCCSize( //取IMC成员的缓冲区大小
HIMCC hIMCC
//IMC成员句柄
)

返回IMC成员的缓冲区大小

12、DWORD WINAPI ImmGetIMCCLockCount(
//返回IMC计数器值
HIMCC hIMCC
//IMC成员的句柄
)

成功返回该IMCC的计数器值,否则为0

13、BOOL WINAPI ImmGetHotKey(
//取输入法状态键,该函数供控制面板使用
DWORD dwHotKeyID,
LPUINT lpuModifiers,
LPUINT
lpuVKey,
LPHKL lphKL
)

14、BOOL WINAPI ImmSetHotKey(
//设置输入法的热键
DWORD dwHotKeyID,
UINT uModifiers,
UINT uVKey,
hKL
hKL
)

15、HWND WINAPI ImmCreateSoftKeyboard( //产生一个软键盘
UINT uType,
//软件盘上的键码含义的定义方式

//=SOFTKEYBOARD_TYPE_T1

//=SOFTKEYBOARD_TYPE_C1
UINT
hOwner, //该输入法的UI窗口
int x, //定位坐标
int y
//定位坐标
)

成功返回软键盘的窗口句柄

16、BOOL WINAPI ImmDestroySoftKeyboard(
//销毁软键盘
HWND hSoftKbdWnd
//软年盘窗口句柄
)

成功为TRUE,法哦则为FALSE.

17、BOOL WINAPI
ImmShowSoftKeyboard( //显示或隐藏软键盘
HWND hSoftKbdWnd, //软年盘窗口句柄
int nCmdShow
//窗口状态=SW_HIDE 表示隐藏,=SW_SHOWNOACTIVATE表示显示
)

如构成功返回 TRUE. 否则为
FALSE.

Win7 64位系统,使用(IME)模式VS2010 编写 和 安装 输入法 教程(1)

转载fakine 最后发布于2016-09-09 16:08:41 阅读数 4759  收藏

分类专栏: 输入法

收起

首先感谢:
http://blog.csdn.net/shuilan0066/article/details/6884483输入法 编程分析
http://blog.csdn.net/mspinyin/article/details/6141599输入法的注册、安装和卸载
http://www.setoutsoft.cn/Html/?256.html 浅谈输入法编程
http://wenku.baidu.com/view/3d179422bcd126fff7050b9d.html输入法漫谈
这些文章作者的无私奉献。

TSF(Text Service Framework)WIN7

写这篇文章的动机是:由于Win7 64 位系统的普及,按照上面这些文章来编写自己的输入法会遇到很多意想不到的问题。至今网络上没有找到现成详细的解决方案。我通过一周的摸索和总结,深知靠自己摸索编译成功并安装一个可以在win7 64位系统下运行的输入法是多么的艰难。 特将自己的经验拿出来和大家分享。

编写输入法有几种方式,如外挂式,IME式,TSF式,今天我们主要介绍IME式( 输入法接口式(Input Method Editor-IME))

1,输入法是什么东西? 
编写输入法其实就是编写一个DLL ,一个导出一些操作系统约定函数的DLL,操作系统通过这些函数和我们的程序交互,将用户输入的编码转换成汉字通过消息传送给应用程序(如记事本,word,)。用现在的话来说就是实现一个“接口”。

(1)操作系统如何和我们的Dll交互呢?



(2)我们编写的这个Dll就是IME




输入法工作原理 如下图:




(上图来自于:http://blog.csdn.net/shuilan0066/article/details/6883629 输入法工作原理)

2,具体我们应该做哪些工作呢?
(1)首先编写一个DLL,导出IME规定的函数。
注意这个DLL除了必须导出IME规定的函数外,还要满足如下条件。
包含一个.rc 的资源文件。包含一个Version资源
如下图划红线的部分必须设置成:FILETYPE : VFT_DRV FILESUBTYPE : VFT2_DRV_INPUTMETHOD 
否则输入法无法加载。


(2)如何导出这些函数呢?
通过def文件导出函数。然后如下图设置。 使用def文件导出dll函数的好处是,导出的函数名不会变化。


 


(3)要导出哪些函数,这些函数有什么用呢?
IME要求导出的有十几个函数。可真正重要的只有几个。
输入法初始化:

ImeInquire: 刚选择某输入法时,IMM调用此函数,获得输入法相关信息
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;
}

ImeSelect: 打开或关闭输入法时被调用,在此函数中对输入法上下文进行初始化或恢复释放,
在打开输入法时 fSelect为TRUE, 在关闭输入法时fSelect为FALSE

将键盘消息转换为相应汉字:
ImeProcessKey :处理键盘消息: IMM通过此函数,对键盘消息进行分类筛选,一类可以直接发给应用程序,一类需要发送给IME进行转换 ,返回值为FALSE,说明键盘消息被直接发送给了应用程序; 返回值为TRUE 说明键盘消息被发送给了IME,被发送IME后,IMM会立即调用ImeToAsciiEx对键盘消息进行转换。

ImeToAsciiEx :输入法编程最重要部分----- 此函数将进过ImeProcessKey 筛选,通过IMM传递过来的键盘消息转换为composition写作窗口中的字符串,然后再查找码表,更新候选窗口,最后选择某候选字符作为最终结果,通过消息传递给应用程序。

(4)我们的输入法怎么显示和更新写作窗户,候选窗口,怎么把用户选择的汉字传递给应用程序,通过什么机制呢?
写作窗口:就是显示和编辑用户输入字符串的窗口,比如输入的是拼音


侯选窗口:就是显示符合用户输入编码的汉字,供用户选择。通常写作窗口和获选窗户可以合二为一。

首先我们要了解两个概念:
<1>UIWnd 和UIWndProc
<2>输入法上下文(HIMC)

我们都知道windos是通过消息来运作的,要显示,更新 输入法的写作和候选窗口,就需要有消息循环。
而输入法是一个插件,他需要依靠应用程序中用户的输入消息,来控制输入法写作和候选窗口的显示和更新。
可应用程序的消息怎样才能传递给输入法呢?
IME要求我们自建的输入法dll导出一个接口,原型如LRESULT WINAPI UIWndProc(HWND hUIWnd, UINT message,WPARAM wParam, LPARAM lParam),
而User.exe(一个系统进程) 会创建一个UIWnd ,这个窗口不会显示,就是它充当应用程序和输入法之间windos消息的传递。在这个窗口的消息循环中会调用我们输入法dll导出的接口:UIWndProc。 传递过来的HWND hUIWnd 就是这个窗口的句柄,它是输入法中创建的窗口如写作窗口,候选窗口的宿主(Owner)。
上面我们说了应用程序怎么把消息传递给输入法,下面我们说说输入法怎么把它的消息和数据传递给应用程序?
User.exe(一个系统进程) 会为应用程序分配一片内存,而HIMC就是这片内存的句柄,我们称之为输入法上下文。我们将输入法要传递给应用程序的消息和数据存入这片内存,再由User.exe 传递给应用程序。

下面我们我简单介绍一下消息和数据传递的具体方法。
首先要获得HIMC,通过UIWnd的句柄g_hUIWnd 使用ImmLockIMC
HIMC hIMC = (HIMC)GetWindowLong(g_hUIWnd, IMMGWL_IMC);
*lpIMC = (LPINPUTCONTEXT)ImmLockIMC(*hIMC)
然后向HIMC中写入消息
if (IsWindow(lpIMC->hWnd))
{

LPTRANSMSG lpTransMsg;

if (!(lpIMC->hMsgBuf = ImmReSizeIMCC(lpIMC->hMsgBuf,(lpIMC->dwNumMsgBuf+1) * sizeof(TRANSMSG))))
goto Error;

if (!(lpTransMsg = (LPTRANSMSG)ImmLockIMCC(lpIMC->hMsgBuf)))
goto Error;
//将消息写入HIMC中
lpTransMsg += (lpIMC->dwNumMsgBuf);
lpTransMsg->message=message;
lpTransMsg->wParam=wParam;
lpTransMsg->lParam=lParam;
lpIMC->dwNumMsgBuf++;

ImmUnlockIMCC(lpIMC->hMsgBuf);
//将消息发送到IME,IME再决定是自己处理还是继续发给应用程序
ImmGenerateMessage(hIMC);
}

向HIMC中写入数据,将用户选择的汉字传递给应用程序。

lpIMECompStr = (LPCOMPOSITIONSTRING)ImmLockIMCC(lpIMC->hCompStr);

if (fFlag) 
{
lpResultStr = GetResultStr();//用户选择的汉字字符串
if (!lpResultStr) return FALSE;
//将用户选择的字符串写入HIMC中
_tcscpy((LPTSTR)((LPBYTE)(lpIMECompStr) + (lpIMECompStr)->dwResultStrOffset), lpResultStr);
lpIMECompStr->dwResultStrLen = _tcslen(lpResultStr);
}
ImmUnlockIMCC(lpIMC->hCompStr); 

同时将用户选择的汉字传递给应用程序需要消息的协助。 
分别是WM_IME_STARTCOMPOSITION、WM_IME_COMPOSITION和WM_IME_ENDCOMPOSITION,它们分别指示开始输入编码,输入编码或者结果(视参数而异)及编码输入完成 ,WM_IME_STARTCOMPOSITION和WM_IME_ENDCOMPOSITION需要成对使用。
当发送WM_IME_COMPOSITION (GCS_COMPREAD|GCS_COMP|GCS_CURSORPOS|GCS_DELTASTART|GCS_RESULTREAD|GCS_RESULT),消息时候,IME将HIMC中lpIMECompStr 中的汉字通过WM_IME_CHAR, 消息传递给应用程序。或者转换成WM_CHAR消息传递给应用程序。

上面总结了输入法编程的基本原理。

3,输入法的安装和调试
64位系统 的输入法安装和32位系统不同。64位系统兼容32位系统的应用程序,可输入法不行,64位的应用程序必须有64位的输入法,而32位程序必须有32位的输入法。
所以我们要在64位系统中安装输入法,首先必须将我们的输入法dll编译成64位和32位两种。
(1)如何编译64位的程序呢?使用64位的操作系统,在VS2010中打开 菜单:生成-》配置管理器



(2)如何安装输入法? 
将编译的dll扩展名改成.ime
<1>拷贝输入法dll到系统目录。将64位编译的dll放入 c:\windows\system32 目录,将32位编译的放入C:\Windows\SysWOW64目录
<2>将输入法注册,网上说可以使用ImmInstallIME 函数,可我在64位系统上始终没有成功。于是使用手动创建注册表项来实现输入法的注册

A.在注册表中HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Keyboard Layouts,创建一个新的Key,名字为xxxx0804 (低位表示语言,这里0804表示简体中文;高位表示设备句柄,0000表示默认的physical layout,如00000804表示简体中文英文键盘)。譬如:

[HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Keyboard Layouts/E0200804]

"IME File"="***.IME" //你的输入法dll的文件名
"Layout File"="kbdus.dll"
"Layout Text"="****输入法 2010"

B. Enable这个输入法,譬如:

[HKEY_CURRENT_USER/Keyboard Layout/Preload]

"1"="00000804"
"2"="e0200804" //按照已有最大的序号+1 = 上面你创建的key的名字:E0200804

C,重新启动系统

(3)如何调试输入法
按照上面的方法按照好输入法以后,我们就可以使用VS2010对输入法进行调试。
<1> 使用附加到进程的方式



然后再窗口选择 写字板,或者word,等进程,



然后,设置断点,在记事本中选择你自己编写的输入法,输入编码,进行调试。


<2>设置命令参数为记事本程序的路径。c:\\windows\\system32\\notepad.exe


 



(4)关于编译源程序可能会遇到的问题
1、确保应用 Imm32.lib


 

2、如果 IMM.H 的位置在项目中,应在 additional include directories 中指定为./


 

 

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值