博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
CEF中JavaScript与C++交互
阅读量:6914 次
发布时间:2019-06-27

本文共 10960 字,大约阅读时间需要 36 分钟。

在CEF里,JS和Native(C/C++)代码能够非常方便的交互,这里解说得非常清楚。我照着它实现了一个简单的交互演示样例。

foruok原创。如需转载请关注foruok的微信订阅号“程序视界”联系foruok。

在贴代码之前。先来看看Browser进程和Render进程是怎么回事儿。有什么不同。

Browser与Render进程

从cefsimple開始吧,cefsimple_win.cc中的wWinMain函数中调用了CefExecuteProcess()方法来检測是否要启动其他的子进程。此处的CefExecuteProcess是在libcef_dll_wrapper.cc中的,它内部又调用了cef_execute_process方法(libcef_dll.cc),cef_execute_process又调用了libcef/browser/context.cc文件内实现的CefExecuteProcess方法。这种方法代码例如以下:

int CefExecuteProcess(const CefMainArgs& args,                      CefRefPtr
application, void* windows_sandbox_info) { base::CommandLine command_line(base::CommandLine::NO_PROGRAM);#if defined(OS_WIN) command_line.ParseFromString(::GetCommandLineW());#else command_line.InitFromArgv(args.argc, args.argv);#endif // Wait for the debugger as early in process initialization as possible. if (command_line.HasSwitch(switches::kWaitForDebugger)) base::debug::WaitForDebugger(60, true); // If no process type is specified then it represents the browser process and // we do nothing. std::string process_type = command_line.GetSwitchValueASCII(switches::kProcessType); if (process_type.empty()) return -1; CefMainDelegate main_delegate(application); // Execute the secondary process.#if defined(OS_WIN) sandbox::SandboxInterfaceInfo sandbox_info = {0}; if (windows_sandbox_info == NULL) { content::InitializeSandboxInfo(&sandbox_info); windows_sandbox_info = &sandbox_info; } content::ContentMainParams params(&main_delegate); params.instance = args.instance; params.sandbox_info = static_cast
(windows_sandbox_info); return content::ContentMain(params);#else content::ContentMainParams params(&main_delegate); params.argc = args.argc; params.argv = const_cast
(args.argv); return content::ContentMain(params);#endif

它分析了命令行參数,提取”type”參数。假设为空。说明是Browser进程,返回-1。这样一路回溯到wWinMain方法里,然后開始创建Browser进程相关的内容。

假设”type”參数不为空,做一些推断,最后调用了content::ContentMain方法,直到这种方法结束。子进程随之结束。

content::ContentMain方法再追溯下去,就到了chromium的代码里了。在chromium/src/content/app/content_main.cc文件里。

详细我们不分析了,感兴趣的能够去看看。

分析了CefExecuteProcess方法我们知道,Browser进程在cefsimple_win.cc内调用了CefExecuteProcess之后做了进一步的配置,这个是在simple_app.cc内完毕的,详细就是SimpleApp::OnContextInitialized()这种方法。代码例如以下:

void SimpleApp::OnContextInitialized() {  CEF_REQUIRE_UI_THREAD();  CefWindowInfo window_info;  window_info.SetAsPopup(NULL, "cefsimple");  // SimpleHandler implements browser-level callbacks.  CefRefPtr
handler(new SimpleHandler()); // Specify CEF browser settings here. CefBrowserSettings browser_settings; std::string url; CefRefPtr
command_line = CefCommandLine::GetGlobalCommandLine(); url = command_line->GetSwitchValue("url"); if (url.empty()) url = "http://www.google.com"; CefBrowserHost::CreateBrowser(window_info, handler.get(), url, browser_settings, NULL);}

能够看到。这里创建SimpleHandler,并传递给CefBrowserHost::CreateBrowser去使用。

如今我们清楚了,Browser进程,须要CefApp(SimpleApp实现了这个接口)和CefClient(SimpleHandler实现了这个接口)。而Renderer进程仅仅要CefApp。

另外,CEF还定义了CefBrowserProcessHandler和CefRenderProcessHandler两个接口,分别来处理Browser进程和Render进程的个性化的通知。

因此,Browser进程的App一般还须要实现CefBrowserProcessHandler接口,Renderer进程的App一般还须要实现CefRenderProcessHandler接口。这里有详细说明。

像cefsimple这个演示样例中的SimpeApp,没有实现CefRenderProcessHandler接口,没有针对Renderer进程做特别处理,所以当它作为Render进程时。会缺失一部分功能。

比方JS与Native代码交互,这正是我们想要的。

假设要实现JS与Native代码交互,最好分开实现Browser进程须要的CefApp和Render进程须要的CefApp。

像以下这样:

class ClientAppRenderer : public CefApp,    public CefRenderProcessHandler {    ...}class ClientAppBrowser : public CefApp,     public CefBrowserProcessHandler{    ...}

当我们实现了CefRenderProcessHandler接口,就能够在其OnContextCreated()方法中获取到CefFrame相应的window对象。在它上面绑定一些JS函数或对象。然后JS代码里就能够通过window对象訪问。假设是函数。就会调用到我们实现的CefV8Handler接口的Execute方法。

第二种实现JS与Native交互的方式,是在实现CefRenderProcessHandler的OnWebKitInitialized()方法时导出JS扩展,详细參考,有详细说明。

cef_js_integration项目

cef_js_integration是非常easy的演示样例。用来演示JS与Native的交互。它在一个项目内实现了ClientAppBrowser、ClientAppRenderer、ClientAppOther三种CefApp。分别相应Browser、Render及其他类别的三种进程。

JS和Native代码的交互发生在Render进程。App须要继承CefRenderProcessHandler来整合JS相关功能。因此在应用在启动时做了进程类型推断。依据不同的进程类型创建不同的CefApp。

这个演示样例演示了三种JS交互方式(參见):

- 在native代码中通过CefFrame::ExecuteJavaScript()来运行JavaScript代码
- 将函数或对象绑定到CefFrame相应的window对象上。JS代码通过window对象訪问native代码导出的函数或对象
- 使用CefRegisterExtension()注冊JS扩展,JS直接訪问注冊到JS Context中的对象

这个项目參考了cefsimple、cefclient,还有,终于它比cefsimple复杂一点,比cefclient简单非常多。

好啦,背景几乎相同,上源代码。

cef_js_integration.cpp:

#include 
#include
#include "cef_js_integration.h"#include
#include
#include "include/cef_app.h"#include "include/cef_browser.h"#include "ClientAppBrowser.h"#include "ClientAppRenderer.h"#include "ClientAppOther.h"#include "include/cef_command_line.h"#include "include/cef_sandbox_win.h"//#define CEF_USE_SANDBOX 1#if defined(CEF_USE_SANDBOX)#pragma comment(lib, "cef_sandbox.lib")#endifint APIENTRY _tWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, _In_ int nCmdShow){ UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // Enable High-DPI support on Windows 7 or newer. CefEnableHighDPISupport(); CefMainArgs main_args(hInstance); void* sandbox_info = NULL;#if defined(CEF_USE_SANDBOX) CefScopedSandboxInfo scoped_sandbox; sandbox_info = scoped_sandbox.sandbox_info();#endif // Parse command-line arguments. CefRefPtr
command_line = CefCommandLine::CreateCommandLine(); command_line->InitFromString(::GetCommandLineW()); // Create a ClientApp of the correct type. CefRefPtr
app; // The command-line flag won't be specified for the browser process. if (!command_line->HasSwitch("type")) { app = new ClientAppBrowser(); } else { const std::string& processType = command_line->GetSwitchValue("type"); if (processType == "renderer") { app = new ClientAppRenderer(); } else { app = new ClientAppOther(); } } // Execute the secondary process, if any. int exit_code = CefExecuteProcess(main_args, app, sandbox_info); if (exit_code >= 0) return exit_code; // Specify CEF global settings here. CefSettings settings;#if !defined(CEF_USE_SANDBOX) settings.no_sandbox = true;#endif // Initialize CEF. CefInitialize(main_args, settings, app.get(), sandbox_info); // Run the CEF message loop. This will block until CefQuitMessageLoop() is // called. CefRunMessageLoop(); // Shut down CEF. CefShutdown(); return 0;}

能够看到。_tWinMain方法中解析了命令行參数,依据进程类型创建了不同的CefApp。

这是它与cefsimple的差别。

ClientAppBrowser类与cefsimple演示样例中的SimpleApp基本一致,略过。

ClientAppRender类在ClientAppRender.h和ClientAppRender.cpp中实现。先是ClientAppRender.h:

#ifndef CEF3_CLIENT_APP_RENDERER_H#define CEF3_CLIENT_APP_RENDERER_H#include "include/cef_app.h"#include "include/cef_client.h"#include "V8handler.h"class ClientAppRenderer : public CefApp,    public CefRenderProcessHandler {public:    ClientAppRenderer();    CefRefPtr
GetRenderProcessHandler() OVERRIDE { return this; } void OnContextCreated( CefRefPtr
browser, CefRefPtr
frame, CefRefPtr
context); void OnWebKitInitialized() OVERRIDE;private: CefRefPtr
m_v8Handler; IMPLEMENT_REFCOUNTING(ClientAppRenderer);};#endif

