CEF完整嵌入DUI窗体(五) --JS调用C++注册的函数

这节我们讲解下JS如何调用C++的函数,我们需要给每个浏览器控件灵活的注册函数,以便JS调用实现,C++代码中如何执行JS已经在前边的章节中说明;

首先我们说下libcef_dll_wrapper 中封装的几个主要类:
CefApp: 提供了进程相关的回调管理,我们通常会继承这个类和CefRenderProcessHandler或CefBrowserProcessHandler,两两组合来形成主进程管理类和渲染进程管理类,这个类对象在每个进程中只有一个;
CefV8Handler:JS调用C++注册函数的通知类,当我们JS中调用C++的函数时会触发其Execute接口;
CefClient:浏览器功能相关的回调接口管理类,这个类管理了诸如浏览器生命周期,右键菜单,自定义文件选择等多种事件的回调,我们定制开发的时候多通过这个管理类多态不同的接口来实现;每个浏览器控件都对应一个CefClient实例,我们的向JS注册的函数保存在这里;

主进程中要完成的操作:
如何实现注册函数给JS调用? 我们可以定义函数指针和函数对象,在这里我们需要将Dui的成员函数注册到Cef中,所以应该选择函数对象:

typedef boost::function<CefRefPtr<CefValue> (CefRefPtr<CefListValue>)> CustomFunction;

我们如果选择单进程模式可以将函数对象定义为:

typedef boost::function<CefRefPtr<CefV8Value> (CefV8ValueList)> CustomFunction;

在多进程模型中CefV8Value类型只能使用在Render进程中,Render进程是渲染进程,也是JS实际运行环境,我们注册的函数JS调用的时候会通知到Render进程;这里我们主要讲解多进程的实现方式;
CefValue和CefListValue类似于Boost库的any,提供了对数据类型的泛化,这样我们可以通过这一个函数对象,来传递所有需要注册的函数(这个函数有一个任意类型的返回值和一个任意类型任意个数的参数vector);我们将函数对象同样存储在CBrowserClient类的成员变量中:

std::map<CefString, CustomFunction> function_map_;

first中存储函数名称,JS调用C++注册的函数时触发的Cef接口中会携带函数名称参数,我们对比参数即可调用second的函数对象,所以CBrowserClient类还得提供一个注册函数的接口:

void CBrowserClient::SetFunction(const CefString &name, CustomFunction function) {
    function_map_[name] = function;
}

在最上层调用的时候通过boost的bind来将Dui类的成员函数生成函数对象传递给CBrowserClient类:

//定义要注册的函数
CefRefPtr<CefValue> CDuiMainWnd::TestCpp(CefRefPtr<CefListValue>) {
    CefRefPtr<CefValue> ret = CefValue::Create();
    ret->SetString(L"It is a test!");
    return ret;
}
//注册函数
browser_ = static_cast<CCefBrowserUI*>(m_PaintManager.FindControl(_T("TestBrowser")));
browser_->SetFunction(L"TestCpp", boost::bind(&CDuiMainWnd::TestCpp, this, _1));

因为Render进程才是JS的运行环境,所以我们需要将函数名称通知到Render进程中,让Render进程向JS中注册函数;注册的时间应该是在在浏览器创建完成后,JS还没有开始运行前,所以我们将在上节中提到的OnAfterCreated回调中来通知Render进程;
在这里还有要注意的一点是JS调用C++函数的时,实际函数执行是在主进程中(因为我们的函数定义在主进程的Dui类中),所以需要将Render进程的出发接口阻塞以等待主进程将返回值返回。阻塞通信我们使用简单的命名管道即可,在主进程中创建管道,通知Render进程连接,这样就可以实现跨进程的函数调用,我们也在OnAfterCreated回调中创建命名管道:

