[转]一个金山词霸的词库读取程序!

一个金山词霸的词库读取程序!
  做这个程序的动机很久了,因为一直在背英语单词,如果可以有一个完整的英语词库,可以完成很多事情,比如,查找一个单词的相似单词,window,widow,wind等。很自然的,这个词库就想从我们的金山词霸下手了。  
  首先,我去google了一下金山词霸的词库,看来没有现成的果子啊,显然词库文件也都是加密了的。所以只能自己动手写一个程序了,单元Kingsoft公司不会找我的麻烦吧!:)  
  在我写下这篇文章的时候,读取单词的进度已经完成了   70%左右(341500/457550个单词)。其实还是存在一些现成的英语单词词库的,在《STL   Tutorial   and   Reference   Guide》一书的示例程序中,有一个名为diction的词库,不过只有20160个单词,而现在,很快我就会有一个457550单词的词库了。  
  OK,下面我就把这个程序的编写过程和大家分享一下。  
  开发平台:Visual   C++   6.0  
  我的词库读取思想是:运行金山词霸2003,在程序中得到它的句柄,进而读取单词列表的子窗口句柄,然后,嘿嘿!把列表中的所有内容读取即可。It   sounds   good,   let's   go   now!  
  1:建立了一个对话框程序,枚举桌面下的所有子窗口,也就是程序的主窗口,窗口标题中包含"金山词霸“的,即为金山词霸2003的主窗口;同样,枚举它的子窗口,窗口标题中包含”WordList“的(使用Spy++跟踪可知),即为单词列表控件。  
  说明:后来修改了一下,应该根据窗口ID来查找单词列表控件更好。  
  CString   strText;  
  CWnd*   pWndCiba=CWnd::GetDesktopWindow()->GetWindow(GW_CHILD);  
  while   (pWndCiba)   {  
  pWndCiba->GetWindowText(strText);  
  TRACE("%s   [%x]\n",strText,pWndCiba->GetSafeHwnd());  
  if   (strText.Find("金山词霸")   !=   -1)  
  break;  
  //   did   not   find   window,   get   next   window   in   list.  
  pWndCiba=pWndCiba->GetWindow(GW_HWNDNEXT);  
  }  
  if   (pWndCiba   ==   NULL)  
  return;  
  CWnd*   pWndWordList=pWndCiba->GetWindow(GW_CHILD);  
  while   (pWndWordList)   {  
  pWndWordList->GetWindowText(strText);  
  TRACE("%s   [%x]\n",strText,pWndWordList->GetSafeHwnd());  
  if   (strText.IsEmpty())  
  break;  
  if   (strText.Find("WordList")   !=   -1)  
  break;  
  //   did   not   find   window,   get   next   window   in   list.  
  pWndWordList=pWndWordList->GetWindow(GW_HWNDNEXT);  
  }  
  if   (pWndWordList->GetSafeHwnd()   ==   NULL)   
  return;  
   
  2:看来工作完成了大半,此时调用以下代码:  
  int   nCount=pWordList->GetItemCount();  
  得到了返回值为457550,这就是词库的单词总数了  
   
  3:问题出现了!  
  然后准备尝试读回列表的第一个单词,就是”A“,使用以下代码:  
  char   pText[256];  
  ListView_GetItemText(pWndWordList.GetSafeHwnd(),0,0,pText,255);  
  结果...金山词霸崩溃,Windows崩溃!或许号称永不死机的WindowsXP没有完蛋吧,总之我只能使用键盘上的Power进行关机了,这也是我在成功前进行了多次的动作。:(  
   
  4:问题在哪里呢?其实要怪自己很久没有编写进程间通信的程序了。  
  ListView_GetItemText()这个宏,实际上是发送LVM_GETITEMTEXT的消息,由接收消息的列表返回文本;但是,char   pText[256];是在我的程序中分配的内存,怎么能由金山词霸的程序来使用呢!  
  后来,我还尝试了这样的方法:  
  HGLOBAL   hGlobal=GlobalAlloc(GPTR,256);  
  char*   pText=(char*)GlobalLock(hGlobal);  
  ListView_GetItemText(pWndWordList->GetSafeHwnd(),0,0,pText,255);  
   
  GlobalUnlock(hGlobal);  
  GlobalFree(hGlobal);  
  其实没有解决本质的问题:GlobalAlloc()分配的内存的确可以供不同进程使用,但是应该在进程间传递hGlobal,即内存空间的句柄,然后由接收消息的程序调用GlobalLock()把hGlobal转换为本地的内存指针,显然Kingsoft公司不会给我们完成这个任务了。  
  如果使用其它进程共享数据的方法,比如shared   memory   files,都存在必须由消息接收者进行处理的问题。那么该怎么办???  
   
  5:既然无法使用进程间共享数据的方法,我们换一个思路,让我们的程序在金山词霸的进程空间运行!这样就不需要考虑进程间共享数据的问题啦。非常幸运,有一种叫做DLL注入进程的方法可以实现。大家可以去google”Injecting   a   DLL   into   Another   Process's   Address   Space“,或者在MSDN中查找LoadDll.exe,我的程序就是基于这个示例。  
  简单介绍LoadDll工程,因为我没有完全研究所有程序。实际上有2个工程,LoadDll,这是一个很简单的命令行程序,用法如下:  
  LoadDll.exe   /L   3448   TestLib.dll   TestFunction  
  /L   表示载入一个DLL  
  3448   表示要注入DLL的进程的ID,可以使用Task   Manager得到  
  TestLib.dll   表示被载入的DLL  
  TestFunction   DLL中的一个导出函数的函数名  
   
  使用LoadDll.exe   /U   3448   TestLib.dll   即可把DLL从进程中卸出。  
   
  6:LoadDll我是根本没有改动,我修改了TestLib.dll中的TestFunction,完全重写了该函数。  
  首先,把步骤1中查找窗口句柄的代码拷贝过来,很不幸,因为TestLib.dll不能使用MFC,所以我只能修改为SDK的版本了。  
  然后,调用  
  ListView_GetItemText(hWndWordList,0,0,pText,255);  
  hWndWordList是列表窗口句柄。开始运行,大功告成了吗?  
  pText中返回的是”1234567890123456789012345678901234567890“!  
  My   God,为什么?!  
  我尝试读取第2行,第100行,结果一样。于是我把这段代码修改一下,读取自己的一个包含列表的程序,很正常的读出了列表的内容。看来问题是出在了金山词霸的单词列表中了。  
   
  在文章接近尾声的时候,读取程序终于运行完了!耗时将近1小时15分钟!没关系了,我也不打算再进行提高,只要逮住了老鼠,就是好猫嘛!  
   
  7:使用Spy++再次跟踪单词列表窗口,发现拥有LVS_OWNERDATA风格,也就是一个虚列表。其实肯定是的,否则40多万行,使用普通的列表,显示速度可以和乌龟或者其兄弟蜗牛比美了。我猜想:  
  1)虚列表不能使用LVM_GETITEMTEXT或者LVM_GETITEM消息进行访问。这个猜想99%是错误的,因为MSDN关于虚列表不能使用的消息中没有包含这2个。限于时间,我没有对其它程序的虚列表控件进行测试了。  
  2)Kingsoft公司防止别人象我这么干,屏蔽了这2个消息。似乎没有其它的接口可以再进行访问了,难道对单词列表控件进行Subclass?因为我不熟悉SDK编程,也放弃了测试。因为,我决定出绝招了!使用一种非常粗鲁且无赖的方法,我,成功了!  
  说明:关于虚列表控件,可以在MSDN中查找“virtual   list   controls"或者”虚列表“。  
   
  8:经过观察金山词霸的使用,发现每次在列表中选择一个单词后,在界面上方的ComboBox会显示该单词,同时右面的窗口会显示单词的解释。没错,我的方法就是去读取ComboBox中的内容,经过测试,ComboBox中的Caption就是单词了。那么怎么读取所有的单词?我们循环的选择所有的单词,就可以得到所有的单词了。代码如下:  
   
  nCount=ListView_GetItemCount(hWndWordList);   //   get   item   count  
  nPrevIndex=ListView_GetNextItem(hWndWordList,-1,LVIS_SELECTED);  
  for   (nIndex   =   0;nIndex   <   nCount;nIndex++)   {  
  if   (nPrevIndex   !=   -1)   {  
  //   un-select   the   previous   item  
  ListView_SetItemState(hWndWordList,nPrevIndex,UINT(~LVIS_SELECTED   &   ~LVIS_FOCUSED   ),LVIS_SELECTED   |   LVIS_FOCUSED);  
  }  
  //   select   the   current   item  
  ListView_SetItemState(hWndWordList,nIndex,LVIS_SELECTED   |   LVIS_FOCUSED   ,LVIS_SELECTED   |   LVIS_FOCUSED);  
  GetWindowText(hWndCombo,pszWord,WORD_MAX);  
  nPrevIndex   =   nIndex;   //   save   the   previous   index  
   
  WriteWord(nIndex,pszWord);   //   write   the   word   into   the   file  
   
  }  
   
  9:最后的小结。  
  由于是使用改变Item的状态的方法去读取单词,大家也就可以理解为什么需要1个多小时读取40多万个单词了。当然,如果你的银子够多,换一台好机器,速度当然更快。我的爱机配置仅为:Celeron   1.7G,   256M。  
  关于程序的思考:  
  1)除了采用把DLL注入进程的方法,是否可以采取全局钩子的方法?网络上流行的星号密码读取程序就是采用这样的方法的,经过简单研究,我放弃了这种方法,不过只是因为我对hook技术不熟悉。  
  2)如何读取单词的解释?就是右面窗口显示的解释,使用GetWindowText()是不能成功的。似乎也可以采取无赖的手段获取的,比如发送一个Ctrl   +   A选择所有文本,然后Ctrl   +   C拷贝内容到剪贴板,接着从剪贴板读取。似乎很傻,但是看来是可以的。这个工作等到欧洲杯结束后再考虑了,如果哪位朋友有更好的意见,请不吝赐教!

原文:http://topic.csdn.net/t/20040621/09/3108877.html

posted @ 2007-12-19 13:35  NewSea  阅读(1153)  评论(1编辑  收藏  举报