ClientAppRender聚合了ClientV8Handler类的实例,回头再说。先来看ClientAppRender的OnContextCreated和OnWebKitInitialized,它们是实现JS与Native交互的关键。代码例如以下:

#include "ClientAppRenderer.h"#include "V8handler.h"#include 
#include
ClientAppRenderer::ClientAppRenderer() : m_v8Handler(new ClientV8Handler){}void ClientAppRenderer::OnContextCreated(CefRefPtr
browser, CefRefPtr
frame, CefRefPtr
context){ OutputDebugString(_T("ClientAppRenderer::OnContextCreated, create window binding\r\n")); // Retrieve the context's window object. CefRefPtr
object = context->GetGlobal(); // Create the "NativeLogin" function. CefRefPtr
func = CefV8Value::CreateFunction("NativeLogin", m_v8Handler); // Add the "NativeLogin" function to the "window" object. object->SetValue("NativeLogin", func, V8_PROPERTY_ATTRIBUTE_NONE);}void ClientAppRenderer::OnWebKitInitialized(){ OutputDebugString(_T("ClientAppRenderer::OnWebKitInitialized, create js extensions\r\n")); std::string app_code = "var app;" "if (!app)" " app = {};" "(function() {" " app.GetId = function() {" " native function GetId();" " return GetId();" " };" "})();"; CefRegisterExtension("v8/app", app_code, m_v8Handler);}

OnContextCreated给window对象绑定了一个NativeLogin函数,这个函数将由ClientV8Handler类来处理,当HTML中的JS代码调用window.NativeLogin时,ClientV8Handler的Execute方法会被调用。

OnWebKitInitialized注冊了一个名为app的JS扩展,在这个扩展里为app定义了GetId方法,app.GetId内部调用了native版本号的GetId()。HTML中的JS代码可能例如以下:

alert(app.GetId());

当浏览器运行上面的代码时,ClientV8Handler的Execute方法会被调用。

好啦。如今来看ClientV8Handler的实现(V8Handler.cpp):

#include "V8handler.h"#include 
#include
bool ClientV8Handler::Execute(const CefString& name, CefRefPtr
object, const CefV8ValueList& arguments, CefRefPtr
& retval, CefString& exception) { if (name == "NativeLogin") { if (arguments.size() == 2) { CefString strUser = arguments.at(0)->GetStringValue(); CefString strPassword = arguments.at(1)->GetStringValue(); TCHAR szLog[256] = { 0 }; _stprintf_s(szLog, 256, _T("user - %s, password - %s\r\n"), strUser.c_str(), strPassword.c_str()); OutputDebugString(szLog); //TODO: doSomething() in native way retval = CefV8Value::CreateInt(0); } else { retval = CefV8Value::CreateInt(2); } return true; } else if (name == "GetId") { if (arguments.size() == 0) { // execute javascript // just for test CefRefPtr
frame = CefV8Context::GetCurrentContext()->GetBrowser()->GetMainFrame(); frame->ExecuteJavaScript("alert('Hello, I came from native world.')", frame->GetURL(), 0); // return to JS retval = CefV8Value::CreateString("72395678"); return true; } } // Function does not exist. return false;}

Execute在处理GetId方法时,还使用CefFrame::ExecuteJavaScript演示了怎样在native代码中运行JS代码。

最后。来看一下html代码:

  
CEF JS Integration

Call into native by Window bindings:

UserName:
  Password:
  

Call into native by js extensions:

通过以下的命令能够測试:

cef_js_integration.exe --url=file:///cef_js_integration.html

好啦,JS与Native交互的演示样例就到这里了。

其他參考文章:

你可能感兴趣的文章
创业找投资,你要警惕的三种人---情商培养
查看>>
cisco 2811路由器详细配置
查看>>
烂泥:vcenter5.5无AD下的安装与配置
查看>>
iOS开发那些事--创建基于故事板的iOS 6的HelloWorld
查看>>
理解并取:frame-relay的工作原理
查看>>
MariaDB 10.3支持update多表ORDER BY and LIMIT
查看>>
2015年值得关注的几个WEB技术
查看>>
Goroutine(协程)为何能处理大并发?
查看>>
QTP中VBS脚本下FSO、WSH的应用——实例讲解
查看>>
乐视超级手机的跨界影响
查看>>
A dream and two wheels
查看>>
我花50元赚来190元的贴吧推广经验
查看>>
指针和引用
查看>>
Python应用01 原始Python服务器
查看>>
Android应用程序与SurfaceFlinger服务的关系概述和学习计划
查看>>
WCF WinCE 中 手机端 非字符串型 datetime,int,decimal,double 等等 传递不到WCF端的解决方案...
查看>>
iis6.0 之前版本发布silverlight程序 注意事项
查看>>
SQL SERVER 2008中启用相应的功能
查看>>
Implementing a small Cron service in C# - CodeProject
查看>>
poj-2155(二维树状数组)
查看>>