1. 程式人生 > >duilib + cef簡單瀏覽器的demo2--c++和js互動

duilib + cef簡單瀏覽器的demo2--c++和js互動

參考連線:

https://bitbucket.org/chromiumembedded/cef/wiki/Home

demo下載地址:

http://download.csdn.net/detail/shuaixingrumo/9560822

前一篇博文講述了在duilib中怎麼使用cef3, 這篇我們來探討下怎麼在duilib中使用cef3, 讓c++和js互動。

首先需要提下, cef3中, render和browser是倆個程序, 所以c++和js互動的時候, 需要用到程序的通訊, 方法我就不多說了, 管道, 共享記憶體之類的, 下面簡單介紹下我在demo中用到的通訊方法, 大家可以根據需要選擇適合自己專案的方法:

1、render往介面傳送訊息, 我用的是比較簡單的方法, 向介面視窗傳送WM_COPYDATA訊息, 我們可以把資料格式化為一個json串來進行資料互動,然後在duilib視窗函式的HandleCustomMessage中處理這個訊息傳過來的資料

2、從duilib往render程序傳送訊息, 用cef3中CefBrowser的SendProcessMessage方法傳送CefProcessMessage給render程序, 然後在render程序中的OnProcessMessageReceived中來處理從duilib視窗中傳送過來的訊息。

首先, 介紹一種簡單的呼叫js函式的方式:

m_handler->GetBrowser()->GetMainFrame()->ExecuteJavaScript(strJSFuncName, m_handler->GetBrowser()->GetMainFrame()->GetURL(), 0);

使用CefBrowser的GetMainFrame()獲取browser的mainframe, 再呼叫他的ExecuteJavaScript方法來呼叫頁面的js函式, 此種方法比較簡單, 就不多介紹了, 大家可以自己去實驗

想要c++和js互動, 我們需要實現CefRenderProcessHandler的OnContextCreated、OnBrowserDestroyed和OnProcessMessageReceived這三個方法, 如果要和js函式互動, 還需要實現CefV8Handler的Execute方法, 這些方法的用途如下:

OnContextCreated:在建立完render的context後呼叫, 我們可以在這裡去繫結一些js的函式, 變數

OnBrowserDestroyed: browser銷燬的時候呼叫, 我們可以用來做一些清理工作

OnProcessMessageReceived: 用來接收處理傳給render程序的訊息

Execute:在OnContextCreated中繫結的js函式執行時, 會呼叫這個函式, 我們可以在這個函式中處理這些js函式

為了demo簡單, 操作方便, 我直接在SImpleApp中實現了這些方法, 具體完整的實現程式碼參看demo中的程式碼

我們先看OnContextCreated的實現:

void SimpleApp::OnContextCreated(CefRefPtr<CefBrowser> browser,
							  CefRefPtr<CefFrame> frame,
							  CefRefPtr<CefV8Context> context)
{
	CEF_REQUIRE_RENDERER_THREAD();
	m_js_callback_context = NULL;
	m_js_callback_func = NULL;
	m_hWndMain = FindWindow(L"main_wnd_class", NULL);
	CefRefPtr<CefV8Value> object = context->GetGlobal();
	CefRefPtr<CefV8Handler> myhandler = this;
	CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction(L"testfunc", myhandler);
	CefRefPtr<CefV8Value> func2 = CefV8Value::CreateFunction(L"testfunc2", myhandler);
	object->SetValue(L"myfunc", func, V8_PROPERTY_ATTRIBUTE_NONE);
	object->SetValue(L"test2", func2, V8_PROPERTY_ATTRIBUTE_NONE);
	object->SetValue(L"registercb", CefV8Value::CreateFunction(L"registercb", myhandler), V8_PROPERTY_ATTRIBUTE_NONE);
}

我們這裡面綁定了js函式, myfunc()、test2()、registercb()和做了一些必要的初始化工作,

首先, 我們我們需要獲取一個render的context視窗物件object, 用它來去繫結js物件和函式, 先介紹下如何繫結js物件, 以string型別為例:

程式碼如下:

 CefRefPtr<CefV8Value> str = CefV8Value::CreateString("test string");
  object->SetValue("strobj", str, V8_PROPERTY_ATTRIBUTE_NONE);

我們把變數strobj的值設定為test string.

關於繫結js函式, 我們首先需要獲取一個CefV8Handler物件, 這樣, 在繫結的js函式執行時, 我們才知道要去哪個Execute方法中處理,實現程式碼:

CefRefPtr<CefV8Handler> myhandler = this;
獲取一個CefV8Handler的物件,因為我們在一個類中實現了這些方法, 所以取該類的this指標就行, 如果你單獨建立了一個類繼承CefV8Handler及實現他的Execute方法, 你需要建立那個類的物件
CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction(L"testfunc", myhandler);
一個名為testfunc的c++函式物件, 我們一會用它去和js函式繫結, 在CefV8Handler的Execute中用名稱testfunc來區分哪個js函式被呼叫了
object->SetValue(L"myfunc", func, V8_PROPERTY_ATTRIBUTE_NONE);
把前面建立的func函式物件和js函式myfunc繫結

然後我們去CefV8Handler的Execute方法中處理, 當js函式myfunc執行時, c++中需要處理的一些操作

bool SimpleApp::Execute(const CefString& name,
                       CefRefPtr<CefV8Value> object,
                       const CefV8ValueList& arguments,
                       CefRefPtr<CefV8Value>& retval,
                       CefString& exception)
{
	if(name.compare(L"testfunc") ==0)
	{
		COPYDATASTRUCT cds;
		const wchar_t* lpData = name.c_str();
		cds.lpData = (LPVOID)lpData;
		cds.cbData = (wcslen(lpData) + 1)*sizeof(WCHAR);
		if(m_hWndMain && IsWindow(m_hWndMain))
			::SendMessage(m_hWndMain, WM_COPYDATA, NULL, (LPARAM)&cds);
		return true;
	}
	else if(name.compare(L"testfunc2") == 0)
	{
		if(arguments.size() == 1 && arguments[0]->IsString())
		{
			COPYDATASTRUCT cds;
			std::wstring str;
			str = L"function name: ";
			str += name;
			str += L"   arg: ";
			str += arguments[0]->GetStringValue();
			LPCWSTR lpData = str.c_str();
			cds.lpData = (LPVOID)lpData;
			cds.cbData = (wcslen(lpData) + 1)*sizeof(WCHAR);
			if(m_hWndMain && IsWindow(m_hWndMain))
				::SendMessage(m_hWndMain, WM_COPYDATA, NULL, (LPARAM)&cds);

			return true;
		}
	}
	else if(name.compare(L"registercb") == 0)
	{
		if(arguments.size() == 1 && arguments[0]->IsFunction())
		{
			m_js_callback_func = arguments[0].get();
			m_js_callback_context = CefV8Context::GetCurrentContext();
			CefV8ValueList args;
			args.push_back(CefV8Value::CreateString(L"register call back function"));
			args.push_back(CefV8Value::CreateInt(0));
			m_js_callback_func->ExecuteFunctionWithContext(m_js_callback_context, NULL, args);
		}
		return true;
	}
	return false;
}

首先, 我們根據引數name來判斷哪個js函式被呼叫了, name的值是前面我們建立函式物件的名稱, 參考前面的程式碼

如果我們要把資料傳給duilib視窗去處理, 我們需要使用WM_COPYDATA訊息去傳遞資料, 我們先用FindWindow找到我們duilib視窗, 然後把需要的資料通過EM_COPYDATA傳送出去, 具體實現參看程式碼。

下面我們要繼續介紹如何註冊一個js的回撥函式, 來讓我們在需要的時候來呼叫這個回撥函式:

首先, 我們繫結一個js函式物件:

object->SetValue(L"registercb", CefV8Value::CreateFunction(L"registercb", myhandler), V8_PROPERTY_ATTRIBUTE_NONE);
在js程式碼中註冊一個回撥函式:
function cbFunc(arg0, arg1)
{
alert("arg0:"+arg0+"  arg1:"+arg1);
}
window.registercb(cbFunc);

再Execute方法中儲存這個回撥函式物件, 等待我們需要的時候呼叫:
m_js_callback_func = arguments[0].get();
m_js_callback_context = CefV8Context::GetCurrentContext();

這裡需要記得儲存當前的context

等我們需要呼叫這個回撥函式的時候, 從duilib視窗或這browser中傳遞一個訊息到render程序中:

CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("callback_func");
CefRefPtr<CefListValue> args = msg->GetArgumentList();
args->SetString(0, "call js call back function");
args->SetInt(1, 10);
m_handler->GetBrowser()->SendProcessMessage(PID_RENDERER, msg);
從程式碼中我們可以看出, 我們建立了一個callback_func的訊息, 同時設定了一個string型別的引數和一個int型別的引數, 然後把這個訊息傳送給render程序, 這裡小提一下, 如果用這種方式, 從render程序傳送給browser程序訊息, 我們可以在render程序中建立一個CefProcessMessage的訊息, 然後把PID_RENDERER改為PID_BROWSER

接下來, 我們在render程序中的OnProcessMessageReceived處理這個訊息:

bool SimpleApp::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
                                        CefProcessId source_process,
                                        CefRefPtr<CefProcessMessage> message)
{
	if(message->GetName().compare(L"callback_func") == 0)
	{
		if(m_js_callback_func)
		{
			CefV8ValueList args;
			CefString str = message->GetArgumentList()->GetString(0);
			args.push_back(CefV8Value::CreateString(str));
			args.push_back(CefV8Value::CreateInt(message->GetArgumentList()->GetInt(1)));
			m_js_callback_func->ExecuteFunctionWithContext(m_js_callback_context, NULL, args);
		}
		return true;
	}
	return false;
}

從程式碼可以看出, 我們用訊息名來區分訊息, 然後建立一個CefV8ValueList物件, 用來儲存傳給js函式的引數, 我們把之前放在訊息中的字串型別引數和int型別引數取出, 放入引數物件中,然後執行ExecuteFunctionWithContext方法來呼叫js回撥函式。

好了, 關於cef3中c++和js互動部分就簡單介紹這些了, 小夥伴們可以根據文章開頭的連線去學習cef3更多的使用技巧, 如果有不對和需要補充的地方, 歡迎大家交流, 下面給出我寫的用來測試的html程式碼:

<!doctype html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
	<script type="text/javascript">
		function testfunc()
		{
			window.myfunc();
		}
		function testfunc2(str){
			window.test2(str);
		}
		function regfunc(){
			alert("regfunc");
		}
		function registerfunc(){
			window.registercb(regfunc);
			alert('register function regfunc successfully, click "call js" to call regfunc');
		}
	</script>
</head>
<body>
	<p>test</p>
	<input type="button" value="call myfunc, no arg" onclick="testfunc()" /><br/>
	<input type="button" value='call test2 func, arg="a string value"' onclick='testfunc2("a string value")' /><br/>
	<input type="button" value="register a js func: regfunc" onclick="registerfunc()" />
</body>
</html>