duilib+cef+echarts (五) duilib加载cef中js与c++的交互

本文详细介绍了在CEF3框架下,如何实现C++与JavaScript之间的通信。通过V8引擎,注册和调用函数,以及处理回调,实现双向交互。文章涵盖了JS调用C++函数和C++调用JS函数的过程,涉及到的类如CefJSBridge、CefJSHandler等,以及在渲染器和浏览器进程中的消息处理。
摘要由CSDN通过智能技术生成

前面我们已经写了如何在duilib中加载cef浏览器并调用echarts显示图表,本章我们来说js与c++如何进行通信
CEF3是基于chromium内核,其所使用的JS引擎是V8。谷歌为了性能,直接省去了转换字节码这一步,它甚至采用直接将JavaScript编译成本地代码的方式。v8 API不仅提供了编译和运行JavaScript代码的功能,还提供了其他与C++交互的功能,包括函数和数据结构的注册,错误处理,安全检查等。C++应用程序可以将v8当作一个普通类库使用,只需引用v8.h即可。

v8的上下文(Context)是独立的JavaScript执行环境,通过使用上下文允许JavaScript应用程序跑在不同的v8实例上。执行一段JavaScript代码必须显示指定上下文。这是因为JavaScript提供了一系列内置辅助函数和全局对象,它们可以被JavaScript代码调用和修改。如果两段不相关的JavaScript代码同时修改全局对象,可能会导致用户不希望看到的结果。根据v8的规范,执行JavaScript函数必须在Context之内,所以调用ExecuteFunction的前我们必须进入Context,执行完毕后必须退出Context。 这个功能是由CefV8Context提供的,最重要的成员有Enter、Exist、Eval等。
  因为我们使用的都是cef v8引擎,所以我们主要用V8引擎扩展的方式 实现CEF C++ JS交互
  js与C++交互有两种模式,一种是js调用c++接口,一中是c++调用js接口

c++调用js方式实现原理是在js中注册个js函数,c++端调用此函数。
js代用c++方式实现原理是在c++中注册cpp函数,js端调用此函数

无论是js端注册个函数,还是调用c++函数,这个操作我们都要感知到,所幸v8引擎已经实现了,我们只需在渲染器进程(render)中继承OnWebKitInitialized()函数中初始化相关信息

void ClientApp::OnWebKitInitialized()
	{
		/**
		* JavaScript 扩展代码,这里定义一个 CefWebFunction 对象提供 call 方法来让 Web 端触发 native 的 CefV8Handler 处理代码
		* param[in] functionName	要调用的 C++ 方法名称
		* param[in] params			调用该方法传递的参数,在前端指定的是一个 Object,但转到 Native 的时候转为了字符串
		* param[in] callback		执行该方法后的回调函数
		* 前端调用示例
		* CefWebHelper.call('showMessage', { message: 'Hello C++' }, (arguments) => {
		*    console.log(arguments)
		* })
		*
		* CefWebInstance.register('showMessage', (arguments) => {
		*	console.log(arguments)
		*	return {
		*		message: 'showMessage success'
		*	}			
		* })
		*/
		std::string extensionCode = R"(
			var CefWebInstance = {};
			(() => {
				CefWebInstance.call = (functionName, arg1, arg2) => {
					if (typeof arg1 === 'function') {
						native function call(functionName, arg1);
						return call(functionName, arg1);
					} else {
						const jsonString = JSON.stringify(arg1);
						native function call(functionName, jsonString, arg2);
						return call(functionName, jsonString, arg2);
					}
				};
				CefWebInstance.register = (functionName, callback) => {
					native function register(functionName, callback);
					return register(functionName, callback);
				};
			})();
		)";
		CefRefPtr<CefJSHandler> handler = new CefJSHandler();

		if (!m_render_js_bridge.get())
			m_render_js_bridge.reset(new CefJSBridge);
		handler->AttachJSBridge(m_render_js_bridge);
		CefRegisterExtension("v8/extern", extensionCode, handler);
	}

