CEF完整嵌入DUI窗体(五) --JS调用C++注册的函数
2017-07-11 22:15
811 查看
这节我们讲解下JS如何调用C++的函数,我们需要给每个浏览器控件灵活的注册函数,以便JS调用实现,C++代码中如何执行JS已经在前边的章节中说明;
首先我们说下libcef_dll_wrapper 中封装的几个主要类:
CefApp: 提供了进程相关的回调管理,我们通常会继承这个类和CefRenderProcessHandler或CefBrowserProcessHandler,两两组合来形成主进程管理类和渲染进程管理类,这个类对象在每个进程中只有一个;
CefV8Handler:JS调用C++注册函数的通知类,当我们JS中调用C++的函数时会触发其Execute接口;
CefClient:浏览器功能相关的回调接口管理类,这个类管理了诸如浏览器生命周期,右键菜单,自定义文件选择等多种事件的回调,我们定制开发的时候多通过这个管理类多态不同的接口来实现;每个浏览器控件都对应一个CefClient实例,我们的向JS注册的函数保存在这里;
主进程中要完成的操作:
如何实现注册函数给JS调用? 我们可以定义函数指针和函数对象,在这里我们需要将Dui的成员函数注册到Cef中,所以应该选择函数对象:
我们如果选择单进程模式可以将函数对象定义为:
在多进程模型中CefV8Value类型只能使用在Render进程中,Render进程是渲染进程,也是JS实际运行环境,我们注册的函数JS调用的时候会通知到Render进程;这里我们主要讲解多进程的实现方式;
CefValue和CefListValue类似于Boost库的any,提供了对数据类型的泛化,这样我们可以通过这一个函数对象,来传递所有需要注册的函数(这个函数有一个任意类型的返回值和一个任意类型任意个数的参数vector);我们将函数对象同样存储在CBrowserClient类的成员变量中:
first中存储函数名称,JS调用C++注册的函数时触发的Cef接口中会携带函数名称参数,我们对比参数即可调用second的函数对象,所以CBrowserClient类还得提供一个注册函数的接口:
在最上层调用的时候通过boost的bind来将Dui类的成员函数生成函数对象传递给CBrowserClient类:
因为Render进程才是JS的运行环境,所以我们需要将函数名称通知到Render进程中,让Render进程向JS中注册函数;注册的时间应该是在在浏览器创建完成后,JS还没有开始运行前,所以我们将在上节中提到的OnAfterCreated回调中来通知Render进程;
在这里还有要注意的一点是JS调用C++函数的时,实际函数执行是在主进程中(因为我们的函数定义在主进程的Dui类中),所以需要将Render进程的出发接口阻塞以等待主进程将返回值返回。阻塞通信我们使用简单的命名管道即可,在主进程中创建管道,通知Render进程连接,这样就可以实现跨进程的函数调用,我们也在OnAfterCreated回调中创建命名管道:
SendProcessMessage 发送的跨进程消息会在Render进程CefRenderProcessHandler的OnProcessMessageReceived函数中接收到,届时我们可以在这个函数中做相应的处理;
主进程如何获知函数调用呢?我们也通过Render进程发送消息来通知主进程,我们在主进程CefClient的OnProcessMessageReceived中接收消息作出处理:
此外我们还需要将关闭浏览器的操作通知到Render进程中,以释放相关浏览器的资源(主要是保存的函数名称和V8回调实例),我们在DoClose回调接口中发消息通知Render进程:
Render进程中的操作:
上边已经提到JS的实际运行环境在Render进程中,所以我们向Cef中注册函数和JS调用函数的通知都是在Render进程中完成。向Cef注册的回调接口OnContextCreated在CefRenderProcessHandler类中,这个类在Render中只实例化过一次,我们如何将注册的函数和浏览器控件区分开呢?
另外CefV8Handler在进程中可以存在多个对象,我们不妨将OnContextCreated回调到CefV8Handler中,这样就可以将函数注册和函数执行进行统一的管理,每个浏览器通过唯一的浏览器ID(Cef内部自己维护的ID)来和CefV8Handler对应,从而在Render进程中清晰的管理浏览器控件和函数,我们首先定义一个回调接口类:
然后定义一个类来继承这个回调接口类和CefV8Handler(JS调用函数通知类),实现Render进程中所有的浏览器操作:
在OnContextCreated函数中向JS注册C++定义的函数:
在Execute函数中通过管道通知主进程函数调用,阻塞等待返回值:
下面是其他函数的实现:
在CefRenderProcessHandler派生类中实例化CV8ExtensionHandler类和回调指针:
以及相关函数的实现:
到这里,我们就可以实现在Dui类中创建函数,注册到JS中,然后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()) { //回调CV8ExtensionHandler的OnContextCreated接口 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");
的形式调用;
相关文章推荐
- CEF完整嵌入DUI窗体(四) --将浏览器的回调通知应用层
- CEF完整嵌入DUI窗体(三) --基本浏览器功能
- CEF完整嵌入DUI窗体(二) --在程序中初始化Cef
- CEF完整嵌入DUI窗体(一) --Cef3简介
- C++调用JS函数之IScriptControl类库的封装
- 【cocos2d-x从c++到js】14:注册函数 推荐
- 【cocos2d-x从c++到js】注册函数
- c++与js脚本交互,C++调用JS函数/JS调用C++函数
- c++与js脚本交互,C++调用JS函数/JS调用C++函数
- CEF JS调用C++代码
- CDHtmlDialog的基本使用(C++调用JS函数的实现)
- duilib CWebBrowser控件 C++调用js函数&&js中调用C++函数
- C++调用JS函数
- c/c++/MFC 调用 js 函数代码
- CEF:C++ 调用 JavaScript 函数 Demo(VS2013)
- webbrowser 里的js调用C#窗体的函数
- c++与js脚本交互,C++调用JS函数/JS调用C++函数
- c/c++/MFC 调用 js 函数代码(自家用)
- C++和JS的交互1 - 在C++中调用JS函数 .
- C++如何调用JS函数