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

轉載請說明原出處,謝謝~~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

相關文章
相關標籤/搜索