本篇介绍如何通过发送 IPC 消息实现 JS 回调函数的异步调用。
实现方法
渲染进程端:
1、实现 CefV8Handler 以注册 JS 回调函数
2、在 CefRenderProcessHandler::OnContextCreated() 中绑定函数到 window 对象
3、实现 CefRenderProcessHandler::OnProcessMessageReceived() 处理浏览器进程发送的消息
浏览器进程端:
1、CefClient::OnProcessMessageReceived() 以处理渲染进程端发送的消息
进程间发送消息
进程间发送消息使用 CefBrowser::SendProcessMessage() 方法,该方法有两个参数,第一个参数表示进程ID,第二个参数是要发送的消息。
参考代码
#include "include/cef_app.h"
#include "include/cef_browser.h"
#include "include/cef_client.h"
#include "include/wrapper/cef_message_router.h"
#include "include/wrapper/cef_helpers.h"
#include <Windows.h>
/**
* @brief 浏览器客户端
*/
class BrowserClient : public CefClient, public CefLifeSpanHandler
{
public:
/** @name Constructor & Destructor */
///@{
BrowserClient() {}
virtual ~BrowserClient() {}
///@}
/** @name CefClient methods */
///@{
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
/**
* @brief 消息处理
*
* 该函数处理从渲染进程发送过来的 IPC 消息
*/
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefProcessId source_process, CefRefPtr<CefProcessMessage> message) override
{
/* 根据不同的消息名称做不同的处理 */
const std::string &messageName = message->GetName();
if (messageName == "saveToDB")
{
/* 遍历所有参数 */
auto args = message->GetArgumentList();
for (int i = 0; i < args->GetSize(); ++i)
{
if (args->GetType(i) == VTYPE_STRING)
{
auto record = args->GetString(i);
if (saveToDB(record))
{
/* 发送消息给渲染进程 */
// 创建消息对象
auto msg = CefProcessMessage::Create("log");
// 获取参数列表
auto args = msg->GetArgumentList();
// 填充参数
std::ostringstream oss;
oss << "Save " << record.ToString() << " Succeeded!" << std::ends;
args->SetString(0, oss.str());
// 发送给渲染进程
browser->SendProcessMessage(PID_RENDERER, msg);
}
}
}
return true; // 返回 true 表示消息被处理
}
return false; // 返回 false 表示消息未被处理
}
///@}
/** @name CefLifeSpanHandler methods */
///@{
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
{
CEF_REQUIRE_UI_THREAD();
CefQuitMessageLoop();
}
///@}
private:
bool saveToDB(const CefString &record)
{
// do some db work here
return true;
}
private:
IMPLEMENT_REFCOUNTING(BrowserClient);
DISALLOW_COPY_AND_ASSIGN(BrowserClient);
};
/**
* @brief 浏览器进程 App
*
* 实现 CefBrowserProcessHandler 以处理与浏览器进程相关的回调
*/
class AppBrowser : public CefApp, public CefBrowserProcessHandler
{
public:
/** @name Constructor & Destructor */
///@{
AppBrowser() {}
virtual ~AppBrowser() {}
///@}
/** @name CefApp methods */
///@{
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; }
///@}
/** @name CefBrowserProcessHandler methods */
///@{
/**
* @brief 上下文初始化
*
* 通常在该函数中创建浏览器窗口
*/
virtual void OnContextInitialized() override
{
CEF_REQUIRE_UI_THREAD();
CefWindowInfo window_info;
CefRefPtr<BrowserClient> client(new BrowserClient());
window_info.SetAsPopup(NULL, "cefsimple");
CefBrowserSettings browser_settings;
CefString url = "file:///G:/projects/vs2015/SimpleBrowser/JSTest/test.html";
CefBrowserHost::CreateBrowser(window_info, client, url, browser_settings, NULL);
}
///@}
private:
IMPLEMENT_REFCOUNTING(AppBrowser);
DISALLOW_COPY_AND_ASSIGN(AppBrowser);
};
/**
* @brief 渲染进程 App
*/
class AppRenderer : public CefApp, public CefRenderProcessHandler
{
/**
* @brief 处理JS消息
*/
class MyV8Handler : public CefV8Handler
{
public:
/** @name Constructor & Destructor */
///@{
MyV8Handler() {}
virtual ~MyV8Handler() {}
///@}
/** @name CefV8Handler methods */
///@{
/**
* @brief 根据函数名称执行 JS
*/
virtual bool Execute(const CefString& name, CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval,
CefString& exception) override
{
CEF_REQUIRE_RENDERER_THREAD();
if (name == "register")
{
if (arguments.size() == 1 && arguments[0]->IsFunction())
{
// 保存回调和上下文
m_callback = arguments[0];
m_context = CefV8Context::GetCurrentContext();
return true;
}
else
{
return false;
}
}
else if (name == "saveToDB")
{
// 创建消息对象
auto msg = CefProcessMessage::Create("saveToDB");
// 构造参数
auto args = msg->GetArgumentList();
args->SetString(0, "record1");
args->SetString(1, "record2");
// 发送给浏览器进程
auto browser = CefV8Context::GetCurrentContext()->GetBrowser();
browser->SendProcessMessage(PID_BROWSER, msg);
return true;
}
return false;
}
///@}
/**
* @brief 调用 JS 注册的回调函数
*/
void log(const CefString& logStr)
{
// 进入上下文
m_context->Enter();
// 构造参数
CefV8ValueList args;
args.push_back(CefV8Value::CreateString(logStr));
// 执行回调
m_callback->ExecuteFunction(NULL, args);
// 退出上下文
m_context->Exit();
}
private:
CefRefPtr<CefV8Value> m_callback;
CefRefPtr<CefV8Context> m_context;
private:
IMPLEMENT_REFCOUNTING(MyV8Handler);
DISALLOW_COPY_AND_ASSIGN(MyV8Handler);
};
public:
/** @name Constructor & Destructor */
///@{
AppRenderer() {}
virtual ~AppRenderer() {}
///@}
/** @name CefApp methods */
///@{
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override { return this; }
///@}
/** @name CefRenderProcessHandler methods */
///@{
virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) override
{
CEF_REQUIRE_RENDERER_THREAD();
/* 在上下文创建之后,绑定函数 */
// 获取 window 对象
CefRefPtr<CefV8Value> object = context->GetGlobal();
// 创建函数
CefRefPtr<CefV8Value> func_register = CefV8Value::CreateFunction("register", m_handler);
CefRefPtr<CefV8Value> func_saveToDB = CefV8Value::CreateFunction("saveToDB", m_handler);
// 将函数绑定为 window 的属性
object->SetValue("register", func_register, V8_PROPERTY_ATTRIBUTE_NONE);
object->SetValue("saveToDB", func_saveToDB, V8_PROPERTY_ATTRIBUTE_NONE);
}
/**
* @brief 消息处理
*
* 该函数处理从浏览器进程发送过来的 IPC 消息
*/
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefProcessId source_process, CefRefPtr<CefProcessMessage> message) override
{
CEF_REQUIRE_RENDERER_THREAD();
/* 根据不同的消息名称做不同的处理 */
const std::string &messageName = message->GetName();
if (messageName == "log")
{
auto args = message->GetArgumentList();
if (args->GetType(0) == VTYPE_STRING)
{
// 调用 log 记录日志
m_handler->log(args->GetString(0));
}
return true;
}
return false;
}
///@}
private:
CefRefPtr<MyV8Handler> m_handler{ new MyV8Handler() };
private:
IMPLEMENT_REFCOUNTING(AppRenderer);
DISALLOW_COPY_AND_ASSIGN(AppRenderer);
};
/**
* @brief 除浏览器进程,渲染进程外的其他进程 App
*/
class AppOther : public CefApp
{
public:
/** @name Constructor & Destructor */
///@{
AppOther() {}
virtual ~AppOther() {}
///@}
private:
IMPLEMENT_REFCOUNTING(AppOther);
DISALLOW_COPY_AND_ASSIGN(AppOther);
};
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
// 获取进程启动参数
CefMainArgs main_args(hInstance);
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(::GetCommandLineW());
// 根据不同的参数,创建不同的 App
CefRefPtr<CefApp> app;
if (!command_line->HasSwitch("type"))
{
app = new AppBrowser();
}
else
{
const std::string &processType = command_line->GetSwitchValue("type");
if (processType == "renderer")
{
app = new AppRenderer();
}
else
{
app = new AppOther();
}
}
// 执行子进程
int exitCode = CefExecuteProcess(main_args, app.get(), NULL);
if (exitCode >= 0)
{
return exitCode;
}
// 执行浏览器进程
CefSettings settings;
settings.no_sandbox = true;
CefInitialize(main_args, settings, app.get(), NULL);
CefRunMessageLoop();
CefShutdown();
return 0;
}
JS端:
<html>
<head>
<title>JS异步通信</title>
<script language="JavaScript">
function logCallback(logStr) {
document.getElementById('result').value += logStr + '\r\n';
}
window.register(logCallback);
</script>
</head>
<body>
<form>
<input type="button" onclick="saveToDB();" value="保存">
<br><br><br>
<textarea rows="10" cols="40" id="result"></textarea>
</form>
</body>
</html>