分类 编程 下的文章

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

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

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

版本1.41 1999.04.01

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

下面是讲述的主题:

Overview:概述

IME User Interface --IME用户接口

IME Input Context --IME输入上下文

Generating Messages—产生消息

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

Soft Keyboard—软键盘

Reconversion—撤销和复原能力支持

IME Menu Functions—IME菜单函数

IME Help File—IME帮助文件

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

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

一、Overview:概述

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

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

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

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

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

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

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

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

注意:

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

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

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

ImmAssociateContextEx

ImmDisableIME

ImmGetImeMenuItems

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

ImmRequestMessage

ImeGetImeMenuItems

撤销和复原能力支持

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

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

IME的新旗位标志

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

IME_CMODE_FIXED

IME_SMODE_CONVERSATION

IME_PROP_COMPLETE_ON_UNSELECT

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

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

改变IME的图标和揭示消息

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

两个新的IMR消息

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

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

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

· IME_PROP_ACCEPT_WIDE_VKEY

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

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

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

不感知IME的应用程序

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

IME 半感知程序

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

IME 完全感知程序

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

BOOL WINAPI DLLEntry (

HINSTANCE    hInstDLL,

DWORD        dwFunction,

LPVOID       lpNot)

{

switch(dwFunction)

{

case DLL_PROCESS_ATTACH:

hInst= hInstDLL;

        wc.style          = CS_MYCLASSFLAG | CS_IME;

        wc.lpfnWndProc    = MyUIServerWndProc;

        wc.cbClsExtra     = 0;

        wc.cbWndExtra     = 2 * sizeof(LONG);

        wc.hInstance      = hInst;

        wc.hCursor        = LoadCursor( NULL, IDC_ARROW );

        wc.hIcon          = NULL;

        wc.lpszMenuName   = (LPSTR)NULL;

wc.lpszClassName = (LPSTR)szUIClassName;

wc.hbrBackground = NULL;

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

return FALSE;

        wc.style          = CS_MYCLASSFLAG | CS_IME;

        wc.lpfnWndProc    = MyCompStringWndProc;

        wc.cbClsExtra     = 0;

        wc.cbWndExtra     = cbMyWndExtra;

        wc.hInstance      = hInst;

        wc.hCursor        = LoadCursor( NULL, IDC_ARROW );

        wc.hIcon          = NULL;

        wc.lpszMenuName   = (LPSTR)NULL;

wc.lpszClassName = (LPSTR)szUICompStringClassName;

wc.hbrBackground = NULL;

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

return FALSE;

break;

case DLL_PROCESS_DETACH:

UnregisterClass(szUIClassName,hInst);

UnregisterClass(szUICompStringClassName,hInst);

break;

}

return TRUE;

}

UI Window

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

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

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

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

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

{

HIMC hIMC;

HGLOBAL hMyExtra;

switch(msg){

case WM_CREATE:

// Allocate the memory block for the window instance.

hMyExtra = GlobalAlloc(GHND,size_of_MyExtra);

if (!hMyExtra)

MyError();

// Set the memory handle into IMMGWL_PRIVATE

//在这里增加了一个句柄(LONG)hMyExtra作为第3个参数,OS就自动把它放进IMMGWL_PRIVATE变量中。

SetWindowLong(hWnd, IMMGWL_PRIVATE, (LONG)hMyExtra);

            :

            :

break;

case WM_IME_xxxx:

// Get IMC;

hIMC = GetWindowLong(hWnd,IMMGWL_IMC);

// Get the memory handle for the window instance.

hMyExtra = GetWindowLong(hWnd, IMMGWL_PRIVATE);

lpMyExtra = GlobalLock(hMyExtra);

            :

            :

GlobalUnlock(hMyExtra);

break;

        :

        :

case WM_DESTROY:

// Get the memory handle for the window instance.

hMyExtra = GetWindowLong(hWnd, IMMGWL_PRIVATE);

// Free the memory block for the window instance.

GlobalFree(hMyExtra);

break;

default:

return DefWindowProc(hWnd, msg, wParam, lParam);

}

}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

LPINPUTCONTEXT lpIMC;

LPCOMOSITIONSTRING lpCompStr;

HIMCC hMyCompStr;

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

{

lpIMC = ImmLockIMC(hIMC);

if (!lpIMC)

{

MyError( “Can not lock hIMC”);

return FALSE;

}

// Use lpIMC->hCompStr.

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

// Access lpCompStr.

ImmUnlockIMCC(lpIMC->hCompStr);

// ReSize lpIMC->hCompStr.

if (!(hMyCompStr = ImmReSizeIMCC(lpIMC->hCompStr,dwNewSize))

{

MyError(“Can not resize hCompStr”);

ImmUnlockIMC(hIMC);

return FALSE;

}

lpIMC->hCompStr = hMyCompStr;

ImmUnlockIMC(hIMC);

}
产生消息

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

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

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

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

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

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

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

{

DWORD dwMyNumMsg = 0;

. . .

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

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

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

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



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

dwMyNumMsg++;

. . .

return dwMyNumMsg;

}

使用消息缓冲区产生消息

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

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

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

{

LPINPUTCONTEXT lpIMC;

HGLOBAL hTemp;

    LPTRANSMSG lpTransMsg;

DWORD dwMyNumMsg = 1;

// Lock the Input Context.

lpIMC = ImmLockIMC(hIMC);

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

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

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

if (!hTemp) ….// Error!

lpIMC->hMsgBuf = hTemp;

// Lock the memory for the message buffer.

lpTransMsg = ImmLockIMCC(lpIMC->hMsgBuf);

if (!lpTransMsg) …. // Error!

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

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

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

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

// Set the number of the messages.

lpIMC->dwNumMsgBuf += dwMyNumMsg;

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

ImmUnlockIMCC(lpIMC->hMsgBuf);

ImmLockIMC(hIMC);

// Call ImmGenerateMessage function.

ImmGenerateMessage(hIMC);

}

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