分类 IME输入法 下的文章

为了防止原数据丢失,得此做一个备份
原文地址: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);

}

为了防止原数据丢失,得此做一个备份
原文地址:https://code.google.com/archive/p/windows-config/wikis/Win32IME.wiki 作者:未知

一些术语

IME: Input Method Editor/Engine, 输入法编辑器, 引擎
IMM: Input Method Manager, 输入法管理器
Comp: Composition String, 一般是用户输入的字串, 比如拼音输入香'字要打xiang', 这个`xiang'就是用户输入的Comp. 你可以告诉IMM当前的Comp是什么, 这样如果应用能自己显示Comp/Cands的话它就自己显示; 你也可以不告诉IMM, 这样你自己负责显示就行了.
Cands: Candidates, 候选词组
Commit: 提交上屏

其他一些软键盘啊, GuideLine啊之类的对我的输入法没什么用, 我就全部无视了.
简介

Win32下的输入法编程概括地来说就是要写一个DLL. 这个DLL要实现并在.def文件中指定输出M$指定的一些API. Win32在装载你的输入法DLL时会检查是不是每个API都能查询到, 如果不是的话, 这个输入法就不会被成功的装载.

建议下载2600.1106版本的win32 DDK, 里面有区位输入法的源程序, 我的影舞笔就是参考这个程序写的. 可以看看里面的wingb.def文件, 总共输出了将近20个API. 其实大部分都没什么用, 直接套就行了, 重要的也就5~6个.

``` $cat wingb.def LIBRARY WINGB

EXPORTS ImeConversionList ImeConfigure ImeDestroy ImeEscape ImeInquire ImeProcessKey ImeSelect ImeSetActiveContext ImeSetCompositionString ImeToAsciiEx NotifyIME

        ImeRegisterWord
        ImeUnregisterWord
        ImeGetRegisterWordStyle
        ImeEnumRegisterWord

        UIWndProc
        StatusWndProc
        CompWndProc
        CandWndProc
事实上win32 DLL还有一个隐含的输出函数, 就是DLL的入口函数, 一般都是名为DllMain的一个函数, 但是在区位输入法里这个函数的名字是ImeDllInit. 你可以在你的makefile (或类似于vc6的.dsp/vc789的.vcproj等文件)里指定入口函数的名字.

初始化的大概的顺序是:

    DllMain里注册窗口类
    ImeInquire里告诉imm你的ime消息窗口的类名
    imm根据这个类名创建你的ime消息窗口
    你的消息窗口的回调函数被调用, 消息是wm_create

至此, 用户就可以开始使用你的输入法了, 这之后有意义的顺序是大概这样的:

    用户按下一个键
    ImeProcessKey返回true, 表示输入法想处理这个键
    ImeToAsciiEx被调用
    此函数创建一些ime消息, 如开始/结束输入法编辑, 设置comp串, cand串, 显示comp窗口, 显示状态栏窗口, 提交等
    相应的窗口被创建, 显示,
    如果当前的应用程序是懂输入法的, 它自己也会显示comp串, 前提是你的ImeToAsciiEx需要告诉它comp串是什么, 如果你只想自己来显示comp串的话, 那么这种程序是不会显示的

我为了自己编程方便, 就没有通知应用程序自己去显示comp和cands. 同时也是因为我觉得实在是没有必要.
DllMain

这个函数肯定是最重要的, 一个DLL没有这个入口函数的话就不是DLL了. 在DDK的区位输入法代码里有一个sources的文件, 里面有一行:

DLLENTRY=ImeDllInit

你如果用别的build系统, 比如Visual Studio或者mingw, 你就应该自己配置你的入口函数是哪个.

这个函数在dll load的时候需要初始化你的输入法里要用到的全局变量, 以及注册win32的几个窗口类. 在区位输入法里注册了四个窗口类, UI, Status, Comp, Cand. 其中UI窗口是一个纯消息窗口, 也是win32 IME必须要求的一个窗口. 这个窗口的类名会在ImeInquire里传给win32 IME以便让win32 IME知道它应该去跟谁通讯. 我的影舞笔输入法把Comp和Cand的窗口合并为一个了.

