轉載請說明原出處,謝謝~~c++
昨天在QQ控件裏和同窗提及QQ2013登錄窗體的開發,從界面角度考慮,單單一個登錄界面是很容易作出來的。騰訊公司爲了less
防止各類盜號行爲可謂煞費苦心,QQ2013採用了動態背景就是爲了防止界面型盜號木馬,這種盜號木馬作起來很簡單,容易騙過很函數
多電腦小白。而才用動態背景後就加大了這種木馬的開發難度。測試
在Duiengine界面庫中,已經有高手作出來一個高仿QQ界面的Demo。其中的登錄窗體只要使用flash作背景就能夠了。在duilib動畫
中,已經有作好的ActiveXUI控件和flashUI控件,今天沒事就準備作一個仿QQ登陸器。ui
先打開了duilib的flash demo,我準備測試一下在flash控件的上層是否能夠繪製控件,可是問題出現了。在duilib中有兩種播放this
flash的方法,第一是使用ActiveXUI控件去指定系統的Flash控件的clsid,而後在c++代碼裏再經過ActiveXUI控件的GetControl方法去spa
獲取IShockwaveFlash接口,進而進一步控制播放flash;第二是直接用duilib的flashUI控件。可是我發現,使用ActiveXUI控件播放的debug
flash界面是透明無句柄的倒是靜態的,只是原flash文件的第一幀;而flashUI控件是動態的,但卻自動建立了一個子窗體而不是透明指針
無句柄界面,由於有了子窗體,就沒法再把其餘duilib控件繪製到Flash界面之上,因此這兩個控件都沒法知足個人需求。又是一場
bug修復之旅(duilib的bug的確有點多了·····)。
分析過程:
首先我想修改一下FlashUI控件的源碼,看看可否解決問題,在UIFlash.h文件的開頭能夠看到做者留下的這句話:
class UILIB_API CFlashUI : public CActiveXUI // , public IOleInPlaceSiteWindowless // 透明模式繪圖,須要實現這個接口 , public _IShockwaveFlashEvents , public ITranslateAccelerator
做者說要想讓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函數,函數源碼爲:
STDMETHODIMP CActiveXCtrl::OnInPlaceActivateEx(BOOL* pfNoRedraw, DWORD dwFlags) { DUITRACE(_T("AX: CActiveXCtrl::OnInPlaceActivateEx")); ASSERT(m_pInPlaceObject==NULL); if( m_pOwner == NULL ) return E_UNEXPECTED; if( m_pOwner->m_pUnk == NULL ) return E_UNEXPECTED; ::OleLockRunning(m_pOwner->m_pUnk, TRUE, FALSE); HWND hWndFrame = m_pOwner->GetManager()->GetPaintWindow(); HRESULT Hr = E_FAIL; if( (dwFlags & ACTIVATE_WINDOWLESS) != 0 ) { m_bWindowless = true; Hr = m_pOwner->m_pUnk->QueryInterface(IID_IOleInPlaceObjectWindowless, (LPVOID*) &m_pInPlaceObject); m_pOwner->m_hwndHost = hWndFrame; m_pOwner->GetManager()->AddMessageFilter(m_pOwner); } if( FAILED(Hr) ) { m_bWindowless = false; Hr = CreateActiveXWnd(); if( FAILED(Hr) ) return Hr; Hr = m_pOwner->m_pUnk->QueryInterface(IID_IOleInPlaceObject, (LPVOID*) &m_pInPlaceObject); } if( m_pInPlaceObject != NULL ) { CDuiRect rcItem = m_pOwner->m_rcItem; if( !m_bWindowless ) rcItem.ResetOffset(); m_pInPlaceObject->SetObjectRects(&rcItem, &rcItem); } m_bInPlaceActive = SUCCEEDED(Hr); return Hr; }
在這裏用過參數dwFlahs, if( (dwFlags & ACTIVATE_WINDOWLESS) != 0 )語句肯定是否去試圖建立無窗口的
實例,而ACTIVATE_WINDOWLESS常量的值爲1,若是dwFlags參數值不爲1,就不回去視圖建立無窗口實例,進而去執行後面的Hr = CreateActiveXWnd();語句,在這裏調用了函數CreateActiveXWnd,這個函數的內容爲:
HRESULT CActiveXCtrl::CreateActiveXWnd() { if( m_pWindow != NULL ) return S_OK; m_pWindow = new CActiveXWnd; if( m_pWindow == NULL ) return E_OUTOFMEMORY; m_pOwner->m_hwndHost = m_pWindow->Init(this, m_pOwner->GetManager()->GetPaintWindow()); return S_OK; }
意思就是若是不建立無窗體的實例,就調用CreateActiveXWnd函數去建立一個CActiveXWnd實例,這是duilib自
定義的窗體類,在類內創建了子窗體,讓com組件附着到這個子窗體上,函數把類CActiveXWnd的實例賦值給
m_pWindow變量,而他就是整個bug的核心關鍵。
建立無窗體Flash的流程:
前面說了一堆只是鋪墊,如今我針對建立無窗體Flash這個點來講一下他的執行步驟,bug就在這裏了!
我直接使用FlashDemo來講明,在demo裏,窗體收到_T("showactivex") 事件後就得知CAcviteXUI控件要顯示出
flash動畫了,而後調用以下語句來初始化Flash:
if( msg.pSender->GetName() == _T("flashActiveX") ) { IShockwaveFlash* pFlash = NULL; CActiveXUI* pActiveX = static_cast<CActiveXUI*>(msg.pSender); pActiveX->GetControl(__uuidof(IShockwaveFlash), (void**)&pFlash); if( pFlash != NULL ) { pFlash->put_WMode( _bstr_t(_T("Transparent") ) ); pFlash->put_Movie( _bstr_t(CPaintManagerUI::GetInstancePath() + _T("\\skin\\FlashRes\\test.swf")) ); pFlash->DisableLocalSecurity(); pFlash->put_AllowScriptAccess(L"always"); BSTR request,response; request = SysAllocString(L"<invoke name=\"setButtonText\" returntype=\"xml\"><arguments><string>Click me!</string></arguments></invoke>"); response = SysAllocString(L""); pFlash->CallFunction(request, &response); SysFreeString(request); SysFreeString(response); } }
當執行到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的來源了!
先看此函數的源碼:
STDMETHODIMP CActiveXCtrl::GetWindowContext(IOleInPlaceFrame** ppFrame, IOleInPlaceUIWindow** ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo) { DUITRACE(_T("AX: CActiveXCtrl::GetWindowContext")); if( ppDoc == NULL ) return E_POINTER; if( ppFrame == NULL ) return E_POINTER; if( lprcPosRect == NULL ) return E_POINTER; if( lprcClipRect == NULL ) return E_POINTER; if (m_pWindow) { ::GetClientRect(m_pWindow->GetHWND(),lprcPosRect); ::GetClientRect(m_pWindow->GetHWND(),lprcClipRect); } *ppFrame = new CActiveXFrameWnd(m_pOwner); *ppDoc = NULL; ACCEL ac = { 0 }; HACCEL hac = ::CreateAcceleratorTable(&ac, 1); lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO); lpFrameInfo->fMDIApp = FALSE; lpFrameInfo->hwndFrame = m_pOwner->GetManager()->GetPaintWindow(); lpFrameInfo->haccel = hac; lpFrameInfo->cAccelEntries = 1; return S_OK; }
能夠看到,代碼裏有一處判斷
if (m_pWindow) { ::GetClientRect(m_pWindow->GetHWND(),lprcPosRect); ::GetClientRect(m_pWindow->GetHWND(),lprcClipRect); }
當m_pWindow不爲NULL時就爲lprcPosRect和lprcClipRect參數賦值,這兩個參數決定了flash組件的輸出的位
置。而我前面分析了,m_pWindow剛好就是NULL,因此這兩個參數沒有被賦值,因此最終沒法正常輸出flash動畫,
咱們就只能獲得靜態的flash效果了,此bug的修復方法很簡單,就是若是m_pWindow爲NULL,就把這兩個參數賦值爲
CActiveXUI控件的位置,修復後的代碼爲:
STDMETHODIMP CActiveXCtrl::GetWindowContext(IOleInPlaceFrame** ppFrame, IOleInPlaceUIWindow** ppDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo) { DUITRACE(_T("AX: CActiveXCtrl::GetWindowContext")); if( ppDoc == NULL ) return E_POINTER; if( ppFrame == NULL ) return E_POINTER; if( lprcPosRect == NULL ) return E_POINTER; if( lprcClipRect == NULL ) return E_POINTER; if (m_pWindow) { ::GetClientRect(m_pWindow->GetHWND(),lprcPosRect); ::GetClientRect(m_pWindow->GetHWND(),lprcClipRect); } else { RECT rcItem = m_pOwner->GetPos(); memcpy(lprcPosRect, &rcItem, sizeof(rcItem)); memcpy(lprcClipRect, &rcItem, sizeof(rcItem)); } *ppFrame = new CActiveXFrameWnd(m_pOwner); *ppDoc = NULL; ACCEL ac = { 0 }; HACCEL hac = ::CreateAcceleratorTable(&ac, 1); lpFrameInfo->cb = sizeof(OLEINPLACEFRAMEINFO); lpFrameInfo->fMDIApp = FALSE; lpFrameInfo->hwndFrame = m_pOwner->GetManager()->GetPaintWindow(); lpFrameInfo->haccel = hac; lpFrameInfo->cAccelEntries = 1; return S_OK; }
只須要修復這一處代碼,咱們用FlashDemo就能夠建立出無窗體的透明flash背景了。效果以下:
這個透明無窗體的Flash問題解決了,就能夠很容易作出個仿QQ2013登錄界面了,這是我簡單作得一個:
這個仿QQ2013登陸器的背景是動態的,不過好像放到博客上就成了靜態的了······由於這個QQ登錄器修復bug
無關,我就在下一篇博客裏說明一下QQ2013登陸器了。
我的水平有限,若是發現個人博客裏有說明不當的地方,請提醒我!
Redrain 2014.8.10 QQ:491646717