CEF3:与 JavaScript 整合(三)—— IPC 通信

下载工程

本篇介绍如何通过发送 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>
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值