我们注册了两个函数
1:register:前端注册js函数格式
2:call:前端调用C++函数格式
前端调用示例:

       (() => {
			/*
			* 注册一个js函数,用于在 C++ 应用中调用
			* param[in] functionName 函数的名称,C++ 会使用该名称来调用此函数
			* param[in] callback 回调函数执行体
			*/
			CefWebInstance.register('cppCallJsFunctionRefreshPancel', (arguments) => {
				console.log(arguments)
				
				return {
					message: 'cppCallJsFunctionRefreshPancel success'
				}
				
			})
			
		})()
		
		const jsCallCppFunctionPancel = () => {
			// 调用一个 C++ 注册过的方法
			CefWebInstance.call('jsCallCppFunctionRefreshPancel', { message:'jsCallCppFunctionRefreshPancel' }, (error, result1) => {
				console.log(error)
				console.log(result1)
			    if(false==error)
				{
				}
			})
		}

之后我们要继承JSHandler处理器类,这样 在V8引擎扩展中注册后, 网页在执行JS时,遇到注册的call 函数 或register函数时,就会执行JSHandle的execute方法
js_handler.h


class CefJSHandler : public CefV8Handler
	{
	public:
		CefJSHandler() {}
		virtual bool Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception) OVERRIDE;
		void AttachJSBridge(std::shared_ptr<CefJSBridge> js_bridge) { m_js_bridge = js_bridge; }

		IMPLEMENT_REFCOUNTING(CefJSHandler);

	private:
		std::shared_ptr<CefJSBridge> m_js_bridge;
	};

js_handler.cpp

bool CefJSHandler::Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception)
	{
		// 当Web中调用了"CefWebFunction"函数后,会触发到这里,然后把参数保存,转发到Broswer进程
		// Broswer进程的BrowserHandler类在OnProcessMessageReceived接口中处理kJsCallbackMessage消息,就可以收到这个消息
		bool ret = false;
		do {
			if (arguments.size() < 2)
			{
				exception = "Invalid arguments.";
				break;
			}

			CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
			CefRefPtr<CefFrame> frame = context->GetFrame();
			CefRefPtr<CefBrowser> browser = context->GetBrowser();

			int64_t browser_id = browser->GetIdentifier();
			int64_t frame_id = frame->GetIdentifier();

			if (!m_js_bridge.get())
			{
				break;
			}
			if (name == "call")
			{
				// 允许没有参数列表的调用,第二个参数为回调
				// 如果传递了参数列表,那么回调是第三个参数
				CefString function_name = arguments[0]->GetStringValue();
				CefString params = "{}";
				CefRefPtr<CefV8Value> callback;
				if (arguments[0]->IsString() && arguments[1]->IsFunction())
				{
					callback = arguments[1];
				}
				else if (arguments[0]->IsString() && arguments[1]->IsString() && arguments[2]->IsFunction())
				{
					params = arguments[1]->GetStringValue();
					callback = arguments[2];
				}
				else
				{
					exception = "Invalid arguments.";
					break;
				}

				// 执行 C++ 方法
				if (!m_js_bridge->CallCppFunction(function_name, params, callback))
				{
					exception = "function_name exception";
					break;
				}

			}
			else if (name == "register")
			{
				if (arguments[0]->IsString() && arguments[1]->IsFunction())
				{
					std::string function_name = arguments[0]->GetStringValue();
					CefRefPtr<CefV8Value> callback = arguments[1];
					if (!m_js_bridge->RegisterJSFunc(function_name, callback))
					{
						exception = "Failed to register function.";
						break;
					}
				}
				else
				{
					exception = "Invalid arguments.";
					break;
				}
			}
			ret = true;
		} while (false);
		return ret;
	}

