最近碰到一个需求,要嵌入IE浏览器到应用程序中。在开发的过程中,碰到了需要加载HTTPS网站,而该网站的证书无效的问题。
如果是直接用IE浏览器打开的话,虽然也会报错,但至少可以通过“转到此网页(不推荐)”继续访问,而CDHtmlDialog提供的嵌入式IE浏览器,则没有这个操作。
当然,这是一个安全设置,正常来说不应该继续访问,要么提供有效的证书以继续访问。但是我的需求是加载一个已知的站点,需要能正常访问,那这个时候就需要拦截安全报警,忽略掉然后继续访问了。
微软的官方文档翻了一遍,由于英格利希不大行,只找到了需要处理 IHttpSecurity 接口,但是苦于COM和ATL都没学好,根本不知道怎么用,不得不继续bing,最后搜到了蒋晟大佬的一篇文章:Howto: Ignoring web browser certificate errors in a webbrowser host
才有了眉目,当然,大佬的文章用的.NET程序作为例子,我尝试过,确实可以,但我还是想用C++来实现,还好文章后面用C++举了个例,并且说的很清楚了,要实现 IHttpSecurity 接口和暴露INewWindowManager接口是一样的,最终我尝试了一下,完成了这个功能:
可以看到页面上方有一个安全提示,但是已经可以直接打开网页了。
要实现这个功能,就需要重载CDHtmlDialog的CreateControlSite接口,传入自定义的COleControlSite,然后在这个类里,实现 IHttpSecurity 接口,就可以处理 OnSecurityProblem 函数了。
首先是改写 CreateControlSite :
BOOL CWebBrowser::CreateControlSite(COleControlContainer* pContainer, COleControlSite** ppSite, UINT , REFCLSID )
{
CMyBrowserControlSite *pBrowserSite =
new CMyBrowserControlSite(pContainer, this);
if (!pBrowserSite)
return FALSE;
*ppSite = pBrowserSite;
return TRUE;
}
这个 CMyBrowserControlSite 就是继承自 COleControlSite ,然后我们需要处理 QueryService 接口,因为WebBrowser插件在创建的过程中,会回调这个接口,其中就包括 IHttpSecurity,接下来就是定义 CMyBrowserControlSite:
class CMyBrowserControlSite : public COleControlSite
{
public:
CMyBrowserControlSite(COleControlContainer* pCtrlCont, CWebBrowser *pHandler);
virtual ~CMyBrowserControlSite(void);
protected:
DECLARE_INTERFACE_MAP()
BEGIN_INTERFACE_PART(ServiceProvider, IServiceProvider)
STDMETHOD(QueryService) (
/* [in] */ REFGUID guidService,
/* [in] */ REFIID riid,
/* [out] */ void __RPC_FAR *__RPC_FAR *ppvObject);
END_INTERFACE_PART(ServiceProvider)
BEGIN_INTERFACE_PART(HttpSecurity, IHttpSecurity)
STDMETHOD(GetWindow)(
/* [in] */ REFGUID rguidReason,
/* [out] */ HWND *phwnd);
STDMETHOD(OnSecurityProblem)(/* [in] */ DWORD dwProblem);
END_INTERFACE_PART(HttpSecurity)
protected:
CWebBrowser* m_pView;
};
由于 IHttpSecurity 继承自 IWindowForBindingUI,而 IWindowForBindingUI 里又有一个虚函数
GetWindow,所以你必须在你的实现里把这些虚函数统统实现。
IHttpSecurity : public IWindowForBindingUI
{
public:
virtual HRESULT STDMETHODCALLTYPE OnSecurityProblem(
/* [in] */ DWORD dwProblem) = 0;
};
IWindowForBindingUI : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetWindow(
/* [in] */ REFGUID rguidReason,
/* [out] */ HWND *phwnd) = 0;
};
根据微软官方文档所述,这个 GetWindow 就是获取一个窗口句柄给WebBrowser用来作为容器窗口的,说白了就是 CDHtmlDialog 那个窗口,到此就很简单了,直接上代码,把所有接口实现就行了:
BEGIN_INTERFACE_MAP(CMyBrowserControlSite, COleControlSite)
INTERFACE_PART(CMyBrowserControlSite, IID_IServiceProvider, ServiceProvider)
INTERFACE_PART(CMyBrowserControlSite, IID_IHttpSecurity, HttpSecurity)
END_INTERFACE_MAP()
CMyBrowserControlSite::CMyBrowserControlSite(COleControlContainer* pCtrlCont, CWebBrowser *pHandler)
: COleControlSite(pCtrlCont), m_pView(pHandler)
{
}
CMyBrowserControlSite::~CMyBrowserControlSite(void)
{
}
ULONG CMyBrowserControlSite::XServiceProvider::AddRef()
{
METHOD_PROLOGUE(CMyBrowserControlSite, ServiceProvider);
return pThis->ExternalAddRef();
}
ULONG CMyBrowserControlSite::XServiceProvider::Release()
{
METHOD_PROLOGUE(CMyBrowserControlSite, ServiceProvider);
return pThis->ExternalRelease();
}
HRESULT CMyBrowserControlSite::XServiceProvider::QueryInterface(REFIID iid, LPVOID* ppvObj)
{
METHOD_PROLOGUE(CMyBrowserControlSite, ServiceProvider);
return pThis->ExternalQueryInterface(&iid, ppvObj);
}
ULONG CMyBrowserControlSite::XHttpSecurity::AddRef()
{
METHOD_PROLOGUE(CMyBrowserControlSite, HttpSecurity);
return pThis->ExternalAddRef();
}
ULONG CMyBrowserControlSite::XHttpSecurity::Release()
{
METHOD_PROLOGUE(CMyBrowserControlSite, HttpSecurity);
return pThis->ExternalRelease();
}
HRESULT CMyBrowserControlSite::XHttpSecurity::QueryInterface(REFIID iid, LPVOID* ppvObj)
{
METHOD_PROLOGUE(CMyBrowserControlSite, HttpSecurity);
return pThis->ExternalQueryInterface(&iid, ppvObj);
}
HRESULT CMyBrowserControlSite::XHttpSecurity::GetWindow(REFGUID rguidReason, HWND *phwnd)
{
METHOD_PROLOGUE(CMyBrowserControlSite, HttpSecurity);
if (rguidReason == IID_IHttpSecurity || rguidReason == IID_IWindowForBindingUI)
{
*phwnd = pThis->m_pView->m_hWnd;
return S_OK;
}
else
{
*phwnd = nullptr;
return S_FALSE;
}
}
HRESULT CMyBrowserControlSite::XHttpSecurity::OnSecurityProblem(DWORD dwProblem)
{
return S_OK;
}
HRESULT CMyBrowserControlSite::XServiceProvider::QueryService(REFGUID guidService, REFIID riid, void __RPC_FAR *__RPC_FAR *ppvObject)
{
if (riid == IID_IHttpSecurity || riid == IID_IWindowForBindingUI)
{
METHOD_PROLOGUE(CMyBrowserControlSite, ServiceProvider);
return (HRESULT)pThis->ExternalQueryInterface(&riid, ppvObject);
}
ppvObject = nullptr;
return E_NOINTERFACE;
}
说来惭愧,学了多年的C++,一直没认真学,下回得静下心来好好深入学习学习了。