1. 程式人生 > >c++內嵌IWebBrowser2功能整理

c++內嵌IWebBrowser2功能整理

目的:加深對IWebBrowser2的理解,整理。方便以後學習和使用。也方便需要這反面的朋友少點彎路。

宣告:有些知識點是本人在使用和學習中藉助網路搜尋到的,所以難免會有雷同,我會盡量標註原著的出處,當然也可能找不到原著的出去了,如果牽扯到版權或者其他的可以通知我,我會跟你一同處理。本文的原始碼也有部分是來自網路和msdn上面,出處我也會盡量標明。

前言:前段時間,一個大學同學問我,在win下面c++怎麼獲取web的登陸狀態,是哪個使用者登陸的,我告訴他可以內嵌webbrowser實現,他叫我幫忙提供一個demo給他,所以我利用空餘時間幫忙做了一個win32的程式模擬登陸csdn的demo給他。後面他又說,不要模擬登陸,要使用者自己點選登陸,然後c++來獲取登陸是否成功,並且是哪個使用者在登陸。我說那就得改改程式,給他提了幾個點。最後也不知道他們公司的c++程式設計師 搞定沒。後面自己想了一下,覺得有必要把c++與IWebBrowser2的東西整理一下,自己也理一下,所以用了點時間添加了一個demo,並且寫這篇部落格。

正文

1.開發環境和知識點

        程式碼是使用vs2008編譯,是win32的程式(不是mfc)。閱讀的時候需要你瞭解過簡單的win32程式設計,也會一些簡單的web基礎(html元素的概念),會根據ie瀏覽器查詢你需要的html元素。如果你具備這些  那麼閱讀和學習這個東西就很簡單了。

2.引用和借鑑的url

       這個我會盡量列舉在這裡,找不到出去就沒法發了

以上就是我能記住的參考過的url了。下面的文章不在註明具體是那個知識點參照上面的url了。

3.win32模擬登陸csdn程式

      原理說明:內嵌一個webbrowser的com元件,指定url就會把頁面加載出來(具體原理需要http通訊,獲取返回值,解析頁面,執行js等等),載入之後我們根據html標籤的id或者是name或者是tag等查詢你需要的IHTMLElement物件(id和name的查詢,可以用ie開啟url按f12鍵查詢元素,檢視html標籤具體資訊),接下來就是對IHTMLElement的操作,可以輸入資料,觸發點選事件等。點選之後等待頁面跳轉或者是重新載入,根據載入的新資料來判斷是否登入成功(一般登入成功會跳轉頁面,有些web會記錄cookie等其他資訊,這些都需要根據不同的web網站 做不同的判斷)

      首先,需要一個基於對話方塊的win32程式,上面有2個按鈕(一個模擬登陸按鈕,一個導航按鈕)和3個輸入框(一個輸入url,一個輸入使用者名稱,一個輸入密碼)

程式碼段

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE h0, LPTSTR lpCmdLine, int nCmdShow)
{
	DialogBox(hInst,MAKEINTRESOURCE(IDD_DIALOG_MAIN),NULL,(DLGPROC)MainDialogProc);
	return TRUE;
}

//   Windows   事件處理
LRESULT CALLBACK MainDialogProc(HWND hDlg,UINT message,
								WPARAM wParam,LPARAM lParam)
{
	//訊息的處理,我想你要的就是這裡了
	switch(message)
	{
	case WM_INITDIALOG:
		return DialogInit(hDlg,wParam,lParam);
		break;
	case WM_COMMAND:     
		return CommandFun(hDlg,wParam,lParam);
		break;
	case WM_CLOSE://關閉在這裡
		EndDialog(hDlg,TRUE);  
		return TRUE;
		break;
	}
	return FALSE;
}

LRESULT DialogInit(HWND hDlg,WPARAM wParam,LPARAM lParam)
{
	RECT rc;
	GetClientRect(hDlg, &rc);

	HWND hStaticOption = GetDlgItem(hDlg,IDC_STATIC_OPTION);
	RECT rc_Option;
	GetWindowRect(hStaticOption,&rc_Option);
	//
	SetDlgItemText(hDlg,IDC_EDIT_URL,_T("https://passport.csdn.net/account/login"));
	SetDlgItemText(hDlg,IDC_EDIT_NAME,_T("xxxxxxx"));
	SetDlgItemText(hDlg,IDC_EDIT_PWD,_T("xxxxxx"));
	//
	RECT webRc;
	webRc.left = 0;
	webRc.top = rc_Option.bottom;
	webRc.right = rc.right;
	webRc.bottom = rc.bottom;
	gWebAutoLogin = new WebAutoLogin(hDlg, webRc);
	gWebAutoLogin->Navigate(L"https://www.baidu.com/");
	StartWaitWebLoad(hDlg);
	return TRUE;
}

