admin 管理员组

文章数量: 1184232


2024年1月23日发(作者:excel条件函数)

输入法编程漫谈(08-09-07更新)

大家好!我是路路通。记得那是2007年,北京的第一场雪,比以往时候来的更晚一些,我在此发表了一篇题为《2007全方位、无障碍、奇妙无穷的输入方式闪亮登场》(注:帖子的标题管理员已在百忙中赐名为《路路通键盘和输入法》)的帖子,得到韦剑大版主的赞赏,并拿得了一份数量为1的订单,当时的心情是何等的激动呀!不久,还将这一广告帖置顶,人生最幸福的事莫过于此,如果老天让我遇见韦剑这个人的话,我会对他说:“爱辣味油!”,如果我的发明变成了产品的话,我希望送他——一套键盘。此后天天泡在北大论坛,静候Microsoft、IBM等公司的收购支票,可是等了3个月有余,也没半点音信,夜沉沉,路茫茫,革命一时转入了低潮。

轻轻的我走了,点了点鼠标,带着我依依的不舍。

今天我胡汉三又回来了!看到韦剑这个版主近来当得这么轻松,我就来气,因此决定出来搅搅局,让他少几分清闲,^_^

还有,北大论坛、加加论坛、五笔爱好者论坛等等众多大师、高手、牛人,他们的码表呀,资料呀等等全免费奉送,让我几万大洋的输入法开发经费一分钱也没花出去,最后只好挪作它用;李振春老师的开源代码让我连当面拜师的机会都失去了,被迫自学成才,他们的行为已到了是可忍熟不可忍地步,今天我如果不出几招让他们瞧瞧,那也太不象话了。

《输入法编程漫谈》小学阶段示范程序

《输入法编程漫谈》初中阶段示范程序

输入法安装程序

拼音码表(供输入法程序测试用)

汉字生成工具源代码

微软DDKIME源代码

自由拼音3.0源代码

自由拼音3.0文档

输入法编程难学吗?

不难,一点都不难。你这是忽悠人吧,不是,咱们IT界的人什么素质,会干本山大叔的那些勾当?只要你有点C语言基础,了解一点windows的运行机制,不出1个月,准能精通。如果这两样都不会,还能学吗?能!如果你能做到妻离子散、父子反目,丢官下岗你都不管了,哪绝对没有问题!比如我,当初买电脑不过是想赶赶时髦,从来也没有想过编程呀啥的,一开始鼠标摆弄得都不利索,双击永远都是两次单击,系统每周得摆上好酒好菜请人重装一次。就这个水平因练五笔练烦了,也想自己编一个输入法。记得当时我宣布这一伟大的决定时候,话音刚落,众人已是满地找牙!我可不管这些,去买了本谭浩强《C程序设计》,整天什么活都不干,不分日夜的啃了起来,时间一长,老婆大人不干了,下了最后通谍:“要电脑还是要我”,我说两样都要,老婆说不可能,最后我就一句话:“电脑是要定了,你爱怎的怎的!”。臭娘们敢威胁我,从小吓大的,还怕你不成,等学会了输入法编程,就是IT界的人了,IT界的人还愁没老婆,说实话如果不是法律的限制还不是要多少就有多少。经过半年的奋斗,辛勤的汗水终于有了回报,独立的写出了一个有模有样又实用的程序——路路通输入法1.0。

所以说:“入门既不难,深造也是办得到的”。

输入法程序是什么东东?

输入法是一个小小的动态链接库程序。

动态链接库又是什么呢?

我们知道电脑有很多文件,大体上可分为数据文件和可执行文件两类,数据文件如文本文档、音视频文件等;可执行文件如记事本、Word和Excel等,它们的扩展名一般是

exe,com,Dll,eml等。动态链接库的扩展名是Dll,输入法程序的扩展名是将Dll改成了ime。

动态链接库一般不能单独运行,需要借助宿主程序才能执行相关操作,打个比方,动态链接库(输入法)就是保姆,宿主程序(记事本)就是雇主。

