最近在做一个屏幕取词的模块,类似金山词霸的取词功能。
因为自己也是边学边做,所以遇到了不少问题,希望大家能够指点或者讨论。
首先说说我的想法吧,基本步骤是:
1。通过鼠标hook或者键盘hook把自己的dll注入其他进程。
2、改变注入进程内文字输出函数,如TextOutA(),TextOutW()等的入口地址
   ,使其指向我自己定义的函数,实现api的替换。
3、在鼠标所在位置放置一个小窗口,触发WM_PAINT消息,当该进程在响应
   WM_PAINT时调用文字输出函数,就会被我替换的函数截获。
4、通过鼠标坐标和文本的输出坐标定位鼠标指向的词语。存在问题:
1、按钮或者菜单上的文本捕捉不到。
   举个例子,如果一个对话框中有一个按钮“Yes",则当我枚举这个对话框进程
的时候,根本没有找到gdi32.dll这个模块(用depends观察也没有)
,也就没有办法替换TextOutA()等函数。那么这个"yes"是怎么输出的呢?
   我的想法是可能gdi32.dll是在进程中通过LoadLibrary()动态调用的,因为
即使按钮上的文本也是需要用TextOutA()等函数输出的,但是这样的话,我就想
不到替换的办法了。这个问题比较严重。
2、鼠标定位文本的问题
    有什么好的办法可以通过鼠标坐标和文本的输出坐标定位鼠标指向的词语呢?谢谢。

解决方案 »

  1.   

    Win9x系统好象调用 16位dll gdi.dll中TextOutA()如何调用,我也想知道。我也想做,16位dll可调用32位dll,反之不行或很难。交个朋友,常联系:[email protected].
      

  2.   

    菜单我不知道咋整。
    但按钮上的文本可以通过常规方法获得。
    WindowFromPoint得到鼠标所指窗口。
    GetClassName 的得到 窗口类型是不是 “BUTTON”
    GetWindowText 得到按钮文本。金山词霸里可以单独设置是否对 按钮菜单取词。
    可见他也不是一味的用钩子。否则就很难区别对待。
      

  3.   

    楼上说得有道理,我就是想得太片面了,你说的方法应该可行的,
    我马上试试。
     3jaja(3++输入法) 兄弟,很高兴你和我有同样的兴趣,大家交个朋友
    我的邮箱是[email protected]
      

  4.   

    icansaymyabc:万一人家的按钮是从CButton派生的,例如类名叫CBtn,那不就完蛋了。
      

  5.   

    papaya_stone,你好
        如果是windows默认的按钮,那么classname应该都是BUTTON,
        如果这个窗口是自己绘制的,那么应该可以捕获它的TextOut函数。
        现在我不明白的是,windows默认的按钮难道就不用TextOut输出文本吗?
    但是我始终捕捉不到它的输出函数。
      

  6.   

    还有:ExtTextOut(),DrawText();
      

  7.   

    3jaja,这个我也知道,现在问题是有些地方,我没有办法截获它的输出函数。
    1、按钮,菜单,这些倒可以用上面提到的GetWindowText获得。
    2、子窗口控件,比如说vc里面的代码窗口,现在也没有办法截获。真是头疼啊
      

  8.   

    在菜单上可用GetMenuItemRect获得菜单项矩形的屏幕坐标位置,再跟据鼠标坐标进行判断。
    在按钮上用“正常”的方法应该可以取到,可能是你没有拦截住API。不知你是用什么方法拦截API的,若是修改PE文件导入表,则无法拦截WIN9X下的16位代码,
    若是往API上直接写跳转指令,则要写两个DLL,一个用于WIN9X,一个用于WIN2000,
    并且要把WIN9X下的DLL用编译指令“pragma comment(linker,"/base:0x80000000")”加载到高端内存区,使各进程都能“看到”。
      

  9.   

    对了,忘了告诉你,我只在WIN9X下在菜单中取到词,在WIN2000下也没取到。
      

  10.   

    nethares大哥,谢谢你的关注。
        我原来使用的办法是修改导入表,也就是《windows核心编程》里面给出例子的方法。
    但是发现这样做常常拦截不到API,主要在进程中根本找不到gdi32.dll这个模块。比如说
    一个空对话框,上面只放一个按钮,这种情况就找不到gdi32.dll了。下面是我拦截的代码,
    请指点一下。
    PROC SetAPIEntry( HANDLE hFromModule, //注入进程句柄
    LPCSTR lpszFunctionName,//要替换的函数名称
    LPCSTR lpszFunctionModule,//模块名称
    PROC pfnNewProc//新函数指针
    )
    {
    PROC pfnOriginalProc; 
    PIMAGE_DOS_HEADER pDosHeader; 
    PIMAGE_NT_HEADERS pNTHeader; 
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc; 
    PIMAGE_THUNK_DATA pThunk; 

    if ( IsBadCodePtr(pfnNewProc) ) // 验证有效性
    return 0; 

    pfnOriginalProc = GetProcAddress( GetModuleHandle(lpszFunctionModule), 
    lpszFunctionName ); //被拦截函数真正的地址值

    if ( !pfnOriginalProc ) 
    return 0; 

    pDosHeader = (PIMAGE_DOS_HEADER)hFromModule; 

    if ( IsBadReadPtr(pDosHeader, sizeof(IMAGE_DOS_HEADER)) )//验正有效性
    return 0; 

    if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE ) //同上
    return 0; 

    pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDosHeader, pDosHeader->e_lfanew); 

    if ( IsBadReadPtr(pNTHeader, sizeof(IMAGE_NT_HEADERS)) ) //同上
    return 0; 

    if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE ) //同上
    return 0; 

    pImportDesc = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, pDosHeader,  pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    if ( pImportDesc == (PIMAGE_IMPORT_DESCRIPTOR)pNTHeader ) //同上
    return 0;  while ( pImportDesc->Name ) 

    PSTR pszModName = MakePtr(PSTR, pDosHeader, pImportDesc->Name);

    if ( stricmp(pszModName, lpszFunctionModule) == 0 ) 
    break; 
        pImportDesc++;
    }
    if ( pImportDesc->Name == 0 ) 
    return 0;  pThunk = MakePtr(PIMAGE_THUNK_DATA, pDosHeader, pImportDesc->FirstThunk); 

    while ( pThunk->u1.Function ) 

    if ( pThunk->u1.Function == (PDWORD)pfnOriginalProc ) 
    //拦截函数被找到

    pThunk->u1.Function = (PDWORD)pfnNewProc; //替换函数
    return pfnOriginalProc; 

    pThunk++;  
    }  return 0;
    }
    因为我也是边学边用,可能有些概念还不清楚,还请指出,谢谢
      

  11.   

    不好意思,上面的MakePtr宏
    #define MakePtr(cast,ptr,addvalue) cast(DWORD(ptr)+DWORD(addvalue))现在我正在试验写跳转指令的方法,目前来说效果不错。
    能不能说明一下为什么这种办法要分别在win9x和2k中写两个dll呢?相信很多朋友
    都想了解这些知识的,3x