我们把js与c++交互操作放到CefJSBridge类中
cef_js_bridge.h

    typedef std::function<void(bool has_error, const std::string& result)> ReportResultFunction;

	typedef std::function<void(const std::string& result)> CallJsFunctionCallback;
	typedef std::function<void(const std::string& params, ReportResultFunction callback)> CppFunction;

	typedef std::map<int/* js_callback_id*/, std::pair<CefRefPtr<CefV8Context>/* context*/, CefRefPtr<CefV8Value>/* callback*/>> RenderCallbackMap;
	typedef std::map<int/* cpp_callback_id*/, CallJsFunctionCallback/* callback*/> BrowserCallbackMap;

	typedef std::map<std::pair<CefString/* function_name*/, int64_t/* frame_id*/>, CefRefPtr<CefV8Value>/* function*/> RenderRegisteredFunction;

	typedef std::map<CefString, int64_t> BrowserRegisteredFunction;
class CefJSBridge
	{
	public:
		CefJSBridge();
		~CefJSBridge();

		// in render process
	public:
		/**
			* 执行已经注册好的 C++ 方法
			* param[in] function_name	要调用的函数名称
			* param[in] params			调用该函数传递的 json 格式参数
			* return 返回 true 表示发起执行请求成功(并不代表一定执行成功,具体看回调),返回 false 可能是注册的回调函数 ID 已经存在
			*/
		bool CallCppFunction(const CefString& function_name, const CefString& params, const CefRefPtr<CefV8Value> &callback);

		/**
			* 通过判断上下文环境移除指定回调函数(页面刷新会触发该方法)
			* param[in] frame		当前运行框架
			*/
		void RemoveCallbackFuncWithFrame(const CefRefPtr<CefFrame> &frame);

		/**
			* 根据 ID 执行指定回调函数
			* param[in] js_callback_id	回调函数的 ID
			* param[in] has_error		是否有错误,对应回调函数第一个参数
			* param[in] json_string	如果没有错误则返回指定的 json string 格式的数据,对应回调函数第二个参数
			* return 返回 true 表示成功执行了回调函数,返回 false 有可能回调函数不存在或者回调函数所需的执行上下文环境已经不存在
			*/
		bool ExecuteJSCallbackFunc(const int &js_callback_id, const bool &has_error, const CefString& json_result);

		/**
			* 注册一个持久的 JS 函数提供 C++ 调用
			* param[in] function_name	函数名称,字符串形式提供 C++ 直接调用,名称不能重复
			* param[in] context		函数的执行上下文环境
			* param[in] function		函数体
			* param[in] replace		若已经存在该名称的函数是否替换,默认否
			* return replace 为 true 的情况下,返回 true 是替换成功,返回 false 为不可预见行为。replace 为 false 的情况下返回 true 表示注册成功,返回 false 是同名函数已经注册过了。
			*/
		bool RegisterJSFunc(const CefString& function_name, const CefRefPtr<CefV8Value> &function, const bool &replace = false);

		/**
			* 反注册一个持久的 JS 函数
			* param[in] function_name	函数名称
			* param[in] frame			要取消注册哪个框架下的相关函数
			*/
		void UnRegisterJSFunc(const CefString& function_name, const CefRefPtr<CefFrame> &frame);

		/**
		* 根据执行上下文反注册一个或多个持久的 JS 函数
		* param[in] frame			当前运行所属框架
		*/
		void UnRegisterJSFuncWithFrame(const CefRefPtr<CefFrame> &frame);

		/**
			* 根据名称执行某个具体的 JS 函数
			* param[in] function_name	函数名称
			* param[in] json_params	要传递的 json 格式的参数
			* param[in] browser			执行哪个浏览器窗口下的 JS 函数
			* param[in] frame			执行哪个框架下的 JS 函数
			* return 返回 true 表示成功执行某个 JS 函数,返回 false 有可能要执行的函数不存在或者该函数的运行上下文已经无效
			*/
		bool ExecuteJSFunc(const CefString& function_name, const CefString& json_params, const CefRefPtr<CefBrowser> &browser, const CefRefPtr<CefFrame> &frame);

		// in browser process
	public:
		/**
			* 执行已经注册好的 JS 方法
			* param[in] js_function_name 要调用的 JS 函数名称
			* param[in] params			调用 JS 方法传递的 json 格式参数
			* param[in] frame			调用哪个框架下的 JS 代码
			* param[in] callback		调用 JS 方法后返回数据的回调函数
			* return 返回 ture 标识发起执行 JS 函数命令成功,返回 false 是相同的 callback id 已经存在
			*/
		bool CallJSFunction(const CefString& js_function_name, const CefString& params, const CefRefPtr<CefFrame> &frame);

		/**
			* 执行c++调用js的回调函数
			* param[in] hwnd 窗口句柄
			* param[in] function_name js函数名
			* param[in] eNCEFErrorType 
			* param[in] json_string	返回的 json 格式数据
			* return 返回 true 执行成功,false 为执行失败,可能回调不存在
			*/
		bool ExecuteCppCallbackFunc(const HWND &hwnd, const CefString& function_name, const ENCEFErrorType& eNCEFErrorType, const CefString& json_string);

		/**
			* 注册一个持久的 C++ 函数提供 JS 端调用
			* param[in] function_name	要提供 JS 调用的函数名字
			* param[in] replace		是否替换相同名称的函数体,默认不替换
			* return replace 为 true 的情况下,返回 true 表示注册或者替换成功,false 是不可预知行为。replace 为 false 的情况下返回 true 表示注册成功,返回 false 表示函数名已经注册
			*/
		bool RegisterCppFunc(const CefString& function_name, const CefRefPtr<CefBrowser> &browser, const bool &replace = false);

		/**
			* 反注册一个持久的 C++ 函数
			* param[in] function_name	要反注册的函数名称
			*/
		void UnRegisterCppFunc(const CefString& function_name, const CefRefPtr<CefBrowser> &browser);

		/**
			* 执行一个已经注册好的 C++ 方法(接受到 JS 端执行请求时被调用)
			* param[in] function_name	要执行的函数名称
			* param[in] params			携带的参数
			* param[in] js_callback_id	回调 JS 端所需的回调函数 ID
			* param[in] browser		browser 实例句柄
			* param[in] frame		
			* return 返回 true 表示执行成功,返回 false 表示执行失败,函数名可能不存在
			*/
		bool ExecuteCppFunc(const CefString& function_name, const CefString& params, const int &js_callback_id, const CefRefPtr<CefBrowser> &browser, const CefRefPtr<CefFrame> &frame);

	private:
		uint32						m_js_callback_id = 0;			// JS 端回调函数的索引计数

		RenderCallbackMap			m_render_callback;				// JS 端回调函数的对应列表

		RenderRegisteredFunction	m_render_registered_function;	// 保存 JS 端已经注册好的持久函数列表
		BrowserRegisteredFunction	m_browser_registered_function;	// 保存 C++ 端已经注册好的持久函数列表
	};

