• CEF中JavaScript与C++交互


    在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
    

    它分析了命令行參数,提取”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);
    }
    

    能够看到。这里创建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
    {
        ...
    }
    

    当我们实现了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是非常easy的演示样例。用来演示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")
    #endif
    
    
    int 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;
    }
    

    能够看到。_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 
    

    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
    "));
    
        // 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
    "));
        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 <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
    "), 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;
    }
    

    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>
    

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

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

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

    其他參考文章:

  • 相关阅读:
    VS中的路径宏
    Eigen3
    Python3.6 import源文件与编译文件的关系
    使用C语言扩展Python3
    mysql.connector 事务总结
    C++ -- STL泛型编程(一)之vector
    JSP -- include指令与include动作的区别
    Rails -- 关于Migration
    ruby -- 进阶学习(八)自定义方法route配置
    ruby -- 进阶学习(七)strong parameters之permitted.has_key
  • 原文地址:https://www.cnblogs.com/zsychanpin/p/7252242.html
Copyright © 2020-2023  润新知