void CBrowserClient::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
        CEF_REQUIRE_UI_THREAD();
        base::AutoLock lock_scope(lock_);
        if (!browser_) {
            browser_ = browser;
            is_created_ = true;
            //给Render进程发送消息

            //创建命名管道
            wchar_t name_pipe[50] = {0};
            //获取管道名称 拼接浏览器id来确保唯一
            wsprintf(name_pipe, L"\\\\.\\pipe\\browser_pipe_%d", browser->GetIdentifier());
            //创建管道
            handle_name_pipe_ = CreateNamedPipe(name_pipe, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
                0, 1, 1024, 1024, 0, NULL);

            //发送消息 创建跨进程Cef的跨进程消息 
            CefRefPtr<CefProcessMessage> msg= CefProcessMessage::Create(L"CreateBrowser");
            if (msg->IsValid()) {
                CefRefPtr<CefListValue> msg_param = msg->GetArgumentList();
                msg_param->SetString(0, name_pipe);
                browser_->SendProcessMessage(PID_RENDERER, msg);
            }

            //客户端连接
            if (handle_name_pipe_ != INVALID_HANDLE_VALUE) {
                HANDLE hEvent;
                hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
                if (hEvent != INVALID_HANDLE_VALUE) {
                    OVERLAPPED over = {0};
                    ConnectNamedPipe(handle_name_pipe_, &over);
                }
            }

            //给Render进程发送消息 设置函数名称
            if (function_map_.size() != 0) {
                CefRefPtr<CefProcessMessage> msg_fun= CefProcessMessage::Create(L"SetFunctionName");
                if (msg_fun->IsValid()) {
                    CefRefPtr<CefListValue> args = msg_fun->GetArgumentList();
                    int index = 0;
                    for (auto iter = function_map_.begin(); iter != function_map_.end(); ++iter) {
                        args->SetString(index, iter->first);
                        ++index;
                    }
                    browser_->SendProcessMessage(PID_RENDERER, msg_fun);
                }
            }
        }

        if (life_handle_->CanUse()) {
            life_handle_->GetSoltPtr(CSmartCountTool::life_span)->OnAfterCreated(browser);
        }
    }

SendProcessMessage 发送的跨进程消息会在Render进程CefRenderProcessHandler的OnProcessMessageReceived函数中接收到,届时我们可以在这个函数中做相应的处理;
主进程如何获知函数调用呢?我们也通过Render进程发送消息来通知主进程,我们在主进程CefClient的OnProcessMessageReceived中接收消息作出处理:

bool CBrowserClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
        CefProcessId source_process,
        CefRefPtr<CefProcessMessage> message) {
            CEF_REQUIRE_UI_THREAD();
            //无用消息
            if (!message->IsValid()) {
                DWORD dwWrite;
                WriteFile(handle_name_pipe_, L"DONE", wcslen(L"DONE") * 2, &dwWrite, NULL);
                return false;
            }

            //执行函数
            CefString name = message->GetName();
            //查找函数
            auto iter = function_map_.find(name);
            if (iter != function_map_.end()) {
                CefV8ValueList list;
                //获取参数列表
                auto arguments = message->GetArgumentList();

                wchar_t buf[1024] = {0}; 
                //执行函数
                CefRefPtr<CefValue> ret = iter->second(arguments);
                if(ret != NULL) {
                    //参数类型转化
                    if (ret->GetType() == VTYPE_BOOL) {
                        if (ret->GetBool()) {
                            wsprintf(buf, L"%s%s", L"b", L"true");
                        } else {
                            wsprintf(buf, L"%s%s", L"b", L"false");
                        }

                    } else if (ret->GetType() == VTYPE_INT) {
                        wsprintf(buf, L"%s%d", L"i", ret->GetInt());

                    } else if (ret->GetType() == VTYPE_STRING) {
                        std::wstring str = ret->GetString();
                        wsprintf(buf, L"%s%s", L"s", str.c_str());

                    } else if (ret->GetType() == VTYPE_DOUBLE) {
                        wsprintf(buf, L"%s%f", L"f", ret->GetDouble());
                    }

                } else {
                    wsprintf(buf, L"%s", L"DONE");
                }
                DWORD dwWrite;
                //写管道
                WriteFile(handle_name_pipe_, buf, wcslen(buf) * 2, &dwWrite, NULL);
                return true;

            } else {
                DWORD dwWrite;
                //没有返回值
                WriteFile(handle_name_pipe_, L"DONE", wcslen(L"DONE") * 2, &dwWrite, NULL);
                return false;
            }
    }

此外我们还需要将关闭浏览器的操作通知到Render进程中,以释放相关浏览器的资源(主要是保存的函数名称和V8回调实例),我们在DoClose回调接口中发消息通知Render进程:

    bool CBrowserClient::DoClose(CefRefPtr<CefBrowser> browser) {
        CEF_REQUIRE_UI_THREAD();
        base::AutoLock lock_scope(lock_);

        if (life_handle_->CanUse()) {
            life_handle_->GetSoltPtr(CSmartCountTool::life_span)->DoClose(browser);
        }
        //清除函数map
        function_map_.clear();

        //通知render进程关闭浏览器
        CefRefPtr<CefProcessMessage> msg= CefProcessMessage::Create(L"CloseBrowser");
        browser->SendProcessMessage(PID_RENDERER, msg);

        return true;
    }