渲染器进程render中要实现OnProcessMessageReceived函数

bool ClientApp::OnProcessMessageReceived(
		CefRefPtr<CefBrowser> browser,
		CefRefPtr<CefFrame> frame,
		CefProcessId source_process,
		CefRefPtr<CefProcessMessage> message)
	{
		//ASSERT(source_process == PID_BROWSER);
		// 收到 browser 的消息回复
		bool ret = false;
		const CefString& message_name = message->GetName();
		//LOG_INFORMATION("[ClientApp][OnProcessMessageReceived]message_name = %s", message_name);
		if (message_name == kExecuteJsCallbackMessage)
		{
			int			callback_id = message->GetArgumentList()->GetInt(0);
			bool		has_error = message->GetArgumentList()->GetBool(1);
			CefString	json_string = message->GetArgumentList()->GetString(2);

			if (m_render_js_bridge.get())
			{
				// 将收到的参数通过管理器传递给调用时传递的回调函数
				ret = m_render_js_bridge->ExecuteJSCallbackFunc(callback_id, has_error, json_string);
			}
		}
		else if (message_name == kCallJsFunctionMessage)
		{
			CefString function_name = message->GetArgumentList()->GetString(0);
			CefString json_string = message->GetArgumentList()->GetString(1);
			int64 frame_id = message->GetArgumentList()->GetInt(2);

			if (m_render_js_bridge.get())
			{
				// 通过 C++ 执行一个已经注册过的 JS 方法
				ret = m_render_js_bridge->ExecuteJSFunc(function_name, json_string, browser, frame);
			}
		}

		return ret;
	}