如果这个函数返回false的话那这个DLL就会load失败, 当前尝试load你的输入法的这个程序就没法用你的输入法了. 所以在这个函数里你可以干一些很``酷'' 的事情, 比如, 在测试阶段, 你可以指定只有notepad才能成功load你的输入法, 通过GetModuleFileName你可以得到当前调用的程序的路径, 如果不是notepad, 那就不让它用你的输入法. 然后呢, 你在win32的控制面板→区域设置里指定你当前正在测试开发的输入法为默认的输入法, 这样你一打开notepad, 就可以开始测试你的输入法了, 而不需要按一下输入法切换键才能开始测. 虽然只是省下按一个键, 但是也是值得的, 因为相信你会按很多次的. 而这时候其他的程序不会受影响, 你可以随便杀死notepad.exe, 做下一轮的开发, 测试迭代.

(win32下一个DLL被load了的话, 是不允许替换这个dll文件的. 所以当你发现一个bug, 做了修正, 你没法把build出来的这个新的dll拷到系统路径里, 必须先把所有的load了这个dll的程序杀死. 如果你的输入法不是默认的, 那你每次都要按一下切换键才能开始测试; 如果它是默认的但是你不把除了notepad的其他程序排除的话, 你每次都要杀死很多程序, 比如explorer.exe等. 尤其是如果你只能手工一个一个的删的话, 很快你会疯掉的. 你甚至都不知道哪个程序load了你的输入法. 只能一个一个的猜? 如果你知道sysinternal的process explorer的话, 那你还可以用一下它的查找功能).

还有一个特别有用的功能是, 即使你的输入法还有bug, 但是如果这个bug只是针对某个程序的话(或者说某个程序有bug, 但只针对你的输入法:-), 你可以把这个程序排除在外. 比如, Cygwin下的X窗口程序都是由xwin.exe来画窗口的, 这些窗口都不能处理win32的输入法, 但是win32的输入法切换键又能把输入法的状态栏给切出来, 很明显没什么意义, 我就把xwin.exe在我的输入法里排除了. 又比如, 所有的DOS窗口的输入法处理都是由一个叫conime.exe的程序处理的, 这个程序好像会对我的输入法提很非分的要求, 我干脆就把它也拒之门外:-) 以后我就打定主意在终端窗口里再也不用输入法了, 呵呵! (造成这个的原因是前面提到的conime是个懂''输入法的应用, 它太懂''了, 它要求你必须设置comp串/cands告诉它知道, 你还不能自己显示! 我怀疑这个应用的imm是不是压根就不会帮你创建那个ime消息窗口. 一句话, 它太霸道了).

做这样的选择, 我的生活会更简单.
ImeInquire