Render进程中的操作:
上边已经提到JS的实际运行环境在Render进程中,所以我们向Cef中注册函数和JS调用函数的通知都是在Render进程中完成。向Cef注册的回调接口OnContextCreated在CefRenderProcessHandler类中,这个类在Render中只实例化过一次,我们如何将注册的函数和浏览器控件区分开呢?
另外CefV8Handler在进程中可以存在多个对象,我们不妨将OnContextCreated回调到CefV8Handler中,这样就可以将函数注册和函数执行进行统一的管理,每个浏览器通过唯一的浏览器ID(Cef内部自己维护的ID)来和CefV8Handler对应,从而在Render进程中清晰的管理浏览器控件和函数,我们首先定义一个回调接口类:

    class IOnContextCreatedSolt : public CefBase {
    public:
        //V8环境创建回调
        virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
            CefRefPtr<CefFrame> frame,
            CefRefPtr<CefV8Context> context) = 0;
        //链接命名管道 将所有浏览器操作都实现在一起
        virtual void ConnectionNamePipe(const CefString& pipe_name) = 0;
        //设置函数名
        virtual void SetFunction(const CefString& name) = 0;
    private:
        IMPLEMENT_REFCOUNTING(IOnContextCreatedSolt);
    };

然后定义一个类来继承这个回调接口类和CefV8Handler(JS调用函数通知类),实现Render进程中所有的浏览器操作:

class CV8ExtensionHandler : public CefV8Handler,
                            public IOnContextCreatedSolt
    {
    public:
        CV8ExtensionHandler();
        ~CV8ExtensionHandler();

        //js回调函数
        virtual bool Execute(const CefString& name,
            CefRefPtr<CefV8Value> object,
            const CefV8ValueList& arguments,
            CefRefPtr<CefV8Value>& retval,
            CefString& exception) OVERRIDE;

        void OnContextCreated(CefRefPtr<CefBrowser> browser,
            CefRefPtr<CefFrame> frame,
            CefRefPtr<CefV8Context> context);
        //设置函数
        void SetFunction(const CefString& name);
        //链接命名管道
        void ConnectionNamePipe(const CefString& pipe_name);

    private:
        HANDLE                  handle_name_pipe_;  //命名管道句柄
        CefRefPtr<CefBrowser>   browser_;           //浏览器对象
        std::vector<CefString>  function_name_;     //函数名称列表
        IMPLEMENT_REFCOUNTING(CV8ExtensionHandler);
    };

在OnContextCreated函数中向JS注册C++定义的函数:

    void CV8ExtensionHandler::OnContextCreated(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefRefPtr<CefV8Context> context) {
            browser_ = browser;
            //Retrieve the context's window object.
            CefRefPtr<CefV8Value> object = context->GetGlobal();
            for (auto iter = function_name_.begin(); iter != function_name_.end(); ++iter) {
                //Create the "NativeLogin" function.
                CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction((*iter), this);
                //Add the "NativeLogin" function to the "window" object.
                object->SetValue((*iter), func, V8_PROPERTY_ATTRIBUTE_NONE);
            }
    }

在Execute函数中通过管道通知主进程函数调用,阻塞等待返回值:

    bool CV8ExtensionHandler::Execute(const CefString& name,
        CefRefPtr<CefV8Value> object,
        const CefV8ValueList& arguments,
        CefRefPtr<CefV8Value>& retval,
        CefString& exception) {

        CefRefPtr<CefProcessMessage> msg= CefProcessMessage::Create(name);
        CefRefPtr<CefListValue> args = msg->GetArgumentList();
        for (int i = 0; i <  arguments.size(); i++) {
            if (arguments[i]->IsBool()) {
                args->SetBool(i, arguments[i]->GetBoolValue());
            } else if (arguments[i]->IsInt()) {
                args->SetInt(i, arguments[i]->GetIntValue());

            } else if (arguments[i]->IsString()) {
                args->SetString(i, arguments[i]->GetStringValue());

            } else if (arguments[i]->IsDouble()) {
                args->SetDouble(i, arguments[i]->GetDoubleValue());
            }
        }
        browser_->SendProcessMessage(PID_BROWSER, msg);
        wchar_t buf[1024] = {0};
        DWORD  dwRead;
        ReadFile(handle_name_pipe_, buf, 1024, &dwRead, NULL);
        //没有返回值
        if (wcscmp(buf, L"DONE") == 0) {
            return true;

        //有返回值
        } else {
            wchar_t* buf_ptr = buf;
            if (*buf_ptr == L'b') {
                if (wcscmp(buf+1, L"true") == 0){
                    retval = CefV8Value::CreateBool(true);
                } else {
                    retval = CefV8Value::CreateBool(false);
                }

            } else if (*buf_ptr == L'i') {
                retval = CefV8Value::CreateInt(boost::lexical_cast<int>(buf+1));

            } else if (*buf_ptr == L's') {
                retval = CefV8Value::CreateString(boost::lexical_cast<std::wstring>(buf+1));

            } else if (*buf_ptr == L'f') {
                retval = CefV8Value::CreateDouble(boost::lexical_cast<double>(buf+1));

            }
            return true;
        }
    }