浏览器进程browser中要实现OnProcessMessageReceived函数

bool BrowserHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefProcessId source_process, CefRefPtr<CefProcessMessage> message)
	{
		// 处理render进程发来的消息
		bool ret = false;
		std::string message_name = message->GetName();
		//LOG_INFORMATION("[BrowserHandler][OnProcessMessageReceived]message_name = %s", message_name);
		if (message_name == kFocusedNodeChangedMessage)
		{
			m_is_focus_oneditable_field = message->GetArgumentList()->GetBool(0);
			ret= true;
		}
		else if (message_name == kCallCppFunctionMessage)
		{
			CefString fun_name = message->GetArgumentList()->GetString(0);
			CefString param = message->GetArgumentList()->GetString(1);
			int js_callback_id = message->GetArgumentList()->GetInt(2);
			ret = CefManager::GetInstance()->ExecuteCppFunc(fun_name, param, js_callback_id, browser, frame);

		}
		else if (message_name == kExecuteCppCallbackMessage)
		{
			CefString fun_name = message->GetArgumentList()->GetString(0);
			ENCEFErrorType errorType = (ENCEFErrorType)(message->GetArgumentList()->GetInt(1));
			CefString param = message->GetArgumentList()->GetString(2);

			HWND handle = ::GetParent(browser->GetHost()->GetWindowHandle());
			ret = CefManager::GetInstance()->ExecuteCppCallbackFunc(handle, fun_name, errorType,param);
		}

		return ret;
	}

1,js调用c++函数

网上很多简单例子只是js调用c++,传个参数就完事了,没有返回值。实际场景js端只要用作前端展示,js端调用c++功能,不仅要能将参数传给c++,还要从c++端获取数据返回给js前端
所以我们在最开始初始化js函数时就已经带了回调函数

// 调用一个 C++ 注册过的方法
			CefWebInstance.call('jsCallCppFunctionRefreshPancel', { message:'jsCallCppFunctionRefreshPancel' }, (error, result1) => {
				console.log(error)
				console.log(result1)
			    if(false==error)
				{
					
				}
			})

要想能够正确调用,首先需要在c++端注册个c++函数

bool CefJSBridge::RegisterCppFunc(const CefString& function_name, const CefRefPtr<CefBrowser> &browser, const bool &replace /*= false*/)
	{
		bool ret = false;
		if (replace)
		{
			m_browser_registered_function.emplace(function_name, browser ? browser->GetIdentifier() : -1);// = ;
			ret= true;
		}
		else
		{
			auto it = m_browser_registered_function.find(function_name);
			if (it == m_browser_registered_function.cend())
			{
				m_browser_registered_function.emplace(function_name, browser ? browser->GetIdentifier() : -1);
				ret= true;
			}
			else {
				ret = false;
			}
		}

		return ret;
	}

1,js端调用后,会进入js_handler中

           if (name == "call")
			{
				// 允许没有参数列表的调用,第二个参数为回调
				// 如果传递了参数列表,那么回调是第三个参数
				CefString function_name = arguments[0]->GetStringValue();
				CefString params = "{}";
				CefRefPtr<CefV8Value> callback;
				if (arguments[0]->IsString() && arguments[1]->IsFunction())
				{
					callback = arguments[1];
				}
				else if (arguments[0]->IsString() && arguments[1]->IsString() && arguments[2]->IsFunction())
				{
					params = arguments[1]->GetStringValue();
					callback = arguments[2];
				}
				else
				{
					exception = "Invalid arguments.";
					break;
				}

				// 执行 C++ 方法
				if (!m_js_bridge->CallCppFunction(function_name, params, callback))
				{
					exception = "function_name exception";
					break;
				}

			}
