Windows95 输入法编辑器

为了防止原数据丢失,得此做一个备份
原文地址: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函数将存储于消息缓冲区里的消息发送到适当的窗口。

留下评论