您的位置:首页 > 编程语言 > C语言/C++

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中,所以应该选择函数对象:

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");


的形式调用;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Cef3 DuiLib