四、进程通信
- 为 JavaScript 全局对象注册方法
- 要想实现窗口标题栏内的按钮的功能,就必须在用户点击这些按钮的时候让 JavaScript 代码访问 C++ 代码,为了达到这个目的,我们用 C++ 代码为 JavaScript 的全局对象(也就是window对象)注册了一个方法,当 JavaScript 代码调用这个方法时,实际上执行的是 C++ 代码,这就是我们让 JavaScript 调用 C++ 原生方法的实现思路。
- 前面我们分离出了浏览器进程处理类、渲染进程处理类、工具进程处理类等不同进程的处理类,现在要为 JavaScript 全局对象注册方法就是在渲染进程的处理类(Renderer)中完成的。
- 首先在 Renderer 类的头文件中加入如下代码:
// 这个成员方法是public类型的 void OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) override; // 这个成员变量是private类型的 CefRefPtr<V8Handler> v8Handler; - OnContextCreated 方法是在 Renderer 类的基类CefRenderProcessHandler中定义的,在一个页面的 JavaScript 执行上下文创建完成的时候, CEF 框架会调用这个方法。如果页面还包括iframe子页面,那么子页面的 JavaScript 执行上下文创建完成时也会调用这个方法。也就是说我们在这个方法里为 JavaScript 注册的全局对象,存在于父页面中,也存在于子页中。
- v8Handler 成员变量是我们自定义的一个类型对象,它可以接收 JavaScript 代码的调用,并执行其内部定义的 C++ 方法。
- OnContextCreated 方法的实现代码如下所示:
void Renderer::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) { CefRefPtr<CefV8Value> globalObject = context->GetGlobal(); v8Handler = new V8Handler(); CefRefPtr<CefV8Value> nativeCall = CefV8Value::CreateFunction("nativeCall", v8Handler); globalObject->SetValue("nativeCall", nativeCall, V8_PROPERTY_ATTRIBUTE_READONLY); }- 通过 context 参数的 GetGlobal 方法获取 JavaScript 的全局对象,这个 JavaScript 的全局对象就是我们在页面中写 JavaScript 代码时常用的 window 对象, context 参数就是 JavaScript 的执行上下文对象。
- 通过 CefV8Value 的 CreateFunction 方法创建一个 JavaScript 的原生方法。这个原生方法的处理逻辑被封装在一个名为 V8Handler 的自定义类中,我们稍后再讲这个类的实现细节。
- 为 JavaScript 的全局对象附加一个只读的方法属性,这个属性名为 nativeCall,这个属性的值就是我们前面创建的原生方法,这个工作执行完成之后,JavaScript 代码就可以通过 window.nativeCall() 来调用我们的 C++ 代码了。
- 使用 V8 处理类处理 JavaScript 方法
- 要想让一个 C++ 类型可以处理 JavaScript 的方法逻辑,那么这个类型必须继承自 CefV8Handler 基类。V8Handler 类就是这么做的,它的头文件代码如下所示:
#pragma once #include "include/cef_v8.h" class V8Handler : public CefV8Handler { public: V8Handler() = default; virtual bool Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception) override; private: IMPLEMENT_REFCOUNTING(V8Handler); };- V8Handler 类在这个头文件中声明重写基类的 Execute 方法,也就是说当 window.nativeCall() 这句 JavaScript 代码执行时,Execute 方法内的逻辑被执行。
- 这个方法的 5 个参数的含义如下:
- name 为方法的名字,它的值就是 nativeCall 字符串。
- object 对象是 JavaScript 调用这个方法时的 this 对象。
- arguments 对象是 JavaScript 调用这个方法时传递的参数,这是个 CEF 框架定义的一个容器对象。
- retval 对象是方法执行完成后 C++ 代码返回给 JavaScript 代码的返回值,这是一个输出参数。
- exception 是方法执行失败时返回的错误信息,JavaScript 会把这个错误信息封装成一个 Error 对象抛出来。
- Execute 这个方法的实现代码如下所示:
#include "V8Handler.h" bool V8Handler::Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception) { auto msgName = arguments[0]->GetStringValue(); CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create(msgName); CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext(); context.get()->GetFrame()->SendProcessMessage(PID_BROWSER, msg); return true; };- 从参数容器中获取第一个参数,这个参数是一个字符串类型的数据,形如:window_minimize,这是我们自己定义的一种格式,window 代表着操作的类型,minimize 代表着操作的名称。我们后文还会介绍为什么要做这样的定义。
- 通过 CefProcessMessage 类型的静态方法 Create 创建一个进程间消息,这个消息的名称就是我们前面获取到的第一个字符串参数。这个消息将会被发送给浏览器进程(要时刻记得现在这段逻辑是在渲染进程中执行的)。
- 接着通过 JavaScript 的上下文对象 context 获取到当前的 Frame 对象,然后通过 Frame 对象的 SendProcessMessage 方法把前面创建的进程间消息发送给浏览器进程,由主进程执行具体的操作。
- 最后方法执行完成,返回 true,代表着我们已经成功的处理了这个 JavaScript 方法 nativeCall 。
- nativeCall 是一个异步方法,方法执行完成后,实际的任务还没执行完。
- 完成这些工作后,运行程序,打开开发者工具,在 console 面板输入window.nativeCall,按下回车,看看是否得到如下结果:
> window.nativeCall < ƒ nativeCall() { [native code] }- 如果是的话,说明你的 JavaScript 全局对象中已经具备了 nativeCall 方法,并且这个方法是原生代码实现的(native code),调试人员看不到这个方法的实现逻辑。
- 现在我们可以在页面的 JavaScript 代码中调用这个 nativeCall 方法了。
- 标题栏按钮的前端逻辑
- 我们在实现标题栏按钮的处理逻辑时,为每个标题栏按钮注册了点击事件,并在点击事件的处理函数中调用了 nativeCall 方法,如下代码所示:
let browserWindow = { getMsgName(args) { return `window_${ args.callee.name}` }, minimize() { let msgName = this.getMsgName(arguments); window.nativeCall(msgName); }, maximize() { let msgName = this.getMsgName(arguments); window.nativeCall(msgName); }, close() { let msgName = this.getMsgName(arguments); window.nativeCall(msgName); }, restore() { let msgName = this.getMsgName(arguments); window.nativeCall(msgName); }, } let minimizeBtn = document

本文介绍了JavaScript如何通过C++实现进程间的通信,包括在渲染进程中为JavaScript全局对象注册原生方法,使用V8Handler处理JavaScript方法,调试渲染进程代码,主进程接收并处理渲染进程消息,以及封装异步API。通过这一系列步骤,实现了JavaScript调用C++代码来控制窗口操作,如最大化、最小化等。
最低0.47元/天 解锁文章
1672

被折叠的 条评论
为什么被折叠?