LRESULT CommandFun(HWND hDlg,WPARAM wParam,LPARAM lParam)
{
	//IDC_CLOSE   是我在對話方塊中加入的一個按鈕的ID   也可以關閉
	if (LOWORD(wParam)==IDOK)       
	{
		PostQuitMessage(0);
	}
	else if (LOWORD(wParam)==IDC_BTN_GO)
	{	
		LoadWeb(hDlg);
	}
	else if (LOWORD(wParam)==IDC_BTN_LOGIN)  
	{
		SimulateLogin(hDlg);
	}
	else
	{
		return FALSE;
	}
	return TRUE;
}

void LoadWeb(HWND hDlg)
{
	wchar_t url[MAX_PATH*2+1] ={0};
	GetDlgItemText(hDlg,IDC_EDIT_URL,url,MAX_PATH*2);
	std::wstring strUrl;
	strUrl.assign(url);

	gWebAutoLogin->Navigate(strUrl);
	StartWaitWebLoad(hDlg);
}

void SimulateLogin(HWND hDlg)
{
	wchar_t name[MAX_PATH+1] ={0};
	GetDlgItemText(hDlg,IDC_EDIT_NAME,name,MAX_PATH);
	wchar_t pswd[MAX_PATH+1] ={0};
	GetDlgItemText(hDlg,IDC_EDIT_PWD,pswd,MAX_PATH);

	std::wstring userName;
	userName.assign(name);
	std::wstring password;
	password.assign(pswd);

	if (gWebAutoLogin->AutoLogin(hDlg,userName,password))
	{
		bIsSimulateLogin = TRUE;
		StartWaitWebLoad(hDlg);
	}
}


這裡面主要是用gWebAutoLogin物件,這個是簡單封裝了的一個WebAutoLogin類。

下面就是 WebAutoLogin類的實現主程式碼片段

WebAutoLogin::WebAutoLogin(HWND hwnd,RECT webRc)
{
	LPOLESTR pszName=OLESTR("shell.Explorer.2");
	m_WinContainer.Create(hwnd, webRc, 0,WS_CHILD |WS_VISIBLE);
	m_WinContainer.CreateControl(pszName);
	HRESULT hr = m_WinContainer.QueryControl(__uuidof(IWebBrowser2),(void**)&m_iWebBrowser); 
	if(FAILED(hr))
	{
		MessageBox(hwnd,_T("獲取IWebBrowser2 物件失敗!!!"),_T("錯誤"),MB_OK|MB_ICONERROR);
		m_iWebBrowser = NULL;
	}
}

WebAutoLogin::~WebAutoLogin(void)
{
	if (NULL != m_iWebBrowser)
	{
		m_iWebBrowser->Release();
	}
}

READYSTATE WebAutoLogin::ReadyState()
{
	READYSTATE r = READYSTATE_UNINITIALIZED;
	HRESULT hr = m_iWebBrowser->get_ReadyState(&r);
	//printf("get_ReadyState = %d",r);
	if (SUCCEEDED(hr) && r == READYSTATE_COMPLETE) 
	{

	}
	return r;
}

bool WebAutoLogin::AutoLogin(HWND hwnd,std::wstring userName,std::wstring password)
{
	bool isLogin = false;
	HRESULT hr = S_OK;
	IHTMLElement *user_nameElet= GetHTMLElementByIdOrName(L"username");
	if (user_nameElet!=0)
	{
		//轉換成CComBSTR      
		CComBSTR bStr =userName.c_str();     
		//輸入內容     
		hr = user_nameElet->put_innerText(bStr);
		user_nameElet->Release();
	}
	else
	{
		MessageBox(hwnd,_T("獲取:使用者名稱HTMLElement 失敗!"),_T("錯誤"),MB_OK|MB_ICONERROR );
	}

	IHTMLElement *passwdElet= GetHTMLElementByIdOrName(L"password");
	if (passwdElet!=0)
	{
		//轉換成CComBSTR      
		CComBSTR bStr = password.c_str();     
		//輸入內容     
		hr = passwdElet->put_innerText(bStr);
		passwdElet->Release();
	}
	else
	{
		MessageBox(hwnd,_T("獲取:密碼HTMLElement 失敗!"),_T("錯誤"),MB_OK|MB_ICONERROR );
	}

	IHTMLElement *loginSubElet = GetHTMLElementByTag(L"input",L"value",L"登 錄");
	if (loginSubElet!=0)
	{
		loginSubElet->click();
		loginSubElet->Release();
		isLogin = true;
	}
	else
	{
		MessageBox(hwnd,_T("獲取:登陸HTMLElement 失敗!"),_T("錯誤"),MB_OK|MB_ICONERROR );
	}
	return isLogin;
}