bool CefJSBridge::CallCppFunction(const CefString& function_name, const CefString& params, const CefRefPtr<CefV8Value> &callback)
	{
		bool ret = false;
		auto it = m_render_callback.find(m_js_callback_id);
		if (it == m_render_callback.cend())
		{
			CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
			CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kCallCppFunctionMessage);

			message->GetArgumentList()->SetString(0, function_name);
			message->GetArgumentList()->SetString(1, params);
			message->GetArgumentList()->SetInt(2, m_js_callback_id);

			m_render_callback.emplace(m_js_callback_id++, std::make_pair(context, callback));

			// 发送消息到 browser 进程
			CefRefPtr<CefFrame> frame = context->GetFrame();
			frame->SendProcessMessage(PID_BROWSER, message);

			ret= true;
		}

		return ret;
	}

2,浏览器browser中会收到此消息

else if (message_name == kCallCppFunctionMessage)
		{
			CefString fun_name = message->GetArgumentList()->GetString(0);
			CefString param = message->GetArgumentList()->GetString(1);
			int js_callback_id = message->GetArgumentList()->GetInt(2);
			ret = CefManager::GetInstance()->ExecuteCppFunc(fun_name, param, js_callback_id, browser, frame);

		}
bool CefJSBridge::ExecuteCppFunc(const CefString& function_name, const CefString& params, const int &js_callback_id, const CefRefPtr<CefBrowser> &browser, const CefRefPtr<CefFrame> &frame)
	{
		bool ret = false;
		CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kExecuteJsCallbackMessage);
		CefRefPtr<CefListValue> args = message->GetArgumentList();

		HWND hwnd= CefManager::GetInstance()->GetWindowHwnd(browser);
		auto it = m_browser_registered_function.find(function_name);
		if (it != m_browser_registered_function.cend())
		{
			std::wstring json_result = L"";
			CCefOperationCallBkManger::GetInstance()->NotifyForJsCallCppBk(hwnd,function_name, params, json_result);

			args->SetInt(0, js_callback_id);
			args->SetBool(1, false);
			args->SetString(2, json_result);
			frame->SendProcessMessage(PID_RENDERER, message);
			ret = true;
		}
		else {
			std::wstring json_result = CEFError_Name[EN_ERROR_CPPFUNC_NO_EXIST];
			args->SetInt(0, js_callback_id);
			args->SetBool(1, true);
			args->SetString(2, json_result);
			frame->SendProcessMessage(PID_RENDERER, message);
			CCefOperationCallBkManger::GetInstance()->NotifyForErrorInfo(hwnd, EN_ERROR_CPPFUNC_NO_EXIST, json_result);
			ret = false;
		}
		return ret;
	}

通过上层定义的回调函数就可以从c++端拿到数据了,将数据发给render进程

if (message_name == kExecuteJsCallbackMessage)
		{
			int			callback_id = message->GetArgumentList()->GetInt(0);
			bool		has_error = message->GetArgumentList()->GetBool(1);
			CefString	json_string = message->GetArgumentList()->GetString(2);

			if (m_render_js_bridge.get())
			{
				// 将收到的参数通过管理器传递给调用时传递的回调函数
				ret = m_render_js_bridge->ExecuteJSCallbackFunc(callback_id, has_error, json_string);
			}
		}
bool CefJSBridge::ExecuteJSCallbackFunc(const int &js_callback_id, const bool &has_error, const CefString& json_result)
	{
		bool ret = false;
		auto it = m_render_callback.find(js_callback_id);
		if (it != m_render_callback.cend())
		{
			auto context = it->second.first;
			auto callback = it->second.second;

			if (context.get() && callback.get())
			{
				context->Enter();

				CefV8ValueList arguments;

				// 第一个参数标记函数执行结果是否成功
				arguments.push_back(CefV8Value::CreateBool(has_error));

				// 第二个参数携带函数执行后返回的数据
				CefV8ValueList json_parse_args;
				json_parse_args.push_back(CefV8Value::CreateString(json_result));
				CefRefPtr<CefV8Value> json_parse = context->GetGlobal()->GetValue("JSON")->GetValue("parse");
				CefRefPtr<CefV8Value> json_object = json_parse->ExecuteFunction(NULL, json_parse_args);
				arguments.push_back(json_object);

				// 执行 JS 方法
				CefRefPtr<CefV8Value> retval = callback->ExecuteFunction(NULL, arguments);
				if (retval.get())
				{
					if (retval->IsBool())
					{
						retval->GetBoolValue();
					}
				}

				context->Exit();

				// 从列表中移除 callback 缓存
			//	m_render_callback.erase(js_callback_id);

				ret= true;
			}
			else
			{
				ret= false;
			}

			// 从列表中移除 callback 缓存
			m_render_callback.erase(js_callback_id);
		}
		return ret;
	}

