【CEF】《CEF 桌面软件开发实战》笔记-Chapter4-进程通信

本文介绍了JavaScript如何通过C++实现进程间的通信,包括在渲染进程中为JavaScript全局对象注册原生方法,使用V8Handler处理JavaScript方法,调试渲染进程代码,主进程接收并处理渲染进程消息,以及封装异步API。通过这一系列步骤,实现了JavaScript调用C++代码来控制窗口操作,如最大化、最小化等。
四、进程通信
  1. 为 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++ 代码了。
  1. 使用 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 方法了。
  1. 标题栏按钮的前端逻辑
  • 我们在实现标题栏按钮的处理逻辑时,为每个标题栏按钮注册了点击事件,并在点击事件的处理函数中调用了 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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值