bool WebAutoLogin::LoginResult()
{
	bool isLogin = false;
	IDispatch *dispatch=0; 
	HRESULT hr = m_iWebBrowser->get_Document(&dispatch); 
	if ((S_OK==hr)&&(dispatch!=0))
	{
		IHTMLDocument2 *doc;  
		hr = dispatch->QueryInterface(IID_IHTMLDocument2,(void**)&doc);
		dispatch->Release(); 
		if ( S_OK == hr )
		{
			//登陸成功的 判斷方式可以用不同的 方法
			BSTR bstrCookie;
			hr = doc->get_cookie(&bstrCookie);
			if (S_OK == hr)
			{
				_bstr_t bstr_t(bstrCookie);

				std::string strCookie(bstr_t);

				::SysFreeString(bstrCookie);
			}
			BSTR bstrReferrer;
			hr = doc->get_referrer(&bstrReferrer);
			if (S_OK == hr)
			{
				if(NULL != bstrReferrer)
				{
					_bstr_t bstr_t0(bstrReferrer);

					std::string strReferrer(bstr_t0);

					::SysFreeString(bstrReferrer);
				}
			}
			BSTR bstrUrl;
			hr = doc->get_URL(&bstrUrl);
			if (S_OK == hr)
			{
				if(NULL != bstrUrl)
				{
					_bstr_t bstr_t(bstrUrl);

					std::string strUrl(bstr_t);
					if (0 == strcmp("http://www.csdn.net/",strUrl.c_str()))
					{
						isLogin = true;
					}
					// free the BSTR
					::SysFreeString(bstrUrl);
				}
			}
		}
		doc->Release();
	}
	dispatch->Release();

	return isLogin;
}

void WebAutoLogin::Navigate(std::wstring strUrl)
{
	VARIANT varMyURL; 
	VariantInit(&varMyURL);
	varMyURL.vt = VT_BSTR; 
	varMyURL.bstrVal = SysAllocString(strUrl.c_str());
	m_iWebBrowser-> Navigate2(&varMyURL,0,0,0,0);
	SysFreeString(varMyURL.bstrVal);
	VariantClear(&varMyURL); 
}

