轉載請說明原出處,謝謝~~
昨天在QQ控件裡和同學說起QQ2013登陸窗體的開發,從界面角度考慮,單單一個登陸界面是很容易做出來的。騰訊公司為了防止各種盜号行為可謂煞費苦心,QQ2013采用了動态背景就是為了防止界面型盜号木馬,這種盜号木馬做起來很簡單,容易騙過很多電腦小白。而采用動态背景後就加大了這種木馬的開發難度。
在Duiengine界面庫中,已經有高手做出來一個高仿QQ界面的Demo。其中的登陸窗體隻要使用flash做背景就可以了。在duilib中,已經有做好的ActiveXUI控件和flashUI控件,今天沒事就準備做一個仿QQ登入器。
先打開了duilib的flash demo,我準備測試一下在flash控件的上層是否可以繪制控件,但是問題出現了。在duilib中有兩種播放flash的方法,第一是使用ActiveXUI控件去指定系統的Flash控件的clsid,然後在c++代碼裡再通過ActiveXUI控件的GetControl方法去擷取IShockwaveFlash接口,進而進一步控制播放flash;第二是直接用duilib的flashUI控件。但是我發現,使用ActiveXUI控件播放的flash界面是透明無句柄的卻是靜态的,隻是原flash檔案的第一幀;而flashUI控件是動态的,但卻自動建立了一個子窗體而不是透明無句柄界面,因為有了子窗體,就無法再把其他duilib控件繪制到Flash界面之上,是以這兩個控件都無法滿足我的需求。又是一場bug修複之旅(duilib的bug的确有點多了·····)。
分析過程:
首先我想修改一下FlashUI控件的源碼,看看能否解決問題,在UIFlash.h檔案的開頭可以看到作者留下的這句話:
作者說要想讓CFlashUI類實作透明模式繪圖,需要實作IOleInPlaceSiteWindowless接口,這個接口是負責會繪制出無句柄的com元件并允許一個無視窗的對象處理window消息。這個接口在UIActiveX.cpp檔案的CAvtiveXCtrl類中已經實作了,我查閱一些資料後給CFlashUI類補充了這個接口,卻任然無法達到效果。debug後發現根本就沒有進入到響應的函數中,我認為需要把另外的 IOleClientSite,
IOleControlSite, IObjectWithSite, IOleContainer等接口也都實作了才會達到效果,但是這些接口的很多功能都已經在CAvtiveXCtrl類中寫好了,我再重寫一遍顯然不是個好辦法。是以我把給CFlashUI寫好的IOleInPlaceSiteWindowless接口代碼都删掉,目标轉向去修複CActiveXUI類的代碼。
通過debug模式下斷點首先搞清楚了整個CActiveXUI.cpp檔案中的幾個類的執行流程。COM元件的主要繪制是在CAvtiveXCtrl類中,總體的執行流程為:com調用載體的IOleClientSite::QueryInterface,申請IOleInPlaceSite。在對象确定了載體是否具有定位能力之後,詢問載體是否可以立即通過調用IOleInPlaceSite::CanInPlaceActivate定位激活該對象。在對象确定它可以進行定位激活之後,它通過調用IOleInPlaceSite::OnInPlaceActivate把自己的意圖告訴載體。然後通過調用IOleInPlaceSite::GetWindowContext,它得到指向其它兩個載體接口----IOleInPlaceUIWindow(面向文檔的)和IOleInPlaceFrame的指針,以及其他必要的資訊(比如繪制的位置)。
在duilib中,OnInPlaceActivate函數又調用了 OnInPlaceActivateEx函數,函數源碼為:
在這裡用過參數dwFlahs, if( (dwFlags & ACTIVATE_WINDOWLESS) != 0 )語句确定是否去試圖建立無視窗的執行個體,而ACTIVATE_WINDOWLESS常量的值為1,如果dwFlags參數值不為1,就不回去視圖建立無視窗執行個體,進而去執行後面的Hr
= CreateActiveXWnd();語句,在這裡調用了函數CreateActiveXWnd,這個函數的内容為:
意思就是如果不建立無窗體的執行個體,就調用CreateActiveXWnd函數去建立一個CActiveXWnd執行個體,這是duilib自定義的窗體,在類内建立了子窗體,讓com元件附着到這個子窗體上,函數把類CActiveXWnd的執行個體指派給m_pWindow變量,而他就是整個bug的核心關鍵。
建立無窗體Flash的流程:
前面說了一堆隻是鋪墊,現在我針對建立無窗體Flash這個點來說一下他的執行步驟,bug就在這裡了!
我直接使用FlashDemo來說明,在demo裡,窗體收到_T("showactivex") 事件後就得知CAcviteXUI控件要顯示出flash動畫了,然後調用如下語句來初始化Flash:
當執行到pFlash->put_WMode( _bstr_t(_T("Transparent") ) );語句時,說明我們想建立一個透明無窗體的flash,這時就會先調用CActiveXCtrl類的CanWindowlessActivate函數來确定是否可以建立無窗體執行個體,此函數傳回真,在flash元件确認了可以建立無窗體執行個體後就回去主動調用OnInPlaceActivateEx函數并且把dwFlags參數設定為1,這時在OnInPlaceActivateEx函數内會去試圖建立無窗體執行個體,如果建立成功了就不會執行CreateActiveXWnd函數,這個函數不執行,那麼m_pWindow變量的值就是NULL。(而事實是可以建立成功,是以m_pWindow就為NULL)。此後flash元件會調用GetWindowContext函數去擷取顯示flash需要的必要資訊,而這個函數就是bug的來源了!
先看此函數的源碼:
可以看到,代碼裡有一處判斷
當m_pWindow不為NULL時就為lprcPosRect和lprcClipRect參數指派,這兩個參數決定了flash元件的輸出的位置。而我前面分析了,m_pWindow恰好就是NULL,是以這兩個參數沒有被指派,是以最終無法正常輸出flash動畫,我們就隻能得到靜态的flash效果了,此bug的修複方法很簡單,就是如果m_pWindow為NULL,就把這兩個參數指派為CActiveXUI控件的位置,修複後的代碼為:
隻需要修複這一處代碼,我們用FlashDemo就可以建立出無窗體的透明flash背景了。效果如下:

這個透明無窗體的Flash問題解決了,就可以很容易做出個仿QQ2013登陸界面了,這是我簡單做得一個:
這個仿QQ2013登入器的背景是動态的,不過好像放到部落格上就成了靜态的了······因為這個QQ登陸器修複bug
無關,我就在下一篇部落格裡說明一下QQ2013登入器了。
個人水準有限,如果發現我的部落格裡有說明不當的地方,請提醒我!
Redrain 2014.8.10 QQ:491646717