这个函数是除了DllMain后第一个会被win32 IMM调用的函数. IMM通过调用这个函数知道你的输入法有什么特性. 比如, 除了按键消息外, 你是不是还想处理键放开的消息. 以下是我的影舞笔的此函数代码(注释版) ``` BOOL WINAPI ImeInquire(LPIMEINFO lpImeInfo, LPTSTR lpszWndCls, DWORD dwSystemInfoFlags) { if (!lpImeInfo) { //简单出错处理 return FALSE; }

lpImeInfo->dwPrivateDataSize = 0; //IMM会根据这个值自动为你的输入法分配一块内存, 你可以用它来保存一些你的context数据. 我嫌这玩意儿太"聪明"了, 不用之.

lpImeInfo->fdwProperty = IME_PROP_KBD_CHAR_FIRST | IME_PROP_UNICODE | IME_PROP_IGNORE_UPKEYS | IME_PROP_SPECIAL_UI;

    // IME_PROP_KBD_CHAR_FIRST 是说IMM调用你的ImeProcessKey和ImeToAsciiEx函数之前是不是把按键消息的char值算出来, 在第一个整型参数的前两个字节里传给你, 其实你也可以自己算的
    // 如果你不设这个标志的话那第一个参数就只有低位的两个字节有意义 (好像是所按的键的虚拟键值).

    // IME_PROP_UNICODE, 意义应该很明显了, 一般肯定得设上

    // IME_PROP_IGNORE_UPKEYS, 就是上文说的要不要处理键放开的消息. 不设这个标志就是要处理, 设了就是不处理(IMM针对键放开的消息就不会来调你的ImeProcessKey和ImeToAsciiEx了).

    // IME_PROP_SPECIAL_UI, 不记得什么意思了, 可以上google查一下.

lpImeInfo->fdwConversionCaps =
    IME_CMODE_NATIVE | IME_CMODE_NOCONVERSION;
lpImeInfo->fdwSentenceCaps = 0;
lpImeInfo->fdwUICaps = UI_CAP_ROT90;
lpImeInfo->fdwSCSCaps = SCS_CAP_COMPSTR | SCS_CAP_MAKEREAD;
lpImeInfo->fdwSelectCaps = (DWORD) 0;

    // 这之上的这段代码是什么意思我也不明白, 但是这样写对我的影舞笔就够用了, 所以我也懒得去弄明白.

lstrcpy(lpszWndCls, get_ui_class_name().c_str());

    // 这里你要把你的UI窗口的类名拷到IMM传给你的输出参数里.

    return (TRUE);

    // 一定要返回true, 没试过这里返回false会怎样. 

} ```

LPIMEINFO的定义可以在ddk的immdev.h里查到.
ImeProcessKey

win32 IMM在收到一个键盘消息之后, 会先问一下这个函数, 你的IME是不是想处理这个键, 如果你在这个函数里返回true, 意思就是你想处理, 那么imm就会接着调下一个函数ImeToAsciiEx, 否则它就会自己处理这个键盘消息.
ImeToAsciiEx

这个函数的返回值有点意思. 返回值应该是你这个函数的这次调用一共给win32产生的多少个消息. 比如用户输入了一个完整的五笔编码, 希望提交他/她选中的候选词了, 你就把要提交的数据(一个unicode字符串)写到输入法上下文的提交字串的内存句柄中, 再把 (WM_IME_COMPOSITION, 0, GCS_COMP|GCS_RESULT|GCS_RESULTREAD) 这样一个消息添加到输入法上下文的消息内存中, 把要返回多少个消息加一.

以下是输入法上下文结构的定义:

