参考连接:
https://bitbucket.org/chromiumembedded/cef/wiki/Home
demo下载地址:
http://download.youkuaiyun.com/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;
- CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction(L"testfunc", myhandler);
- object->SetValue(L"myfunc", func, V8_PROPERTY_ATTRIBUTE_NONE);
然后我们去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);
- 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);
接下来, 我们在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>