//函数isseparetor用于判断一个字符是否为分隔符
Function Tform1.isseparetor(ch:char):boolean;
begin
  isseparetor:=false;
  if ch in [' ',',','.','?',#13,#10] then
    isseparetor:=true;
end;//函数getword用于读取鼠标所在位置的单词
Function Tform1.getword(pos:word):string;
var st:string;pos1,pos2:word;
i:longint;w:string;
begin
  w:='';
  pos1:=1;
  getword:='';
  //读取文本框中的内容及文本长度
  st:=memo1.Lines.Text;
  pos2:=length(st);
  //向前搜索当前单词的起始位置
  for i:=pos-1 downto 1 do
  if isseparetor(st[i]) then
  begin
    pos1:=i+1;break
  end;
  //向后搜索当前单词的结束位置
  for i:=pos to pos2 do
  if isseparetor(st[i]) then
  begin
    pos1:=i-1;break
  end;
  //截取pos1-pos2间的字符,以构成一个单词
  if pos1<=pos2 then
  begin
    for i:=pos1 to pos2 do w:=w+st[i];
    getword:='单词:'+W
  end;
end;procedure TForm1.Memo1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var lc:longint;charpos:word;
begin
  //向文本框传递消息EM_CHARFROMPOS
   lc:=sendmessage(memo1.handle,EM_CHARFROMPOS,0,x+(y shl 16));
   //取得鼠标位于第几个字符上
   charpos:=word(lc);
   //显示我们所点取得单词
   memo1.hint:=getword(charpos)
end;procedure TForm1.FormCreate(Sender: TObject);
begin
memo1.ShowHint:=true
end;end.

解决方案 »

  1.   

    转贴~~~~Copy~~And~~Paste
    屏幕取词技术很多人对这个问题感兴趣。 
    原因是这项技术让人感觉很神奇,也很有商业价值。 
    现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。 
    但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。 
    大约每周一两次。想知道的人就常常来看看吧! 一.基础知识 
    首先想编这种程序需要一些基础知识。 
    会用Vc++,包括16/32位。 
    精通Windows API特别是GDI,KERNEL部分。 
    懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。 二.基本原理 
    在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。 
    TextOut 
    ExtTextOut 
    DrawText 
    ...... 
    其中DrawText最终是用ExtTextOut实现的。 所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。 到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@.......... 
    我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。 另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。 三.技术要点 
    要实现取词,主要要解决以下技术问题。 
    1.截取API入口,获得API的参数。 
    2.安全地潜入Windows内部,良好地兼容Windows的各个版本 
    3.计算鼠标所在的单词和字母。 
    4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。 首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。 
    另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。 
    你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。 
    你可以在TextOut开头设一个读写断点 
    bpm textout 
    再取词,就会找到词霸用来写钩子的代码了。 /********************************** 
    所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice. 
    **********************************/ 至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。 我先来讲述取词的过程, 0 判断鼠标是否在一个地方停留了一段时间 
    1 取得鼠标当前位置 
    2 以鼠标位置为中心生成一个矩形 
    3 挂上API钩子 
    4 让这个矩形产生重画消息 
    5 在钩子里等输出字符 
    6 计算鼠标在哪个单词上面,把这个单词保存下来 
    7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子 
    8 用单词查词库,显示解释框。 很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。 其中0,1,2,7,8比较简单就不提了。 先说如何挂钩子: 
    所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。 步骤如下: 
    1.取得Windows API入口,用GetProcAddress实现 
    2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节 
    3.写入跳转语句 
    这步最复杂 
    Windows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。 
    有一个未公开函数是AllocCsToDsAlias, 
    UINT WINAPI ALLOCCSTODSALIAS(UINT); 
    你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。 这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。 
    这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。
    咱们以截取TextOut为例。 下面是代码: //截取TextOut typedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT); ALLOCCSTODSALIAS AllocCsToDsAlias; BYTE NewValue[5];//保存新的入口代码 
    BYTE OldValue[5];//API原来的入口代码 
    unsigned char * Address=NULL;//可写的API入口地址 
    UINT DsSelector=NULL;//指向API入口的可写的选择符 
    WORD OffSetEntry=NULL;//API的偏移量 BOOL bHookAlready = FALSE; //是否挂钩子的标志 BOOL InitHook() 

    HMODULE hKernel,hGdi; 
    hKernel = GetModuleHandle("Kernel"); 
    if(hKernel==NULL) 
    return FALSE; AllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址 
    if(AllocCsToDsAlias==NULL) 
    return FALSE; hGdi = GetModuleHandle("Gdi"); 
    if(hmGdi==NULL) 
    return FALSE; FARPROC Entry = GetProcAddress(hGdi,"TextOut"); 
    if(Entry==NULL) 
    return FALSE; OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符 
    DsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符 
    Address = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址 NewValue[0]=0xEA; 
    *((DWORD*)(NewValue+1)) = (DWORD)MyTextOut; OldValue[0]=Address[0]; 
    *((DWORD*)(OldValue+1)) = *((DWORD*)(Address+1)); 
    } BOOL ClearHook() 

    if(bHookAlready) 
    HookOff(); FreeSelector(DsSelector); 
    } BOOL HookOn() 

    if(!bHookAlready){ 
    for(int i=0;i<5;i++){ 
    Address[i]=NewValue[i]; 

    bHookAlready=TRUE; 

    } BOOL HookOff() 

    if(bHookAlready){ 
    for(int i=0;i<5;i++){ 
    Address[i]=OldValue[i]; 

    bHookAlready=FALSE; 

    } //钩子函数,一定要和API有相同的参数和声明 
    BOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) 

    BOOL ret; 
    HookOff(); 
    ret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut 
    HookOn(); 
    return ret; 
    } 上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过 
    因为我没有VC++1.52.所以代码可能会有错。 建议使用Borland c++,按16位编译。 
    如果用VC++1.52,则要改个选项 在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。 
    在95和NT里有些不同 
    在Win32里,每个模块(DLL,EXE)有一个ImageBase,这个数存放在DLL和EXE的文件头里。每个模块通常是不一样的。当Windows加载这个模块时优先考虑把模块放到Imagebase指定的地址,但有时会出现两个模块的地址重叠会有冲突,Windows会把模块移到与Imagebase最近的地址。所以Imagebase相同的模块在不同进程可能会在不同的地址上。这个地址就是module handle.Imagebase是可以在编译时指定的。 你用moudlefirst,moudlenext遍历得到的module handle是和进程有关的。 比如:你编了一个Imagebase为0x10000000的DLL A,进程A调用这个DLL A,在进程A里这个DLL被加载到地址0x10000000处,他的module handle为0x10000000,进程B也调用这个DLL A,但是进程B还调用另外一个DLL B,这另外的DLL B也是Imagebase为0x10000000的而且先加载,这是进程B的这个DLL A可能就被加载到0x13000000了,DLL A在进程B里的module handle 就是0x13000000了。 在95下,模块是共享的,也就是Windows只加载一份模块到内存,所有用到这个模块的进程都映射同一个模块,也就是说在95里每个模块在物理内存里只有一份。NT则不同,他为每个进程都加载一份模块。所以NT比95需要的内存多。所以在NT里不但在不同进程里的Module handle可能不同,连物理地址都是不同的。