CEF中JavaScript与C++交互

本文介绍如何在Chromium Embedded Framework (CEF)中实现JavaScript与Native代码的交互,包括通过CefFrame绑定函数、注册JS扩展等方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在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方法。这个方法代码如下:

?
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
<code> 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 >(args.argv);
 
   return  content::ContentMain(params);
#endif
</ const ></sandbox::sandboxinterfaceinfo*></cefapp></code>

它分析了命令行参数,提取”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()这个方法,代码如下:

?
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
<code> 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);
}
</cefcommandline></simplehandler></code>

可以看到,这里创建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。像下面这样:

?
1
2
3
4
5
6
7
8
9
10
11
12
<code> class  ClientAppRenderer :  public  CefApp,
     public  CefRenderProcessHandler
{
     ...
}
 
class  ClientAppBrowser :  public  CefApp,
     public  CefBrowserProcessHandler
{
     ...
}
</code>

当我们实现了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:

?
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
90
<code>#include <windows.h>
#include <tchar.h>
#include  "cef_js_integration.h"
#include <string>
#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<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 ;
}
</cefapp></cefcommandline></algorithm></string></tchar.h></windows.h></code>

可以看到,_tWinMain方法中解析了命令行参数,根据进程类型创建了不同的CefApp。这是它与cefsimple的区别。

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

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

?
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
<code>#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
</clientv8handler></cefv8context></cefframe></cefbrowser></cefrenderprocesshandler></code>

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

?
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
<code>#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);
}
</cefv8value></cefv8value></cefv8context></cefframe></cefbrowser></tchar.h></windows.h></code>

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

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

?
1
2
<code>alert(app.GetId());
</code>

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

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

?
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
<code>#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 ;
}
</cefframe></cefv8value></cefv8value></tchar.h></windows.h></code>

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:

?
1
2
<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:

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

?
1
2
<code>cef_js_integration.exe --url=file: ///cef_js_integration.html
</code>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值