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>