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

来源:互联网 发布:怎么查看端口是否打开 编辑:程序博客网 时间:2024/06/06 18:36

这节我们讲解下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");

的形式调用;

原创粉丝点击