IHTMLElement * WebAutoLogin::GetHTMLElementByTag(std::wstring tagName,std::wstring PropertyName,
								   std::wstring macthValue)
{
	IHTMLElement *retElement=0;
	IDispatch *dispatch=0; 
	HRESULT hr = m_iWebBrowser->get_Document(&dispatch); 
	if ((S_OK==hr)&&(0 != dispatch))
	{
		IHTMLDocument2 *doc;  
		dispatch->QueryInterface(IID_IHTMLDocument2,(void**)&doc);
		dispatch->Release(); 
		IHTMLElementCollection* doc_all;
		hr = doc->get_all(&doc_all);      // this is like doing document.all
		if (S_OK == hr)
		{ 
			VARIANT vKey; 
			vKey.vt=VT_BSTR;
			vKey.bstrVal=SysAllocString(tagName.c_str());
			VARIANT vIndex; 
			VariantInit(&vIndex);
			hr = doc_all->tags(vKey,&dispatch);       // this is like doing document.all["messages"]
			//清理
			SysFreeString(vKey.bstrVal);
			VariantClear(&vKey); 
			VariantClear(&vIndex); 
			if ((S_OK == hr) && (0 != dispatch))
			{ 
				CComQIPtr< IHTMLElementCollection > all_tags = dispatch;
				//hr = dispatch->QueryInterface(IHTMLElementCollection,(void **)&all_tags); // it's the caller's responsibility to release 
				if (S_OK == hr)
				{
					long nTagsCount=0; //
					hr = all_tags->get_length( &nTagsCount);
					if ( FAILED( hr ) )
					{
						return retElement;
					}

					for(long i=0; i<nTagsCount; i++)
					{
						CComDispatchDriver spInputElement; //取得第 i 項
						hr = all_tags->item( CComVariant(i), CComVariant(i), &spInputElement );

						if ( FAILED( hr ) ) 
							continue;
						CComVariant vValue;
						hr = spInputElement.GetPropertyByName(PropertyName.c_str(), &vValue );
						if (VT_EMPTY != vValue.vt)
						{
							LPCTSTR lpValue = vValue.bstrVal?
								OLE2CT( vValue.bstrVal ) : NULL; 
							if(NULL == lpValue)
								continue;
							std::wstring cs = (LPCTSTR)lpValue;
							if (0 == _tcscmp(cs.c_str(),macthValue.c_str()))
							{
								hr = spInputElement->QueryInterface(IID_IHTMLElement,(void **)&retElement);
								if (S_OK == hr)
								{
								}
								else
								{
									retElement = 0;
								}
								break;
							}
						}
						//
						//CComVariant vName,vVal,vType; //名,值,型別
						//hr = spInputElement.GetPropertyByName( L"name", &vName );
						//if( FAILED( hr ) ) continue;
						//hr = spInputElement.GetPropertyByName( L"value", &vVal );
						//if( FAILED( hr ) ) continue;
						//hr = spInputElement.GetPropertyByName( L"type", &vType );
						//if( FAILED( hr ) ) continue;
						//LPCTSTR lpName = vName.bstrVal?
						//	OLE2CT( vName.bstrVal ) : _T("NULL"); //未知域名
						//LPCTSTR lpVal  = vVal.bstrVal?
						//	OLE2CT( vVal.bstrVal  ) : _T("NULL"); //空值,未輸入
						//LPCTSTR lpType = vType.bstrVal?
						//	OLE2CT( vType.bstrVal ) : _T("NULL"); //未知型別
					}
				}
				else
				{
					retElement = 0;
				}
				dispatch->Release();
			}
			doc_all->Release();
		}
		doc->Release();
	}
	return retElement;
}

IHTMLElement * WebAutoLogin::GetHTMLElementByIdOrName(std::wstring idorName)
{
	IHTMLElement *retElement=0;
	IDispatch *dispatch=0; 
	HRESULT hr = m_iWebBrowser->get_Document(&dispatch); 
	if ((S_OK==hr)&&(0!=dispatch))
	{
		IHTMLDocument2 *doc;  
		dispatch->QueryInterface(IID_IHTMLDocument2,(void**)&doc);
		dispatch->Release(); 
		IHTMLElementCollection* doc_all;
		hr = doc->get_all(&doc_all);      // this is like doing document.all
		if (S_OK == hr)
		{ 
			VARIANT vKey; 
			vKey.vt=VT_BSTR;
			vKey.bstrVal=SysAllocString(idorName.c_str());
			VARIANT vIndex; 
			VariantInit(&vIndex);
			hr = doc_all->item(vKey,vIndex,&dispatch);       // this is like doing document.all["messages"]
			//清理
			SysFreeString(vKey.bstrVal);
			VariantClear(&vKey); 
			VariantClear(&vIndex); 
			if ((S_OK == hr) && (0 != dispatch))
			{ 
				hr = dispatch->QueryInterface(IID_IHTMLElement,(void **)&retElement); // it's the caller's responsibility to release 
				if (S_OK == hr)
				{
				}
				else
				{
					retElement = 0;
				}
				dispatch->Release();
			}
			doc_all->Release();
		}
		doc->Release();
	}
	return retElement;
}

到這裡 一個模擬登陸csdn的就已經可以了,但是還是有幾個問題:1.頁面載入完成的判斷是用定時器查詢的這種處理不太好。2.這個需要把使用者名稱和賬號輸入到win32的介面,而不是web頁面(當然這也是為了自動登陸)。3.c++執行js或者是js能否執行c++函式,這個demo裡面沒有實現

