程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> CEF中JavaScript與C++交互

CEF中JavaScript與C++交互

編輯:C++入門知識

CEF中JavaScript與C++交互


在CEF裡,JS和Native(C/C++)代碼可以很方便的交互,講解得很清楚。我照著它實現了一個簡單的交互示例。

在貼代碼之前,先來看看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接口。這裡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是非常簡單的示例,用來演示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 
#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")
#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 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代碼:

<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>

Call into native by Window bindings:

<form>
	<code>UserName: <input id="userName" type="text" />&nbsp;&nbsp;Password: <input id="password" type="text" />&nbsp;&nbsp;<input onclick="Login()" type="button" value="Login" /> </code></form>

Call into native by js extensions:

通過下面的命令可以測試:

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

好啦,JS與Native交互的示例就到這裡了。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved