古詩詞大全網 - 成語故事 - 金山詞霸的屏幕取詞是什麽原理

金山詞霸的屏幕取詞是什麽原理

壹.基礎知識

首先想編這種程序需要壹些基礎知識。

會用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混合編程的技術。

今天先到這裏吧!最好準備壹份softice for 95/98和金山詞霸,讓我們先來分析壹下別人是怎麽做的。

歡迎與我聯系

E-Mail:yeedong@163.net

主題 屏幕取詞技術系列講座(二)

作者 亦東

很抱歉讓大家久等了!

我看了壹些人的回帖,發現很多人對取詞的原理還是不太清楚。

首先我來解釋壹下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多家小公司。他們的具體實現雖然不同,但大致原理是相同的。

我這些都是隨手寫的,也沒有提綱之類的東西,以後如果有機會我會整理壹下,大家先湊合著看吧!xixi...

主題 關於屏幕取詞的討論(三)

作者 亦東

讓大家久等,很抱歉,前些時候工作忙硬盤又壞了,太不幸了。

這回來點真格的。

咱們以截取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.",切記,否則會系統崩潰。