4.IWebBrowser2的事件獲取以及c++與js的互動程式

       原理說明:通過繼承IDispatch(或者是HTMLElementEvents2,DWebBrowserEvents2)實現對應的介面,來獲取IWebBrowser2的事件以及HTMLElement的事件(這個的具體原理得到msdn上面去找了,這裡就不細說了)。通過繼承IDocHostUIHandler(這個介面內容很豐富,不止js呼叫c++這點功能)來實現js呼叫c++的函式(具體原理這裡也不展開說了)

下面貼一下主程式碼,win32程式就不貼了和上一個demo差不多。

class WebBrowserSink: public DWebBrowserEvents2 這個類主要是監聽web的載入是否完成事件,以及下載進度事件,狀態等資訊等事件。

STDMETHODIMP WebBrowserSink::Invoke(DISPID dispIdMember,REFIID riid,LCID lcid,WORD wFlags,DISPPARAMS *pDispParams,VARIANT *pVarResult,EXCEPINFO *pExcepInfo,UINT *puArgErr)
{
	UNREFERENCED_PARAMETER(lcid);
	UNREFERENCED_PARAMETER(wFlags);
	UNREFERENCED_PARAMETER(pVarResult);
	UNREFERENCED_PARAMETER(pExcepInfo);
	UNREFERENCED_PARAMETER(puArgErr);

	if(!IsEqualIID(riid,IID_NULL)) 
		return DISP_E_UNKNOWNINTERFACE; // riid should always be IID_NULL

	//LogTrace(L"WebBrowserSink::Invoke dispIdMember = %d",dispIdMember);
	switch (dispIdMember)
	{
		case DISPID_BEFORENAVIGATE2: 
			if (NULL != m_pEventCallBack)
			{
				m_pEventCallBack->OnBeforeNavigate2(
					(IDispatch*)pDispParams->rgvarg[6].byref,
					(VARIANT*)pDispParams->rgvarg[5].pvarVal,
					(VARIANT*)pDispParams->rgvarg[4].pvarVal,
					(VARIANT*)pDispParams->rgvarg[3].pvarVal,
					(VARIANT*)pDispParams->rgvarg[2].pvarVal,
					(VARIANT*)pDispParams->rgvarg[1].pvarVal,
					(VARIANT_BOOL*)pDispParams->rgvarg[0].pboolVal
					);
			}

			break;
		case DISPID_DOCUMENTCOMPLETE:
			if (NULL != m_pEventCallBack)
			{
				m_pEventCallBack->OnDocumentComplete(
					(IDispatch*)pDispParams->rgvarg[0].byref,
					pDispParams->rgvarg[0].pvarVal->bstrVal
					);
			}
			break; 
		case DISPID_PROGRESSCHANGE:
			if (NULL != m_pEventCallBack)
			{

			}
			break;
		case DISPID_STATUSTEXTCHANGE:
			if (NULL != m_pEventCallBack)
			{
				m_pEventCallBack->OnStatusTextChange(
					(IDispatch*)pDispParams->rgvarg[0].byref,
					pDispParams->rgvarg[0].bstrVal
					);
 			}
			break;
		default:
			break;
	}

	return S_OK;
}

其他地方呼叫的方式為:

void WebMonitor::RegisterIeEventDealer()
{
	HRESULT hr;
	// 宣告一個IConnectionPointContainer和IConnectionPoint例項。  
	CComPtr<IConnectionPointContainer> spConnectionPointContainer;
	CComPtr<IConnectionPoint> spConnectionPointBrowserEvents;
	// pWebBrowser2->QueryInterface(IID_IConnectionPointContainer,(void**)&spConnectionPointContainer);  
	// 利用 IWebBrowser2 介面的 QueryInterface 方法獲得 IConnectionPointContainer 介面  
	m_piWebBrowser->QueryInterface(IID_IConnectionPointContainer,(void**)&spConnectionPointContainer);  
	// 利用 IConnectionPointContainer 介面的 FindConnectionPoint 獲取 IID為DIID_DWebBrowserEvents2 的連線點  
	spConnectionPointContainer->FindConnectionPoint(DIID_DWebBrowserEvents2,&spConnectionPointBrowserEvents);
	// 利用IID為DIID_DWebBrowserEvents2的連線點的Advise建立一個實現了DWebBrowserEvents2介面的接收器的例項和此連線點的連線。
	// 第一個引數就是接收器的例項,必須是一個實現了DWebBrowserEvents2介面的類的例項 
	// 在這裡我們設定成this,也就是自己實現了DWebBrowserEvents2介面,這個是通過繼承CWebEventSink實現的   
	m_dwCookie = 0;
	m_pWebBrowserSink = new WebBrowserSink(this);
	hr = spConnectionPointBrowserEvents->Advise(m_pWebBrowserSink,&m_dwCookie); 
	if (SUCCEEDED(hr))
	{
		// Successfully advised
	}
}