至此js调用C++就完成了

2,c++调用js

使用的V8上下文环境,执行JS函数 直接调用JS函数的ExecuteFunction 执行

CEF 利用 V8 JS 引擎来实现 JS

浏览器中的每一个 frame 都有自己的 JS 上下文,JS 只能在该上下文中执行
需要先在js中注册js函数,同时定义了返回值,用于c++调用时返回的数据

(() => {
			/*
			* 注册一个js函数,用于在 C++ 应用中调用
			* param[in] functionName 函数的名称,C++ 会使用该名称来调用此函数
			* param[in] callback 回调函数执行体
			*/
			CefWebInstance.register('cppCallJsFunctionRefreshPancel', (arguments) => {
				console.log(arguments)

				return {
					message: 'cppCallJsFunctionRefreshPancel success'
				}
				
			})
			
		})()

1,同样会先进入js_handler中

else if (name == "register")
			{
				if (arguments[0]->IsString() && arguments[1]->IsFunction())
				{
					std::string function_name = arguments[0]->GetStringValue();
					CefRefPtr<CefV8Value> callback = arguments[1];
					if (!m_js_bridge->RegisterJSFunc(function_name, callback))
					{
						exception = "Failed to register function.";
						break;
					}
				}
				else
				{
					exception = "Invalid arguments.";
					break;
				}
			}
bool CefJSBridge::RegisterJSFunc(const CefString& function_name, const CefRefPtr<CefV8Value> &function, const bool &replace/* = false*/)
	{
		bool ret = false;
		CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
		CefRefPtr<CefBrowser> browser = context->GetBrowser();

		if (replace)
		{
			m_render_registered_function.emplace(std::make_pair(function_name, browser->GetIdentifier()), function);
			ret= true;
		}
		else
		{
			auto it = m_render_registered_function.find(std::make_pair(function_name, browser->GetIdentifier()));
			if (it == m_render_registered_function.cend())
			{
				m_render_registered_function.emplace(std::make_pair(function_name, browser->GetIdentifier()), function);
				ret= true;
			}
		}

		return ret;
	}

至此js函数也就注册成功了,
2,c++端调用

bool CefJSBridge::CallJSFunction(const CefString& js_function_name, const CefString& params, const CefRefPtr<CefFrame> &frame)
	{
		bool ret = false;
		do
		{
			if (!frame.get())
			{
				break;
			}

			// 发送消息给 render 要求执行一个 js function
			CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kCallJsFunctionMessage);
			CefRefPtr<CefListValue> args = message->GetArgumentList();
			args->SetString(0, js_function_name);
			args->SetString(1, params);
			args->SetInt(2, (int)(frame->GetIdentifier()));

			//frame->GetBrowser()->GetMainFrame()->SendProcessMessage(PID_RENDERER, message);
			frame->SendProcessMessage(PID_RENDERER, message);
			
			ret = true;
		} while (false);
		return ret;
	}

进入render进程

else if (message_name == kCallJsFunctionMessage)
		{
			CefString function_name = message->GetArgumentList()->GetString(0);
			CefString json_string = message->GetArgumentList()->GetString(1);
			int64 frame_id = message->GetArgumentList()->GetInt(2);

			if (m_render_js_bridge.get())
			{
				// 通过 C++ 执行一个已经注册过的 JS 方法
				ret = m_render_js_bridge->ExecuteJSFunc(function_name, json_string, browser, frame);
			}
		}
