CEF的branch 都是发布的正式分支(开发自己的app时使用branch的代码) Master是平时的开发分支
官方已编译好的dll,因为涉及到版权问题,不能播放很多音视频,需要自己手动修改配置并重新编译chromium。
官方资料:https://bitbucket.org/chromiumembedded/cef/wiki/Home
建议先看完资料再看官方demo,不然代码逻辑理解起来很困难。其中重要的几个文档是:
BranchesAndBuilding 获取和编译已发布的cef版本
GeneralUsage 使用手册(内容很丰富 包括cef的多进程架构和常用的基础概念)
JavaScriptIntegration JS和C++的交互
基于CEF的程序在运行时,会启动多个进程(browser process,render process,other process)
其中后面两种进程都是由CEF底层启动,这种exe称为宿主程序,根据你browser process中的配置,宿主程序既可以和你的browser process共用一个exe,也可以另外写一个exe(具体可以参考官方提供demo或前面提到的文档)
如上,因为render process和other process是不同的进程,所以在你调试官方提供的cefclient工程时,发现很多代码你的断点都无法命中,那并不代表这段代码没有被执行,很可能是因为这段代码并不是执行在browser process中。为了调试这种代码,可以安装vs插件,修改vs配置,使其可以调试主进程启动的子进程。具体参考我另一个转载的博文:
https://blog.csdn.net/felicityWSH/article/details/80651847
======================== 各种通信机制 ==========================
JaveScript和Render-C++通信 (Window binding & Extensions):
https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md
通过Windows binding和Extensions的方法,JS调用C++函数的流程一般如下:
1. 注册function,传递render中的CefV8Handler对象
2. JS调用render中的CefV8Handler::Execute
3. 如果不需要browser执行,render直接处理后调用Execute参数中的function,给js返回执行结果,结束通信。
4. 如果需要browser执行,render保存Execute参数中的function、context等参数,然后通过SendProcessMessage发送给browser,为了收到回复的消息后能找到保存的对应的function等信息,需要在发给browser的参数中赋一些id之类的信息
5. browser通过CefClient::OnProcessMessageReceived收到render的消息,处理完后通过SendProcessMessage发送结果给render(收到的id信息需要原样返回)
6. render通过OnProcessMessageReceived收到browser传回来的结果,根据之前发送的id信息,可以找到保存的对应的function和context,执行ExecuteFunction()或ExecuteFunctionWithContext()将结果返回给js,然后删除保存的function等信息。
注意:因为此时已经不在v8 context的上下文范围中,所以需要调用以下函数:
CefV8Context::Enter()
CefV8Context::Exit()
根据官方文档:The Enter() and Exit() methods should only be used:
- When creating a V8 object, function or array outside of an existing context. For example, when creating a JS object in response to a native menu callback.
- When creating a V8 object, function or array in a context other than the current context. For example, if a call originating from frame1 needs to modify the context of frame2.
Render和 Browser的IPC通信
消息发送函数:CefBrowser::SendProcessMessage
从Browser进程发送到Render进程的消息,将会在以下函数接收:
CefRenderProcessHandler::OnProcessMessageReceived()
从Render进程发送到Browser进程的消息,将会在以下函数接收:
CefClient::OnProcessMessageReceived()
cefClient的代码示例:
Render发送消息:
// class ClientAppRenderer : public ClientApp, public CefRenderProcessHandler
// virtual void OnFocusedNodeChanged
void OnFocusedNodeChanged(CefRefPtr<ClientAppRenderer> app,
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefDOMNode> node) OVERRIDE
{
bool is_editable = (node.get() && node->IsEditable());
if (is_editable != last_node_is_editable_)
{
// Notify the browser of the change in focused element type.
last_node_is_editable_ = is_editable;
CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kFocusedNodeChangedMessage);
message->GetArgumentList()->SetBool(0, is_editable);
browser->SendProcessMessage(PID_BROWSER, message);
}
}
Browser响应消息:
// class ClientHandler : public CefClient,
bool ClientHandler::OnProcessMessageReceived(
CefRefPtr<CefBrowser> browser,
CefProcessId source_process,
CefRefPtr<CefProcessMessage> message)
{
CEF_REQUIRE_UI_THREAD();
if (message_router_->OnProcessMessageReceived(browser, source_process, message))
return true;
// Check for messages from the client renderer.
std::string message_name = message->GetName();
if (message_name == kFocusedNodeChangedMessage)
{
// A message is sent from ClientRenderDelegate to tell us whether the
// currently focused DOM node is editable. Use of |focus_on_editable_field_|
// is redundant with CefKeyEvent.focus_on_editable_field in OnPreKeyEvent
// but is useful for demonstration purposes.
focus_on_editable_field_ = message->GetArgumentList()->GetBool(0);
return true;
}
return false;
}
JaveScript和 Browser-C++通信(消息路由)
详见cef_message_router.h 中的注释
JS的请求方法:
function sendMessage()
{
window.cefQuery({ // 此处的cefQuery 函数名就是你程序中 创建消息路由时 CefMessageRouterConfig中指定的函数名称
request: 'BindingTest:' + document.getElementById("message").value,
onSuccess: function (response)
{
document.getElementById('result').value = 'Response: ' + response;
},
onFailure: function (error_code, error_message)
{
// do something
}
});
}
对应到browser的回调函数:
class CefMessageRouterBrowserSide::Handler
virtual bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64 query_id,
const CefString& request, // 请求的参数
bool persistent,
CefRefPtr<Callback> callback) // 成功或失败以及返回参数,都是通过调用callback的函数进行回传
JS的请求参数只有一个CefString,如果有多个参数,需要browser自己从中解析
如果JS的请求处理成功,browser只能回传一个CefString的参数(Callback::Success)
如果JS的请求处理失败,browser只能回传一个CefString、一个int的参数(Callback::Failure)
JS的Query会经过消息路由,到达browser,browser中会遍历之前所有注册过的handle,每个handle在Handler::OnQuery中选择忽略或处理该请求,如果选择处理,成功的话必须调用Callback::Success,失败的话必须调用Callback::Failure。如果browser中所有handle都没有处理这个请求,则js中会调用onFailure,errorcode会被自动填充为-1
消息路由的创建
CefMessageRouterConfig中有两个参数,指定js的函数调用名称,默认是cefQuery和cefQueryCancel。
注意:render和browser进程创建消息路由的参数必须一致、JS的函数调用名称也要一致。
browser:
CefMessageRouterConfig config;
message_router_ = CefMessageRouterBrowserSide::Create(config);
message_router_->AddHandler(MyHandler*, false);
// class MyHandler : public CefMessageRouterBrowserSide::Handler
// JS的请求 会调用到MyHandle实现的虚函数OnQuery中
render:
CefMessageRouterConfig config;
message_router_ = CefMessageRouterRendererSide::Create(config);
这两个对象创建后,之后的逻辑调用可参考cefClient的代码。
cefClient中测试该功能的UI操作是:cefclient>top menu>tests>Other Tests>
Render中注册js的函数