class HtmlElementSink: public HTMLElementEvents2這個類是對指定的 HTMLElement 物件進行事件的監聽。

STDMETHODIMP HtmlElementSink::Invoke(DISPID dispidMember,
								REFIID riid,
								LCID lcid,
								WORD wFlags,
								DISPPARAMS* pdispparams,
								VARIANT* pvarResult,
								EXCEPINFO* pexcepinfo,
								UINT* puArgErr)
{
	switch (dispidMember)
	{
	case DISPID_HTMLELEMENTEVENTS2_ONCLICK:
		if (NULL != m_pEventCallBack)
		{
			m_pEventCallBack->OnClick();
		}
		break;

	default:
		break;
	}

	return S_OK;
}

呼叫方式,需要先獲取到指定的IHTMLElement *,再進行監聽繫結。

//監聽  頁面事件 
void WebMonitor::ConnectHTMLElementEvent(IHTMLElement* pElem)
{
	if (NULL == pElem)
	{
		return;
	}
	HRESULT hr;
	IConnectionPointContainer* pCPC = NULL;
	IConnectionPoint* pCP = NULL;
	DWORD dwCookie;

	// Check that this is a connectable object.
	hr = pElem->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPC);

	if (SUCCEEDED(hr))
	{
		// 列舉 檢視一下 支援的 ConnectionPoint
		//IEnumConnectionPoints *pECPS = NULL;
		//hr = pCPC->EnumConnectionPoints(&pECPS);
		//if (SUCCEEDED(hr))
		//{
		//	IConnectionPoint* tmppCP = NULL;
		//	ULONG cFetched = 1;
		//	IID guid;
		//	while(SUCCEEDED(hr = pECPS->Next(1,&tmppCP,&cFetched))
		//		&&(cFetched>0))
		//	{	
		//		tmppCP->GetConnectionInterface(&guid);
		//	}
		//}

		// Find the connection point.
		//hr = pCPC->FindConnectionPoint(DIID_HTMLElementEvents2, &pCP);
		hr = pCPC->FindConnectionPoint(DIID_HTMLButtonElementEvents, &pCP);

		if (SUCCEEDED(hr))
		{
			// Advise the connection point.
			// pUnk is the IUnknown interface pointer for your event sink
			if(NULL == m_pHtmlElementSink)
			{
				m_pHtmlElementSink = new HtmlElementSink(this);
			}
			hr = pCP->Advise(m_pHtmlElementSink, &dwCookie);

			if (SUCCEEDED(hr))
			{
				// Successfully advised
			}
			pCP->Release();
		}
		pCPC->Release();
	}
}

接下來就是c++呼叫js函數了,這個程式碼比較簡單,給定一個js函式名稱和引數就可以利用 IHTMLDocument2 的JavaScript介面的invoke方法呼叫。

bool WebMonitor::ExecJsFun( const std::wstring& lpJsFun,const std::vector<std::wstring>& params )
{
	if ( NULL == m_piWebBrowser )
		return false;
	CComPtr<IDispatch> pDoc;
	HRESULT hr = m_piWebBrowser->get_Document(&pDoc);
	if ( FAILED(hr) )
		return false;
	CComQIPtr<IHTMLDocument2> pDoc2=pDoc;
	if ( NULL == pDoc2 )
		return false;
	CComQIPtr<IDispatch> pScript;
	hr = pDoc2->get_Script(&pScript);
	if ( FAILED(hr) )
		return false;
	DISPID id = NULL;   
	CComBSTR bstrFun(lpJsFun.c_str());
	hr = pScript->GetIDsOfNames(IID_NULL, &bstrFun, 1, LOCALE_SYSTEM_DEFAULT, &id);
	if ( FAILED(hr) )
		return false;
	DISPPARAMS dispParams;
	memset(&dispParams, 0, sizeof(DISPPARAMS));
	int nParamCount	= params.size();
	if (nParamCount > 0)
	{
		dispParams.cArgs	=nParamCount;
		dispParams.rgvarg	=new VARIANT[nParamCount];
		for (int i=0; i<nParamCount; ++i )
		{
			const std::wstring& str = params[nParamCount-1-i];
			CComBSTR bstr(str.c_str());
			bstr.CopyTo(&dispParams.rgvarg[i].bstrVal);
			dispParams.rgvarg[i].vt	= VT_BSTR;
		}
	}

	EXCEPINFO execInfo;
	memset(&execInfo, 0, sizeof(EXCEPINFO));
	VARIANT vResult;
	UINT uArgError = (UINT)-1;
	hr = pScript->Invoke(id, IID_NULL, 0, DISPATCH_METHOD, &dispParams, &vResult, &execInfo, &uArgError);
	delete[] dispParams.rgvarg;
	if ( FAILED(hr) )
		return false;
	return true;
}