动态链接库又有动态加载和静态加载两种方式,它们的区别就像保姆和小时工,保姆要包吃包住——静态加载,费用稍高;小时工只需预约——动态加载,费用较低。那种方式更好呢?看你的需要,比如你家有小宝宝,24小时都需要有人伺候,保姆比较合适;如果只是搞搞室内卫生,擦擦玻璃什么的,小时工就比较合算。当然啦,如果保姆长的美若天仙,又有大学文凭,又聪明能干,你想将她转正(集成到你的程序去)也行,但做这个决定之前可要想好啦,以后感情不和想离婚可就没那么容易了。和平分手,民政局你得跑N个来回,强行分手,法院你得去N次,而且还要分大半家产,可不像保姆、小时工那么好打发的。

输入法程序不太可能静态加载,因为雇主来自五湖四海。它的加载方式一般是手工动态加载,当然也可以做到自动加载,但这样做一般会让人骂流氓。

输入法编程从那入手

一般人学输入法编程都是从研究源代码开始的,网上开源的有《自由拼音》、微软的DDK,其它还有一些都是根据这两个改写的。从源代码入手是个不错的法子,但很遗憾,这两个代码不是带你入门的,与国内的计算机书籍一样都是给计算机的本科生、研究生、博士之类作参考用的。网上还有一些关于输入法编程的文章,但也只是碰碰嘴唇,并没有把舌头伸进去狂搅一番,初学者看了也是不得要领。研究源代码难,又无相关的书籍,想入门还真的是不容易。造成这种局面,说白了就是输入法这种小程序,低手不会,高手不屑。

实际上输入法很简单,它实际上就是一个查表操作。不相信的朋友,可将你的码表导入到Excel。假设你的码表是基于拼音的,码表分两列,一列是拼音,一列是汉字串,那么用鼠标点击编辑->查找,在查找内容框中敲入shishi,点击查找下一个按钮,黑色矩形框立即就跳到A列的shishi的行上面,对应的B列就是“试试、事事、实施、时世、史实……”等等这些汉字串,输入法的原理就是这样。

既然输入法这么简单,可是做起来却不是那么容易呢?这还得从那晕到死的消息机制谈起。消息机制是什么?打个比方,两个人面对面有话不能直说,要这样子:你的话->老盖手下->老盖->老盖手下->对方,对方也是如此。由于老盖事多,还得排队,就像你去银行取款,先在营业厅门口的机子上拿一个号比如是A911,然后到一边去等候,当营业员用喇叭喊:“请A911到XX窗口……”时,你方可去柜台取款,如果这时你还在对刚才路上碰见的美眉想入非非而没有听见,对不起,重新再去拿一个号排队。

这还不算完,你电脑的所有的东西都得经他批准才能使用,再打个比方:你两口子久别重逢,正准备上演儿童不宜的画面,这时老盖说:“哥们,别急,给我手下打个报告等我批了先”。如果你不听,那会死得很难看。没办法,在他的统治之下,咱们就得按他的旨意办事。

所以学输入法编程,先得对消息机制、硬件访问机制有所了解。

你的《输入法编程漫谈》分个阶段?

分两个阶段:小学阶段、初中阶段。每个阶段都有示范程序。

小学阶段

Win32对话框的创建

组合窗体、候选窗体的创建和操作

码表的操作

初中阶段

状态窗体的创建和操作

输入法消息处理

输入法程序与IME的接口处理

注:文章可转载须经本人同意,不得用于商业用途,而且必须注明:

转:中华人民共和国->北大中文论坛->中文信息处理->输入法讨论专区->路路通网友教授的《输入法编程漫谈》。

这个有点长,为了节约网络资源,可用缩写

转:北大路教授《输入法编程漫谈》。

在此告诉大家一个消息:路路通键盘经过一年多的改进,如今是要多小就有多小,按键的数量要多大就有多大,操作要多简单就有多简单,以后形码基本上就不用拆了。

《输入法编程漫谈》小学阶段示范程序

本程序在Win2k、WinXP下、VC6编译通过。

编译时,请进入:Project->settings->Preprocessor definitions,将_MBCS去掉,加上_UNICODE,UNICODE

再进入:Build->set Active Configuration,选中:srf-Win32 Release 点击OK。

码表是基于拼音的,含有声调,没有按字频排序。网友可以换上自己的码表,将目录下srfReleaseBHFile下的替换掉即可。

程序默认只支持打开UNICODE的文本,有能力的网友可自行修改增加它的兼容性。

“学习输入法编程,要带着问题学,活学活用,学用结合,急用先学,立杆见影,在“用”字上狠下功夫。为了把输入法编程的精髓真正学到手,要反复学习输入法编程的结构和算法,有些概念、算法最好要背熟,反复学习,反复运用。”

