转载请说明原出处,谢谢~~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