js呼叫c++函式,這個需要先繼承重寫IDocHostUIHandler並且需要在js程式碼用 window.external.CppCall(10);方式去呼叫。

主要實現IDocHostUIHandler 的 virtual HRESULT STDMETHODCALLTYPE GetExternal( /* [out] */ IDispatch **ppDispatch) = 0;方法,返回一個繼承IDispatch的ClientCall物件,ClientCall裡面需要實現對c++函式的id指定,以及呼叫引數的處理等。

下面是一些主要程式碼,這是註冊 TDocHostUIHandlerImpl物件。

void WebMonitor::RegisterUIHandlerToJs()
{
	ICustomDoc   *m_spCustDoc; 
	HRESULT   hr; 
	CComPtr<IDispatch> pDoc;
	hr = m_piWebBrowser->get_Document(&pDoc);
	if ( FAILED(hr) )
		return;
	CComQIPtr<IHTMLDocument2> pDoc2=pDoc;
	if ( NULL == pDoc2 )
		return ;
	hr = pDoc2-> QueryInterface(IID_ICustomDoc,(void**)&m_spCustDoc); 
	if(SUCCEEDED(hr)) 
	{ 
		if (NULL == m_pDocHostUIHandler)
		{
			m_pDocHostUIHandler = new TDocHostUIHandlerImpl();
		}
		hr = m_spCustDoc-> SetUIHandler(m_pDocHostUIHandler); 
		if (SUCCEEDED(hr))
		{
			// Successfully advised
		} 
	}
}

ClientCall的主要程式碼片段:

class ClientCall:public IDispatch
{
	HRESULT _stdcall GetIDsOfNames(
		REFIID riid, 
		OLECHAR FAR* FAR* rgszNames, 
		unsigned int cNames, 
		LCID lcid, 
		DISPID FAR* rgDispId 
		)
	{
		if(lstrcmp(rgszNames[0], L"CppCall")==0)
		{
			//網頁呼叫window.external.CppCall時,會呼叫這個方法獲取CppCall的ID
			*rgDispId = 100;
		}
		return S_OK;
	}
	HRESULT _stdcall Invoke(
		DISPID dispIdMember,
		REFIID riid,
		LCID lcid,
		WORD wFlags,
		DISPPARAMS* pDispParams,
		VARIANT* pVarResult,
		EXCEPINFO* pExcepInfo,
		unsigned int* puArgErr
		)
	{
		if(dispIdMember == 100)
		{
			//網頁呼叫CppCall時,或根據獲取到的ID呼叫Invoke方法
			CppCall(pDispParams->rgvarg[0].intVal);
		}
		return S_OK;
	}
}

以上就是主要的程式碼段和一些說明。

5.後記

       需要完整程式碼的可以下載:http://download.csdn.net/detail/nanjun520/9674092     不需要積分,有csdn賬號就行。

目前的demo都是在主ui執行緒裡面執行,這個在網路不好的情況下會阻塞主ui,大家在使用的時候可以考慮新增執行緒來解決,也可以查詢一下是否有其他的引數設定可以解決這個問題。

IWebBrowser2的功能挺多的,本文只是挑選了幾個 個人感覺常用的功能,其他的功能大家可以去深挖一下,IWebBrowser2->Navigate2 函式也可以配置post資料和引數,不過一般比較少這樣用。

作為碼農,需要學習的東西挺多的,基於興趣來學習還是比較有動力的。畢竟程式是碼農創造的,身為碼農,大家一起努力。 文章就寫到這來,有問題歡迎大家一起討論,如果文章或者是原始碼有錯誤 歡迎指正。