下面是其他函数的实现:

    //析构函数关闭管道连接
    CV8ExtensionHandler::~CV8ExtensionHandler() {
        if (handle_name_pipe_ != INVALID_HANDLE_VALUE) {
            DisconnectNamedPipe(handle_name_pipe_);
            CloseHandle(handle_name_pipe_);
        }
        function_name_.clear();
    }
    //添加函数名称
    void CV8ExtensionHandler::SetFunction(const CefString& name) {
        function_name_.push_back(name);
    }
    //连接管道
    void CV8ExtensionHandler::ConnectionNamePipe(const CefString& pipe_name) {
        if (WaitNamedPipe(pipe_name.ToWString().c_str(), NMPWAIT_WAIT_FOREVER)) {
            handle_name_pipe_ = CreateFile(pipe_name.ToWString().c_str(), GENERIC_READ | GENERIC_WRITE, 
                0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
        }
    }

在CefRenderProcessHandler派生类中实例化CV8ExtensionHandler类和回调指针:

    class CRenderApp : 
        public ClientApp,
        public CefRenderProcessHandler
    {
    public:
        CRenderApp();

        ~CRenderApp(void);

        virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE;
        //注册函数的地方
        virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
            CefRefPtr<CefFrame> frame,
            CefRefPtr<CefV8Context> context);

        //消息接收
        virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
            CefProcessId source_process,
            CefRefPtr<CefProcessMessage> message);

    private:
        //V8扩展实例指针
        std::map<int/*浏览器ID*/, CefRefPtr<IOnContextCreatedSolt>> browser_v8extension_map_;

        IMPLEMENT_REFCOUNTING(CRenderApp);
}

以及相关函数的实现:

    CefRefPtr<CefRenderProcessHandler> CRenderApp::GetRenderProcessHandler() {
        return this;
    }

    void CRenderApp::OnContextCreated(CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefRefPtr<CefV8Context> context) {
        int id = browser->GetIdentifier();
        auto iter = browser_v8extension_map_.find(id);
        if (iter != browser_v8extension_map_.end()) {
            //回调CV8ExtensionHandlerOnContextCreated接口
            iter->second->OnContextCreated(browser, frame, context);
        }
    }

    bool CRenderApp::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
        CefProcessId source_process,
        CefRefPtr<CefProcessMessage> message) {
        //处理消息
        //创建浏览器
        if (message->GetName() == L"CreateBrowser") {
            CefRefPtr<IOnContextCreatedSolt> context_solt = new CV8ExtensionHandler();
            context_solt->ConnectionNamePipe(message->GetArgumentList()->GetString(0));
            browser_v8extension_map_[browser->GetIdentifier()] = context_solt;

        //设置函数
        } else if (message->GetName() == L"SetFunctionName") {
            auto iter = browser_v8extension_map_.find(browser->GetIdentifier());
            if (iter != browser_v8extension_map_.end()) {
                auto argu = message->GetArgumentList();
                for (int i = 0; i < argu->GetSize(); ++i) {
                    iter->second->SetFunction(argu->GetString(i));
                }
            }

        //关闭浏览器
        } else if (message->GetName() == L"CloseBrowser") {
            auto iter = browser_v8extension_map_.find(browser->GetIdentifier());
            if (iter != browser_v8extension_map_.end()) {
                iter->second->Release();
            }
        }
        return true;
    }

到这里,我们就可以实现在Dui类中创建函数,注册到JS中,然后JS中以

window.TestCpp("It is a test");

的形式调用;

猜你喜欢

转载自blog.csdn.net/u012778714/article/details/74999549