``` typedef struct tagINPUTCONTEXT { HWND hWnd; BOOL fOpen; POINT ptStatusWndPos; POINT ptSoftKbdPos; DWORD fdwConversion; DWORD fdwSentence; union { LOGFONTA A; LOGFONTW W; } lfFont; COMPOSITIONFORM cfCompForm; CANDIDATEFORM cfCandForm[4]; HIMCC hCompStr; HIMCC hCandInfo; HIMCC hGuideLine; HIMCC hPrivate; DWORD dwNumMsgBuf; HIMCC hMsgBuf; DWORD fdwInit; DWORD dwReserve[3]; } INPUTCONTEXT

UIWndProc

这个函数里要处理IME消息. 其实UI窗口根本没有UI, 没有图形! 这个窗口是一个纯消息窗口, 你不会收到wm_paint的消息. 所以我直接把区位输入法里处理wm_paint的回调函数删了. 我觉得把这个窗口命名为ImePureMsgWnd更合适一些.

话说回来, 这个IME第一重要的窗口是谁创建的呢? 答案是IMM. 你在区位输入法的代码里不会看到这个窗口被CreateWindowEx. 在DllMain里你会注册这个窗口的类, 在ImeInquire里你会把这个窗口的类名传给IMM. 之后你就会收到这个窗口的创建消息了. 说明肯定是IMM负责创建了这个窗口.

这个函数根据收到的消息要负责创建Comp, Status等窗口, 移动这些窗口的位置, 等等.
CompWndProc 和 StatusWndProc

这两个窗口函数最重要的当然是负责画窗口了.
其他不怎么重要的API

ImeConfigure 这个函数就是按理你应该弹一个对话框给用户配置你的输入法的地方了, 像我们这种高级电脑用户, 对这种功能直接无视.
ImeConversionList 这个函数是让另一个输入法来反查一个汉字在你的这个输入法里是怎么打出来的. 比如区位输入法可以指定用微软拼音来反查一个汉字的拼音. 在区位输入法的unicode模式下按9999'';输入香'', 输入完后还会继续显示编辑窗, 内容是``xiang''. 这么无厘头的功能, 无视!
ImeDestroy 没什么好说的,
ImeEscape 也没什么好说的, 没什么鸟用. 以下是我的代码:
ImeSelect Google出来的文档是说在这个函数里初始化或析构你的私有数据, 好吧, 我前面已经说了, 我没什么私有数据, 所以这个函数也可以简化了.
ImeSetActiveContext
ImeRegisterWord
ImeUnregisterWord
ImeGetRegisterWordStyle
ImeEnumRegisterWord
ImeSetCompositionString
NotifyIME

可以上这儿去看文档. 这些函数都没有什么用.
一些注意事项

win32输入法编程的陷阱还是挺多的, 搞不好会很迷惑.

一定要用版本管理工具(废话), 但是用了版本管理工具还不一定够, 在前期开发的时候, 要时不时地重启一下机器, 有时候测着没问题, 重启一次就不行了. 如果改动量大的话就会不知道是哪个版本引进的问题.
ime消息窗口的类名不能随便换. win32的IMM好像装载过一次以后就会把你这个输入法的各个窗口的类名给记下来, 你要是换了的话下次就装载不上了, 必须重启一次机器. 靠, 发现这个问题当时花了我很多时间.
某种情况下winlogon.exe不能被排除在外. 如果你的输入法开发的差不多了, 你把它设成默认的输入法, 准备以后一直用它了, 嗯, 用的好好的, 一重启, 嘿, 用不了了. 这是因为winlogon是你的第一个用户, 而这个进程好像比较特别, 如果它load这个默认输入法失败的话, windows就会认为这个输入法有问题. 所以你想把winlogon排除在外的话记得重启前要把另一个输入法换成默认的再重启.
出现以上两个问题时你也可以不重启, 只要把你的.dll换一个名字, 在注册表里换一个注册键.

往注册表里添的时候内容大概是这样的: (E0330804的0804比较重要, 这代表这是中文输入法, 更关键的是你的资源文件.rc里面也有0804, 如果这个不匹配的话这个ime也是load不了的. 前面的e033可以随便换).
``` Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layouts\E0330804] "Ime File"="ywb.dll" "Layout File"="kbdus.dll" "Layout Text"="Chinese (Simplified) - YWB"
```
影舞笔的运行方法

编译用VS2008, 同时还要求安装了python3.1, 安装路径必须是C:/python31, 同时你必须把代码co到Q:\gcode\scim-cs下, 如果没有Q:盘可以用subst.exe挂一个. 当然也可以自己改一下源代码.

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

为了防止原数据丢失,得此做一个备份
原文地址:https://blog.csdn.net/pkfish/article/details/7339890 作者:pkfish

·win下的输入法编程方法

    在win下编写输入法主要可以采用外挂式和IME方式两种:

    ·外挂式的实现核心是通过键盘钩子对输入进行拦截,再把要输出的内容传送给处于活动状态的编辑窗口。

    ·IME方式则是使用系统提供的IME(Input Method Editor)接口实现输入法。

    头一种方法自己并未接触过,但使用钩子技术在兼容性上来说总是有点不足的(遇上防钩子的程序,输入法就没辙了),再者,把要输出的内容传送给处于活动状态的输入焦点处并不总能成功(对win下的api了解不多,这里或许有误)。既然系统已经为我们编写输入法专门弄了个IME出来,那当然还是直接用系统的好些啦。

·IME介绍

    IME的实现原理可用下图简单的表述:


键盘事件  应用程序

↓     ↑

Windows的USER.EXE

↓ ↑

输入法管理器(IME)

↓ ↑

输入法


    当当前活动窗口开启了输入法后,键盘事件就会通过系统进程USER.exe转交给IME处理,IME再转告给当前输入法,输入法处理完后把处理结果告知IME,IME根据这个处理结果决定反馈给USER.exe,然后再反馈给当前应用程序的活动窗口。

    基于IME开发的输入法文件后缀为“ime”,但实际上是一个dll文件。这个dll文件类似于IME的一个插件存在,实现了IME要求的十几个接口函数。实际上,当IME收到USER.exe发来的键盘事件时,IME就会调用当前启动的输入法中某些IME接口函数,根据这些函数的返回值,IME可以确定输入法是否打算处理当前的键盘输入(选择不处理的情况下该键盘事件将会送去给应用程序自己去处理,否则应用程序不会收到该键盘事件),或把输入法要求输出的内容输出到应用程序中。

·系统对输入法信息的保存

    IME输入法的信息保存在注册表中,位置有两个:



    ·HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Keyboard Layouts



    这里保存了所有当前系统已安装的输入法的基本信息,这里要特别说明一下:已安装的输入法并不仅仅是指在语言栏里能直接看到,能用热键切换的输入法,还包括安装了但未在语言栏中启动的那些,这些未启用的输入法可以在语言栏的设置->按[添加]按钮弹出的窗口中找到。

    在这个注册表项下是一堆以英文和数字组成的8位字符串命名的子项(实际应该理解为一个8位的16进制数,为输入法的键盘布局唯一标识号),这里每一个子项代表的就是一个已安装的输入法。而这些子项的字符串的后4位代表了该输入法所属的语言,这个值具体的意义可从“HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Keyboard Layout\\DosKeybCodes”项里面的值里查到,而前4位则仅表示一个安装序号,以保证每个输入法项的这个8位字符串都是唯一的,每当有新的输入法通过系统提供的方法安装进来,系统就会找出与要安装的输入法同一语言的输入法中最大使用到的安装序号,新安装的输入法则在这个安装序号基础上+1,如果安装是完全通过自行写入注册表的方式完成的,则只要保证字符串后4位值满足要求且整个8位字符串值惟一也行,不过还是建议使用系统提供的方法安装(具体操作文章稍后的地方有讲)。

    举个例子,假如当前系统刚通过系统提供的方法安装了一个新输入法,其安装好后的输入法项为“00060804”,则根据后4位“0804”可以查到,这个输入法的语言是属于中文的,而前4位“0006”则为其安装序号,假设现在有一个新的中文输入法要通过系统提供的方法安装进来,则这个新输入法安装完后所分配的项应该为“00070804”。

在对于非系统的输入法项里面都写有三个必定存在的键:

Ime File:该输入法对应的ime文件文件名。系统从system32目录(win9x下为相对于system目录)下读入该文件;

Layout File:键盘布局文件,一般指向某个系统文件(自己也没搞清楚是怎么回事[-.-]);

Layout Text:在语言栏设置中选择要操作的输入法(启用/取消启用)时看到的输入法名称。

    另外也有输入法项里存在Layout Display Name和Layout Id这些键,但没研究过其意义。



    ·HKEY_CURRENT_USER\\Keyboard Layout\\Preload



    这个位置保存的是当前用户启用的输入法的列表,也即当前用户可在语言栏中切换到的输入法都可以在这里看到。每个启用的输入法占一个键,其键名代表了该输入法的启用顺序,值内容则为该输入法安装后的8位字符串标识。