CEF中JavaScript与C 交互

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

                       

在CEF里,JS和Native(C/C++)代码可以很方便的交互,这里https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md讲解得很清楚。我照着它实现了一个简单的交互示例。

 

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<CefApp> 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<sandbox::SandboxInterfaceInfo*>(windows_sandbox_info);  return content::ContentMain(params);#else  content::ContentMainParams params(&main_delegate);  params.argc = args.argc;  params.argv = const_cast<const char**>(args.argv);  return content::ContentMain(params);#endif
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

它分析了命令行参数,提取”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<SimpleHandler> handler(new SimpleHandler());  // Specify CEF browser settings here.  CefBrowserSettings browser_settings;  std::string url;  CefRefPtr<CefCommandLine> 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);}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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

现在我们清楚了,Browser进程,需要CefApp(SimpleApp实现了这个接口)和CefClient(SimpleHandler实现了这个接口)。而Renderer进程只要CefApp。

另外,CEF还定义了CefBrowserProcessHandler和CefRenderProcessHandler两个接口,分别来处理Browser进程和Render进程的个性化的通知。因此,Browser进程的App一般还需要实现CefBrowserProcessHandler接口,Renderer进程的App一般还需要实现CefRenderProcessHandler接口。这里https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage有详细说明。

像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{    ...}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

另外一种实现JS与Native交互的方式,是在实现CefRenderProcessHandler的OnWebKitInitialized()方法时导出JS扩展,具体参考https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md,有详细说明。

cef_js_integration项目

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

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

这个示例演示了三种JS交互方式(参见https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md):
- 在native代码中通过CefFrame::ExecuteJavaScript()来执行JavaScript代码
- 将函数或对象绑定到CefFrame对应的window对象上,JS代码通过window对象访问native代码导出的函数或对象
- 使用CefRegisterExtension()注册JS扩展,JS直接访问注册到JS Context中的对象

这个项目参考了cefsimple、cefclient,还有https://github.com/acristoffers/CEF3SimpleSample,最终它比cefsimple复杂一点,比cefclient简单很多。

好啦,背景差不多,上源码。

cef_js_integration.cpp:

#include <windows.h>#include <tchar.h>#include "cef_js_integration.h"#include <string>#include <algorithm>#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<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();    command_line->InitFromString(::GetCommandLineW());    // Create a ClientApp of the correct type.    CefRefPtr<CefApp> 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;}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89

可以看到,_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<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE    {        return this;    }    void OnContextCreated(        CefRefPtr<CefBrowser> browser,        CefRefPtr<CefFrame> frame,        CefRefPtr<CefV8Context> context);    void OnWebKitInitialized() OVERRIDE;private:    CefRefPtr<ClientV8Handler> m_v8Handler;    IMPLEMENT_REFCOUNTING(ClientAppRenderer);};#endif 
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

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

#include "ClientAppRenderer.h"#include "V8handler.h"#include <Windows.h>#include <tchar.h>ClientAppRenderer::ClientAppRenderer()    : m_v8Handler(new ClientV8Handler){}void ClientAppRenderer::OnContextCreated(CefRefPtr<CefBrowser> browser,    CefRefPtr<CefFrame> frame,    CefRefPtr<CefV8Context> context){    OutputDebugString(_T("ClientAppRenderer::OnContextCreated, create window binding\r\n"));    // Retrieve the context's window object.    CefRefPtr<CefV8Value> object = context->GetGlobal();    // Create the "NativeLogin" function.    CefRefPtr<CefV8Value> 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);}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

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

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

alert(app.GetId());
   
   
  • 1

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

好啦,现在来看ClientV8Handler的实现(V8Handler.cpp):

#include "V8handler.h"#include <Windows.h>#include <tchar.h>bool ClientV8Handler::Execute(const CefString& name,    CefRefPtr<CefV8Value> object,    const CefV8ValueList& arguments,    CefRefPtr<CefV8Value>& 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<CefFrame> 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;}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

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

最后,来看一下html代码:

<!DOCTYPE html><html>  <!--  Copyright (c) 2016 foruok. All rights reserved.  欢迎关注foruok的微信订阅好“程序视界”。  --><head>    <script type="text/javascript">      function Login(){        window.NativeLogin(document.getElementById("userName").value, document.getElementById("password").value);      }      function GetId(){        alert("get id from native by extensions: " + app.GetId());      }    </script>  <title>CEF JS Integration</title></head><body><h3>Call into native by Window bindings:</h3><form>UserName: <input type="text" id="userName" />&nbsp;&nbsp;Password: <input type="text" id="password" />&nbsp;&nbsp;<input  type="button" value="Login" onclick="Login()"/></form><hr><h3>Call into native by js extensions:</h3><input  type="button" value="GetId" onclick="GetId()"/></html>
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

通过下面的命令可以测试:

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

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

其他参考文章:

           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

这里写图片描述

猜你喜欢

转载自blog.csdn.net/jfdfhh/article/details/83825649
今日推荐