[转载]翻译:windows2000 DDK中IME输入法编程的资料(前10页)
为了防止原数据丢失,得此做一个备份
原文地址: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);
}