duilib修復ActiveXUI控件bug,以支持flash透明動態背景

轉載請說明原出處,謝謝~~

          昨天在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文件的開頭可以看到作者留下的這句話:

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

發佈了84 篇原創文章 · 獲贊 208 · 訪問量 61萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章