Win32对话框的创建

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,

LPSTR lpCmdLine,int nCmdShow)

{

MSG msg;

hInst = hInstance;

if (!InitApplication(hInstance)) return(FALSE);

if (!InitInstance(hInstance,nCmdShow)) return(FALSE);

while (GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return ();

}

这是Win32程序的入口函数,完成窗口类的注册、窗口创建、获取调用线程的消息、消息转换、消息分派等操作。

InitApplication()函数的使用:windows操作系统的最大特点是什么?当然是大大小小、无处不在的窗口,想创建一个程序,一般情况下当然要创建窗口了。这个有点像开商店,先选好场地,然后到工商局申请营业执照:

WNDCLASSEX wc;

= sizeof(WNDCLASSEX);

……

商店名称、经营范围等等在这里填清楚。开多少家都可以,只要你的本钱足够。

之后是等候审批,这个审批速度比咱们工商局快多了,只要是合法经营,瞬间就给你

批下来。不用找关系送烟什么的,老盖这点还算清廉。

if( !RegisterClassEx( (LPWNDCLASSEX)&wc )) return FALSE;

如果批准了,那么你就可以准备开张了,否则,就是无照经营。无照经营,在老盖的统治下是绝对不可以的啊。

InitInstance()函数的使用:执照批了,下一步就是装修门面了。

hWnd = CreateWindowEx (),

装修得合乎城建的规定,如果违章,那你还是不能开业。

if(!hWnd) return(FALSE);

一切工作都做好了,就可以开门营业了:

ShowWindow(hWnd,nCmdShow);

UpdateWindow(hWnd);

获得系统信息。

while (GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

这段我觉得跟咱们村公所非常像。

GetMessage()接待、接电话、跑腿的,一天24小时都在村公所呆着。TranslateMessage(支书)、DispatchMessage(村长)一般都在家里干私活,有事才到村公所,不像咱们上班一族那么正规的。如果乡党委、政府有事找支书、村长,GetMessage这人马上就会去通知他们,不至于因为找不着人把官给丢了。

MainWndProc()函数的使用:你的买卖就在这里进行。

如果不想做买卖了,记得去工商局注销执照,还有商场租用合同、水呀、电呀、闭路电视、电话等等都得报停结算清楚,我想没有谁还很乐意继续交这些费吧。

case WM_DESTROY:

DeleteObject(MyFont);

FreeMBArray();

PostQuitMessage(0);

break;

default:

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

}

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

系统给每个窗体过程的消息、数据都是标配的,很多根本就用不着,这时就需要返回给默认的函数处理。

跟老盖打交道,一定要记住:“早请示,晚汇报,先申请,后使用,用后归还”,

创建文本框

学会了对话框的创建之后,我们学习在对话框上创建一个文本框,它的作用就是用来接收键盘输入。

hWndEdit = CreateWindowEx (WS_EX_WINDOWEDGE,

_T("EDIT",

NULL,

WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL |

ES_LEFT |

ES_MULTILINE | ES_AUTOVSCROLL| ES_AUTOHSCROLL,

0,

0,

0,

0,

hWnd,

(HMENU)ID_EDITCHILD,

(HINSTANCE)GetWindowLong(hWnd,GWL_HINSTANCE),

NULL);

//让文本框获得焦点

SetFocus(hWndEdit);

case WM_SIZE:

//让文本框填满整个对话框窗口

MoveWindow (

hWndEdit,

0,

0,

LOWORD(lParam),

HIWORD(lParam),

TRUE);

break;

我们不能让文本框直接接收键盘消息,我们要把它截下来经过一番加工后才让它发送出去。就像打仗一样,我们要想办法把敌人的情报偷出来,改成对我们有利的情报后再放回去。

这里要要用到一个窗口子类化的技术,因这个技术与输入法的关系不大,在正规的输入法程序也不会用到它,在这里就不详细解说了,有兴趣的网友可去查相关的资料。在这里使用它主要是基于初学者容易理解的考虑。

输入法就是:把键盘的消息截下来,经加工后再放回去,没有什么神秘的东西。

键盘消息的处理

我们通过窗口子类化的手段将操作系统发给对话框的键盘消息截取后,接下来就是如何处理这些消息了。

键盘的消息很多,我们只需要处理对我们有用的消息。

输入法需用到的虚拟键主要有两类:用于编码的键和功能键。编码键一般就是26个字母键、数字键、标点符号键,可根据自己的输入法进行增减;功能键就是空格键、退格键、Esc键、4个方向键、用于翻页的键等。下面以拼音输入法为例子来说说按键消息的处理:

case WM_CHAR:

flg = CharHandler(hWnd,wParam,lParam);

break;

case WM_KEYDOWN:

flg = FunKeyHandlerNormal(hWnd,wParam,lParam);

break;

VK_ESCAPE键是取消编码串,由于编码串变量g_PYBMStr是全局变量,因此要调用函数InitPYBMStr()将其清空。

VK_DELETE、VK_BACK与编码键都是对字符串的操作,C语言的教科书上都有例子,不难理解。

VK_SPACE与10个数字键是对候选框的词条进行选择。这个比较关键,你的短语取代操作系统默认的字符就是在这里处理。

VK_HOME、VK_END、VK_LEFT、VK_UP、VK_RIGHT、VK_DOWN是对编码串上面光标(插入符)移动的操作,在设计你的编码串结构的时候,必须要有一个变量存储插入符的位置。

a~z键用于编码输入。

“=”、 “.”用于向前翻页,“-”、“,”用于向后翻页。

功能键必须是在编码串的长度不为零的情况下才需要处理,这个初学者要注意。还有光标(插入符)的操作记得通知操作系统刷新一下对应的窗体,否则插入符会呆在原地不动。

将字串发送到宿主程序

将选好的字串发送到宿主程序是输入法程序的目的,这个过程就是程序的核心,其它的工作都是围绕这个核心而展开的。初学者首先要认清这个事实,不要把输入法编程想象的很复杂,学任何东西都一样,一定要把握它住的本质,然后逐层展开,最后搞清楚它的方方面面,这样才能学到它的精髓,这样才能事半功倍。

我们先来看看示范程序是如何把字串发送到EDIT控件的:

case MY_WM_IME_COMPOSITION:

//获得最终候选串的指针

lpszResult = GetCompResultStr();

if (lpszResult)

{

while(*(lpszResult) != _T('0'))

{

//用特快专递将字串发送到EDIT控件

SendMessage(hWndEdit,WM_CHAR,(WPARAM)(*lpszResult),0);

lpszResult++;

}

}

//字串发送完毕,隐藏候选窗体

MoveCandWindow();

//对组合串结构和候选串结构初始化

InitPYBMStr();

InitCandStrPage();

break;

再来看看标准的输入法程序是如何做的:

BOOL MakeResultString(BOOL fFlag)

{

HIMC hIMC = NULL;

LPINPUTCONTEXT lpIMC = NULL;

LPCOMPOSITIONSTRING lpIMECompStr = NULL;

LPTSTR lpConvStr = NULL;

GENEMSG GnMsg = {0};

hIMC = (HIMC)GetWindowLong(GetUIWnd(), IMMGWL_IMC);

if (!hIMC)

{

MessageBeep((UINT)-1);

return FALSE;

}

if (!(lpIMC = (LPINPUTCONTEXT)ImmLockIMC(hIMC)))

{

MessageBeep((UINT)-1);

return FALSE;

}

lpIMC = ImmLockIMC(hIMC);

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

if (fFlag)

{

lpConvStr = GetConvStr();

if (!lpConvStr) return FALSE;

_tcscpy((LPTSTR)((LPBYTE)(lpIMECompStr) +

(lpIMECompStr)->dwResultStrOffset),lpConvStr);

lpIMECompStr->dwResultStrLen = _tcslen(lpConvStr);

}

else

{

*((LPTSTR)((LPBYTE)(lpIMECompStr) + (lpIMECompStr)->dwResultStrOffset))

= _T('0');

lpIMECompStr->dwResultStrLen = 0;

}

InitCompADT();

ImmUnlockIMCC(lpIMC->hCompStr);

GenerateMessage(WM_IME_COMPOSITION,0,GCS_RESULTSTR);

GenerateMessage(WM_IME_ENDCOMPOSITION,0,0);

ImmUnlockIMC(hIMC);

return TRUE;

}

一直到if (fFlag)这行代码为止,之前的那些都是为了取得IMC结构中存放结果串的内存块,没有别 的目的。你选好的字串必须先放到这里,然后才能帮你发送出去。你可能要问为什么不能直接发送呢? 先来看看我们现在是怎么杀猪。农民兄弟把猪养大了,一刀把它杀了拿去 卖钱行吗?不行,政府有规定生猪要定点屠宰,理由是:一、可以防止病猪、老母猪、灌水猪上市, 二、各种收费有绝对的保障,你明白其中的道理了吧?

输入法的调试

普通的可执行程序调试很方便,设置好断点,鼠标点击感叹号(或Ctrl + F5)就行。

但输入法是动态链接库,不能单独运行,所以这招就不灵了。

很多初学者不知道如何调试输入法程序,因此编程的效率很低。

如何调试输入法?

其实也很简单:

Project->Settings->Debug->Executable for debug session

单击它下面编辑框右边的有三角符号的按钮,弹出一个菜单,点击选择你要用来调试输入法的程序(即输入法的宿主程序)比如记事本程序

windows2000 C:

windowsXP C:

这样在你的输入法代码中设置好断点后,点击感叹号,VC就会打开记事本,在Windows的状态栏中选择你的输入法,就可以像调试普通可执行程序一样调试你的输入法了。

WM_KEYDOWN事件

wParam(nVirtKey) 就是虚拟键的值。非系统键的虚拟键代码。

lParam(lKeyData)。指定重复计数值、扫描码、扩展键标志、转换码、原有的键状态标志和暂态标志。

重复计数值 0-15指定重复计数值,通常为1。可是,如果把键按下去不放,应用程序不能一足够快的速度来处理这些键消息(由于各种原因),那么Windows将把多个WM_KEYDOWN,WM_SYSKEYDOWN,WM_CHAR和WM_SYSCHAR消息合并为一条消息,正确地对重复计数值进行增量。当然,对于WM_KEYUP或WM_SYSKEYUP,重复计数值总为1。

重复计数值可以大于1,是因为要将其作为对自动键入超过限度的响应。用户可以从控制面板中选择高的重复

速率,来产生键入溢出的现象。

扫描码 键盘扫描码是有键盘硬件所产生的一个值,用来标识按下或释放的实际物理键。虽然不同的键可以产

生相同的字符代码,例如,主键顶部的数字键和数字键盘上的数字键都可以返回数字1这个字符,但对于物理键来

说,扫描码是唯一的(也就是说两个1的扫描码是不相同的)。

同样地,尽管左右Alt和Ctrl键返回一个字符代码,但它们返回独立的扫描代码。然而,某些物理键可能根据Alt、Ctrl或Shift的状态返回不同的扫描码。

扩展键标志 如果当前按键是由增强型键盘独有的一个键产生的,那么这个标志就被设置为1。这些键包括非键

盘光标和翻页键以及Insert和Delete键、数字键盘上的反斜杠/、数字键盘上的Enter键和Num Lock键。

上下文代码标志 如果在当前按键期间还按下了Alt键,或者当前消息为WM_SYSKEYUP或WM_SYSKEYDOWN,那么这个标志被设置为1,上下文代码标志为所有的WM_KEYUP和WM_KEYDOWN消息而清零,但有两个例外:

某些非英语键盘使用Shift、Ctrl和Alt键与传统键的结合来产生特殊字符。这些结合将使上下文代码标志置位

,但不报告为系统按键。

如果活动窗口是一个图标,那么他不读取输入焦点,因此所有按键都将产生WM_SYSKEYUP和WM_SYSKEYDOWN消息,以避免活动窗口(作为一个图标)试图去处理这些事件。在这些情况下,只有当Alt键按下时,才对上下文代码标志进行置位。

主键状态标志 对于WM_CHAR、WM_CHARDOWN、WM_SYSCHAR和WM_SYSCHARDOWN消息,如果前面按下相同的键,那么主键态标志就置位1,如果前面相同的键释放,它就清零。

对于WM_KEYUP和WM_KEYDOWN消息,主键态总是处于置位状态。显然,在一个键可以被释放之前,一定是已经将它按了下去。

过渡状态标志 这个标志提供了多余的信息。如果键被按下,它就清零,象在WM_KEYDOWN或WM_SYSKEYDOWN消息中一样;如果键被释放,象在WM_KEYUP或WM_SYSKEYUP消息中一样,那么这个标志就置位1。对于WM_CHAR和WM_SYSCHAR消息,过渡标志被清楚(但也是不相关的)。


本文标签: 输入法 程序 消息 编程 没有