bool CefJSBridge::ExecuteJSFunc(const CefString& function_name, const CefString& json_params, const CefRefPtr<CefBrowser> &browser, const CefRefPtr<CefFrame> &frame)
	{
		bool ret = false;
		auto it = m_render_registered_function.find(std::make_pair(function_name, browser->GetIdentifier()));
		if (it != m_render_registered_function.cend())
		{

			auto context = frame->GetV8Context();
			auto function = it->second;

			if (context.get() && function.get())
			{
				context->Enter();

				CefV8ValueList arguments;

				// 将 C++ 传递过来的 JSON 转换成 Object
				CefV8ValueList json_parse_args;
				json_parse_args.push_back(CefV8Value::CreateString(json_params));
				CefRefPtr<CefV8Value> json_object = context->GetGlobal()->GetValue("JSON");
				CefRefPtr<CefV8Value> json_parse = json_object->GetValue("parse");
				CefRefPtr<CefV8Value> json_stringify = json_object->GetValue("stringify");
				CefRefPtr<CefV8Value> json_object_args = json_parse->ExecuteFunction(NULL, json_parse_args);
				arguments.push_back(json_object_args);

				// 执行回调函数
				CefRefPtr<CefV8Value> retval = function->ExecuteFunction(NULL, arguments);
				if (retval.get() && retval->IsObject())
				{
					// 回复调用 JS 后的返回值
					CefV8ValueList json_stringify_args;
					json_stringify_args.push_back(retval);
					CefRefPtr<CefV8Value> json_string = json_stringify->ExecuteFunction(NULL, json_stringify_args);
					CefString str = json_string->GetStringValue();

					CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kExecuteCppCallbackMessage);
					CefRefPtr<CefListValue> args = message->GetArgumentList();
					args->SetString(0, function_name);
					args->SetInt(1, EN_ERROR_SUCCESS);
					args->SetString(2, json_string->GetStringValue());
					frame->SendProcessMessage(PID_BROWSER, message);
				}

				context->Exit();

				ret= true;
			}

		}
		else {
			auto context = frame->GetV8Context();
			std::wstring json_string = CEFError_Name[EN_ERROR_JSFUNC_NO_EXIST];
			CefRefPtr<CefProcessMessage> message = CefProcessMessage::Create(kExecuteCppCallbackMessage);
			CefRefPtr<CefListValue> args = message->GetArgumentList();
			args->SetString(0, function_name);
			args->SetInt(1, EN_ERROR_JSFUNC_NO_EXIST);
			args->SetString(2, json_string);
			frame->SendProcessMessage(PID_BROWSER, message);

		}
		return ret;
	}

进入browser中获取返回值

else if (message_name == kExecuteCppCallbackMessage)
		{
			CefString fun_name = message->GetArgumentList()->GetString(0);
			ENCEFErrorType errorType = (ENCEFErrorType)(message->GetArgumentList()->GetInt(1));
			CefString param = message->GetArgumentList()->GetString(2);

			HWND handle = ::GetParent(browser->GetHost()->GetWindowHandle());
			ret = CefManager::GetInstance()->ExecuteCppCallbackFunc(handle, fun_name, errorType,param);
		}

通过上层定义的回调函数就可以将js端返回的数据传给上层了。

bool CefJSBridge::ExecuteCppCallbackFunc(const HWND &hwnd, const CefString& function_name, const ENCEFErrorType& eNCEFErrorType, const CefString& json_string)
	{
		if (EN_ERROR_SUCCESS == eNCEFErrorType)
		{
			CCefOperationCallBkManger::GetInstance()->NotifyForCppCallJsBk(hwnd, function_name, json_string);
		}
		else
		{
			CCefOperationCallBkManger::GetInstance()->NotifyForErrorInfo(hwnd, eNCEFErrorType, json_string);
		}
		return true;
	}

至此c++调用js和js调用C++功能都实现了
在这里插入图片描述
上一章:duilib+cef+echarts (四) duilib中加载cef浏览器多窗口

源码路径:
duilib+cef+echarts,cef多窗口,js与c++交互(带回调函数的)双向通信

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值