菜鸟与 cef 的邂逅之旅(三):Cef3 中 C++ 与 JavaScript 的互相调用
2017-08-02 20:48
459 查看
一、引言
我们要实现一个强大的浏览器控件,必须要能够实现 C++ 与 JavsScript 的互相调用。于是,在我研究了:
菜鸟与 cef 的邂逅之旅(一):cef 源码获取与编译
菜鸟与 cef 的邂逅之旅(二):Soui 中接入 Cef3 的实现
这些主题之后,研究 Cef3 中 C++ 与 JavaScript 之间的互相调用也是顺水推舟的事情了。
这里,我依然参照了大神 蓝先生 的示例代码(根据此示例代码在上一篇博客中自行建立的项目),认真研究了下如何在基于 Soui 界面库的 Cef3 机制中实现 C++ 和 JavaScript 互相调用的方法。
二、Cef3: C++ 调用 JavaScript
C++ 调用 JavaScript 可以说是比较简单的。看一下 Cef3 官方文档:CefBrowser 和 CefFrame 对象被用来发送命令给浏览器以及在回调函数里获取状态信息。每个 CefBrowser 对象包含一个主 CefFrame 对象,主 CefFrame 对象代表页面的顶层 frame;同时每个 CefBrowser 对象可以包含零个或多个的 CefFrame 对象,分别代表不同的子 Frame。
CefBrowser 和 CefFrame 对象在 Browser 进程和 Render 进程都有对等的代理对象。
根据文档的了解,我们发现,我们可以通过 CBrowser 的实例得到其顶层 CefFrame 对象,然后通过它来调用 JavaScript 代码。
以下是大神 蓝先生封装的 SCefWebView 控件中的 ExecJavaScript 方法:
void SCefWebView::ExecJavaScript(const SStringW& js) { if (!m_pBrowserHandler) { return; } CefRefPtr<CefBrowser> pb = m_pBrowserHandler->GetBrowser(); if ( pb.get() ) { CefRefPtr<CefFrame> frame = pb->GetMainFrame(); if ( frame ) { frame->ExecuteJavaScript((LPCWSTR)js, L"", 0); } } }
我们了解了 C++ 调用 JavaScript 方法的原理之后,只需要在指定的事件里找到这个 Cef 控件,通过控件调用 ExecJavaScript 方法即可:
// 执行 JavaScript 代码 bool CMainDlg::OnRunJavaScript(SOUI::EventArgs *pEvt) { SOUI::SEdit *pEditURL = FindChildByName2<SOUI::SEdit>(L"edit_input_url"); SOUI::SCefWebView *pCefBrowser = FindChildByName2<SOUI::SCefWebView>(L"cef_browser"); if (pEditURL && pCefBrowser) { SStringW strJs = pEditURL->GetWindowText(); pCefBrowser->ExecJavaScript(strJs); } return true; }
这里,用户通过在地址栏输入 JavaScript 代码之后,点击
Run Js按钮之后执行该处代码(具体绑定过程看 SOUI 教程)。
以下是运行结果:
三、Cef3: JavaScript 调用 C++
上面我们已经实现了 C++ 调用 JavaScript 的功能。而如何实现 JavaScript 对于 C++ 的调用呢?
通过认真参看代码逻辑,我了解到了这样的实现:
1. 定义 SOUI::SCefWebView 的窗口通知事件
以下是文件 ExtendEvents.h 的内容:
namespace SOUI { #define EVT_CEFWEBVIEW_BEGIN (EVT_EXTERNAL_BEGIN + 900) #define EVT_WEBVIEW_NOTIFY (EVT_CEFWEBVIEW_BEGIN+0) class EventWebViewNotify : public TplEventArgs<EventWebViewNotify> { SOUI_CLASS_NAME(EventWebViewNotify, L"on_webview_notify") public: EventWebViewNotify(SObject *pSender) :TplEventArgs<EventWebViewNotify>(pSender) {} enum { EventID = EVT_WEBVIEW_NOTIFY }; SStringW MessageName; SArray<SStringW> Arguments; }; };// namespace SOUI
这里定义了通知消息类型 EventWebViewNotify,其中定义了通知事件 Id 为 EVT_WEBVIEW_NOTIFY,并且声明了两个成员变量,一个用于存储响应 JavaScript 函数名称的 MessageName,另一个则是存储 JavaScript 函数调用的参数的数组变量。
2. 子进程 WebProcess 中初始化声明全局函数(JavaScript 调用),JavaScript 可以调用该函数达到调用 C++ 函数的目的
以下是 SubProcessClientApp.cpp 中对于 JavaScript 可调用全局函数的定义:
void SubProcessClientApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) { CefRefPtr<CefV8Value> object = context->GetGlobal(); CefRefPtr<CefV8Handler> handler = new HtmlEventHandler(); CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction("HandleEvent", handler); object->SetValue("HandleEvent", func, V8_PROPERTY_ATTRIBUTE_NONE); }
可以看到,我们在其中将这个 JavaScript 可以全局调用的函数命名为了 HandleEvent,那么之后我们在 JavaScript 代码中就要使用这个函数进行调用 C++ 的目的。
以下是 HTMLEventHandler.cpp 中对于 JavaScript 执行函数的定义:
bool HtmlEventHandler::Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception) { if (name != "HandleEvent" || arguments.size() == 0) { return true; } CefRefPtr<CefBrowser> browser = CefV8Context::GetCurrentContext()->GetBrowser(); CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(arguments[0]->GetStringValue()); message->GetArgumentList()->SetSize(arguments.size() - 1); for (size_t i = 1; i < arguments.size(); ++i) { message->GetArgumentList()->SetString(i - 1, arguments[i]->GetStringValue()); } browser->SendProcessMessage(PID_BROWSER, message); return false; }
可以看到,同样的,我们仔细甄选了 HandleEvent 的函数调用信息,然后将其发送给了我们的浏览器实例 Browser 进程,之后我们在 Browser 进程中的 BrowserHandler::OnProcessMessageReceived() 函数中可以接收到该调用信息,该函数又将信息发送给了子类实现 SCefWebView::OnBrowserMessage() 函数中:
bool SCefWebView::OnBrowserMessage(CefRefPtr<CefBrowser> browser, CefProcessId source_process, CefRefPtr<CefProcessMessage> message) { EventWebViewNotify evt(this); evt.MessageName = message->GetName().ToWString().c_str(); CefRefPtr<CefListValue> arg = message->GetArgumentList(); for (int i = 0; i < arg->GetSize(); ++i) { SStringW str = arg->GetString(i).ToWString().c_str(); evt.Arguments.Add(str); } return !!FireEvent(evt); }
可以看到,我们将收到的 JavaScript 调用信息转化成了我们之前定义的 EventWebViewNotify 通知类型,这里经过一系列的参数处理,然后发送响应到了 UI 层。
3. 使用我们之前定义的全局函数 HandleEvent 实现 JavaScript 对于 C++ 的调用:
以下是 test.htm 的实现:
<html> <head> <meta charset="utf-8" /> </head> <script> function callCpp() { window.HandleEvent("ClickButton", document.getElementById("input_text").value); } </script> <body> <input type="text" id="input_text" stype="width:200px;height:50" value="hello cef3" /> <button type="button" onclick="callCpp()">Call C++</button> </body> </html>
可以看到,我们点击 Call C++ 的 button,响应了 callCpp() 的 JavaScript 函数,在这个 JavaScript 函数中,我们调用了全局函数 HandleEvent,并往里面传递了函数名 ClickButton,还传递了页面元素 input_text 的值。
3. 绑定 cef 浏览器控件响应自定义的 EVT_WEBVIEW_NOTIFY 消息,将接收到的 JavaScript 函数传递的函数名和参数名显示出来:
以下是 SOUI 中绑定浏览器消息的实现:
SOUI::SCefWebView *pCefBrowser = FindChildByName2<SOUI::SCefWebView>(L"cef_browser"); if (pCefBrowser) { pCefBrowser->GetEventSet()->subscribeEvent(EVT_WEBVIEW_NOTIFY, Subscriber(&CMainDlg::OnWebViewNotify, this)); }
以下是界面层中显示 JavaScript 调用信息的实现:
// 获得 Js 的通知信息,显示结果 bool CMainDlg::OnWebViewNotify(SOUI::EventArgs *pEvt) { SOUI::EventWebViewNotify *pev = (SOUI::EventWebViewNotify*)pEvt; SStringW args; for (size_t i = 0; i < pev->Arguments.GetCount(); ++i) { if (i != 0) { args += _T(", "); } args += pev->Arguments[i]; } SOUI::SMessageBox(m_hWnd, args, pev->MessageName, MB_OK); return true; }
最后,让我们看看最后的运行结果吧:
^_^
完结!撒花!
四、总结
基于 SOUI 的 Cef3 中 C++ 与 JavaScript 的互相调用其实并不复杂,其根本原理还是 Browser 进程与 Render 进程的互相通信。其中 Browser 进程向 Render 进程发送消息,Render 进程响应,这就是 C++ 调用 JavaScript;
同样的,Render 进程向 Browser 进程发送消息,Browser 进程响应,这就是 JavaScript 调用 C++;
当然内部有着复杂的实现,不过原理就是这样。
最后附上本博客的实验代码:
wangying2016/Cef3-Soui-Demo
Cef3 之路还很漫长,想要灵活运用还有很长的距离要走:
最后再次感谢大神蓝先生对于我的帮助
To be Stronger!
相关文章推荐
- CEF:C++ 调用 JavaScript 函数 Demo(VS2013)
- CEF:JavaScript 调用 C++ 函数 Demo(VS2013)
- C与C++互相调用的方法
- C#托管代码与C++非托管代码互相调用
- Objective C 和 Javascript 之间的互相调用
- CEF中JavaScript与C++交互
- C语言和C++区别——C和C++之间互相调用
- JsBridge实现Javascript和Java的互相调用
- C/C++文件中函数互相调用
- javascript与asp.net(c#)互相调用方法
- Android和JavaScript互相调用
- 【CEF】 VC应用程序让JS代码能够调用C++方法
- Boost.Python C++与Python的互相调用之Python调用c/c++函数
- android中Webview与javascript的交互(互相调用)
- 使用SWIG实现C/C++与其他语言间的互相调用 zz
- Android WebView中javascript和java的互相调用
- Android中实现WebView和JavaScript的互相调用详解
- c#编程指南(九) 平台调用P-INVOKE完全掌握,C#和C++互相调用
- 如何在C++中增加给JavaScript调用的API 推荐
- javascript与java值互相调用