CEF加载本地H5页面,支持JS和C++交互

2023-05-16

今天闲来无事,写个CEF相关的博客,很多同学还只会加载简单的CEF控件,但是涉及到需要和H5页面交互就比较头疼了,今天来简单介绍一下,有兴趣可以加qq 295282563,简单问题可以免费告知,复杂问题需要红包鼓励:)

       CEF控件很多开源的地方都有,我也写了一篇关于如何处理白屏的问题,以及一些注意事项。

最近研究发现还有很多人使用的是MFC框架,然后使用VC++的链接方式为MT或者MTd,这种方式不太适合加载很多第三方的东西,很容易出问题,推荐使用MD或者MDd。不然链接不上或者莫名其妙的崩溃问题就比较难处理了。好了开始正题。

一、设置本地页面的根目录和index入口文件      

二、在CEF控件里面创建一个一直循环的线程,作为本地服务,如函数HttpServerThread

三、找到一个可用的端口,利用evhttp_bind_socket创建本地socket服务,路径为本地H5页面的根目录

四、读取index文件,这样就可以完整的展示本地页面了,如何读取,见下面的代码

五、还需要进行C++和js的交互,就还需要加东西,通过继承CefV8Handler,注册C++函数到js

六、通过继承CefV8Handler的Execute函数,在js调用C++的时候,该函数会被调用

七、m_pBrowser->GetMainFrame()->ExecuteJavaScript(strJs, m_pBrowser->GetMainFrame()->GetURL(), 0);可以直接C++调用JS。

index页面如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title></title>
    <style type="text/css">
        div {
            width: 100px;
            height: 50px;
            background-color: skyblue;
            margin: 40px;
        }
    </style>

    <script language="javascript">


        function aaa(){
            document.getElementById('text_input').value = 'js called cpp';
            window.TestCpp("This is js value , js  js value!");
        };


        function CppCalJsFunc(str) {
            document.getElementById('text_input').value = str;
            window.TestCpp(str);
        }

    </script>

</head>
    <body>
        <div onclick="aaa()">click</div>

        <div>
            <input id="text_input" type="text" value="" />
        </div>

    </body>
</html>




#include "stdafx.h"
#include "UICefBrowser.h"
#include "IsPortUse.h"
#include "HttpLocalServer/Event2Handle.h"

CCefBrowserUI *gpThis = NULL;
CCefBrowserUI::CCefBrowserUI()
	: m_pProcessMessageHandler(new CProcessMessageHandler)
	, m_pClientHandler(new CCefClientHandler(this))
{
	gpThis = this;
}

CCefBrowserUI::~CCefBrowserUI()
{
	if (m_http_event != NULL)
		event_base_loopexit(m_http_event, 0);

	m_pClientHandler->Close();
	if (m_handle_name_pipe != NULL)
	{
		DisconnectNamedPipe(m_handle_name_pipe);
		CloseHandle(m_handle_name_pipe);
		m_handle_name_pipe = NULL;
	}
	
	if (m_pBrowser != nullptr)
	{
		m_pClientHandler->CloseBrowser(m_pBrowser);
	}
	printf("~CCefBrowserUI \n");
}


unsigned __stdcall HttpServerThread(void * pParam)
{
	CCefBrowserUI* pTHis = (CCefBrowserUI*)pParam;
	pTHis->m_http_event = event_base_new();
	struct evhttp* http_server = evhttp_new(pTHis->m_http_event);
	if (!http_server)
	{
		return 0;
	}
	while (IsPortInUse(pTHis->m_nServerPort))
	{
		pTHis->m_nServerPort++;
	}
	int ret = evhttp_bind_socket(http_server, "127.0.0.1", pTHis->m_nServerPort);
	if (ret != 0)
	{
		char str[256] = { 0 };
		sprintf(str, "evhttp_bind_socket 失败 绑定的端口为%d", pTHis->m_nServerPort);
		MessageBoxA(NULL, str, "error", MB_OK);
		evhttp_free(http_server);
		return 0;
	}

	evhttp_set_gencb(http_server, send_document_cb, (void*)pTHis->m_strDirectory.c_str());

	read_file((char*)pTHis->m_strIndexFilePathName.c_str());

	pTHis->m_bThreadRunning = true;
	event_base_loop(pTHis->m_http_event, 0);

	evhttp_free(http_server);

	return 0;
}

void CALLBACK  CCefBrowserUI::TimerProc(HWND   hWnd, UINT   nMsg, UINT   nTimerid, DWORD   dwTime)
{
	if (nMsg == gpThis->m_dwTimeId)
	{
		if (gpThis->m_bThreadRunning)
		{
			::KillTimer(NULL, gpThis->m_dwTimeId);
			TCHAR url[256] = TEXT("");
			_stprintf_s(url, TEXT("http://127.0.0.1:%d"), gpThis->m_nServerPort);
			gpThis->Navigate2(url);
			gpThis->m_bThreadRunning = false;
		}
	}
}

bool CCefBrowserUI::LoadFileServer(std::string strDirectory, std::string strIndexFilePathName)
{
	m_strDirectory = strDirectory;
	m_strIndexFilePathName = strIndexFilePathName;

	HANDLE hThread;
	unsigned threadID;
	hThread = (HANDLE)_beginthreadex(NULL, 0, &HttpServerThread, (void*)this, 0, &threadID);

	if (hThread == NULL)
		return false;

	TIMERPROC tProc;
	__asm
	{
		mov eax, TimerProc 
		mov tProc, eax    
	}
	m_dwTimeId = ::SetTimer(NULL, WM_USER + 200, 200, tProc);
}

void CCefBrowserUI::jsBindFunction(const CefString &name, CustomFunction function)
{
	if (m_mapfunction.size() > 0)
		ASSERT(m_mapfunction.find(name) == m_mapfunction.end());//插入的函数名已存在,报错
	m_mapfunction[name] = function;
}


void CCefBrowserUI::LoadString(LPCTSTR string, LPCTSTR failedUrl)
{
	if (m_pBrowser->GetMainFrame().get()) m_pBrowser->GetMainFrame()->LoadString(string, failedUrl);
}

//C++调用JS的其中一个入口,总的入口为ExecuteJavaScript
void CCefBrowserUI::CallJsFunc(std::string jsCode, const char* strParam, int nParamCount /* = 0*/)
{
	std::string stringParam = strParam;
	stringParam.erase(std::remove(stringParam.begin(), stringParam.end(), '\n'), stringParam.end());
	stringParam.erase(std::remove(stringParam.begin(), stringParam.end(), '\r'), stringParam.end());

	char strJs[1024] = {0};
	sprintf(strJs, "window.%s(%s)", jsCode.c_str(), stringParam.c_str());
	//m_pBrowser->GetMainFrame()->ExecuteJavaScript(jsCode.c_str(), m_pBrowser->GetMainFrame()->GetURL(), 0);//直接这样调用不起作用
	m_pBrowser->GetMainFrame()->ExecuteJavaScript(strJs, m_pBrowser->GetMainFrame()->GetURL(), 0);
}

void CCefBrowserUI::call(std::string jsFunction, bool bRet, std::string strParam2, std::string strParam3)
{
	std::string stringParam2 = strParam2;
	stringParam2.erase(std::remove(stringParam2.begin(), stringParam2.end(), '\n'), stringParam2.end());
	stringParam2.erase(std::remove(stringParam2.begin(), stringParam2.end(), '\r'), stringParam2.end());

	std::string stringParam3 = strParam3;
	stringParam3.erase(std::remove(stringParam3.begin(), stringParam3.end(), '\n'), stringParam3.end());
	stringParam3.erase(std::remove(stringParam3.begin(), stringParam3.end(), '\r'), stringParam3.end());

	std::string stringParam1 = "false";
	if (bRet)
		stringParam1 = "true";
	char strJs[1024] = { 0 };
	
	sprintf(strJs, "window.%s(%s, %s, %s)", jsFunction.c_str(), stringParam1.c_str(), stringParam2.c_str(), stringParam3.c_str());

	m_pBrowser->GetMainFrame()->ExecuteJavaScript(jsFunction.c_str(), m_pBrowser->GetMainFrame()->GetURL(), 0);
}

void CCefBrowserUI::call(std::string jsFunction, std::string strParam1)
{
	CallJsFunc(jsFunction, strParam1.c_str(), 1);
	//m_pBrowser->GetMainFrame()->ExecuteJavaScript(jsFunction.c_str(), m_pBrowser->GetMainFrame()->GetURL(), 0);
}

void CCefBrowserUI::DoInit()
{
	m_pClientHandler->CreateBrowser(m_pManager->GetPaintWindow(), m_rcItem);
}

bool CCefBrowserUI::DoPaint(HDC hDC, const RECT& rcPaint, CControlUI* pStopControl)
{
	resize();

	return CControlUI::DoPaint(hDC, rcPaint, pStopControl);
}

void CCefBrowserUI::SetAttribute(faw::string_view_t pstrName, faw::string_view_t pstrValue)
{
	if (pstrName == _T("url") == 0) Navigate2(pstrValue.data());
	else return CControlUI::SetAttribute(pstrName, pstrValue);
}

void CCefBrowserUI::SetVisible(bool bVisible)
{
	if (m_pBrowser != nullptr)
	{
		m_pBrowser->GetHost()->SetWindowVisibility(bVisible);
	}
	else
	{
		m_AfterCreatedCacheTasks.push([bVisible, this]{ SetVisible(bVisible); });
	}

	return CControlUI::SetVisible(bVisible);
}

void CCefBrowserUI::SetInternVisible(bool bVisible)
{
	if (m_pBrowser != nullptr)
	{
		m_pBrowser->GetHost()->SetWindowVisibility(bVisible);
	}
	else
	{
		m_AfterCreatedCacheTasks.push([bVisible, this]{ SetInternVisible(bVisible); });
	}

	return CControlUI::SetInternVisible(bVisible);
}

void CCefBrowserUI::SetProcessMessageHandler(CefRefPtr<CProcessMessageHandler> pHandler)
{
	m_pProcessMessageHandler = pHandler;
}

void CCefBrowserUI::OnProcessMessageReceived(CefRefPtr<CefProcessMessage> message)
{
	if (m_pProcessMessageHandler != nullptr)
	{
		if (!message->IsValid()) {
			DWORD dwWrite;
			WriteFile(m_handle_name_pipe, L"DONE", wcslen(L"DONE") * 2, &dwWrite, NULL);
			return ;
		}

		CefString name = message->GetName();
		CefRefPtr<CefListValue> list = message->GetArgumentList();

		bool bFindFunc = false;
		auto it = m_mapfunction.begin();
		while (it != m_mapfunction.end())
		{
			if (name == it->first)
			{
				
				bFindFunc = true;
				break;
			}
			it++;
		}

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

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

				}
				else if (ret->GetType() == VTYPE_STRING) {
					CefString str = ret->GetString();
					wsprintf(buf, L"%s%s\n", L"s", str.ToWString().c_str());

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

			}
			else {
				wsprintf(buf, L"%s\n", L"DONE");
			}

			DWORD dwWrite;
			//写管道
			WriteFile(m_handle_name_pipe, buf, wcslen(buf)*2, &dwWrite, NULL);
		}
		else {
			DWORD dwWrite;
			//没有返回值
			WriteFile(m_handle_name_pipe, L"DONE\n", wcslen(L"DONE\n")*2 , &dwWrite, NULL);
		}

		//m_pProcessMessageHandler->OnProcessMessageReceived(message);
	}
}

void CCefBrowserUI::OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
	// 只执行一次
	if (m_pBrowser != nullptr)
		return;

	m_pBrowser = browser;

	

	//创建命名管道
	wchar_t name_pipe[50] = { 0 };
	//获取管道名称 拼接浏览器id来确保唯一
	wsprintf(name_pipe, L"\\\\.\\pipe\\browser_pipe_%d", browser->GetIdentifier());
	//MessageBox(NULL, name_pipe, L"", MB_OK);
	//创建管道
	m_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);
		m_pBrowser->SendProcessMessage(PID_RENDERER, msg);
	}

	//这边是服务端
	if (m_handle_name_pipe != INVALID_HANDLE_VALUE) {
		HANDLE hEvent;
		hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (hEvent != INVALID_HANDLE_VALUE) {
			OVERLAPPED over = { 0 };
			int n = ConnectNamedPipe(m_handle_name_pipe, &over);
			int m = 0;
		}
	}

	

	if (m_pBrowser)
	{
		//给Render进程发送消息 设置函数名称
		if (m_mapfunction.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 = m_mapfunction.begin(); iter != m_mapfunction.end(); ++iter) {
					args->SetString(index, iter->first);
					++index;
				}
				m_pBrowser->SendProcessMessage(PID_RENDERER, msg_fun);
			}
		}
	}

	// 执行缓存的任务
	CefCacheTask task;
	while (!m_AfterCreatedCacheTasks.empty())
	{
		task = move(m_AfterCreatedCacheTasks.front());
		m_AfterCreatedCacheTasks.pop();

		task();
	}
}

bool CCefBrowserUI::DoClose(CefRefPtr<CefBrowser> browser) 
{
	//清除函数map
	m_mapfunction.clear();

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

	return true;
}

void CCefBrowserUI::Navigate2(CefString url)
{
	if (m_pBrowser != nullptr)
	{
		m_pBrowser->GetMainFrame()->LoadURL(url);
	}
	else
	{
		m_AfterCreatedCacheTasks.push([url, this]
		{
			Navigate2(url);
		});
	}
}

void CCefBrowserUI::RunJs(CefString JsCode)
{
	if (m_pBrowser != nullptr)
	{
		m_pBrowser->GetMainFrame()->ExecuteJavaScript(JsCode, "", 0);
	}
	else
	{
		m_AfterCreatedCacheTasks.push([JsCode, this]{ RunJs(JsCode); });
	}
}

void CCefBrowserUI::resize()
{
	if (m_pBrowser != nullptr)
	{
		POINT pt	= { m_rcItem.left, m_rcItem.top };
		HWND hwnd	= m_pBrowser->GetHost()->GetWindowHandle();

		if (GetWindowStyle(hwnd) & WS_POPUP)
		{
			::ClientToScreen(GetManager()->GetPaintWindow(), &pt);
		}

		::MoveWindow(hwnd
			, pt.x
			, pt.y
			, m_rcItem.right - m_rcItem.left
			, m_rcItem.bottom - m_rcItem.top
			, TRUE);
	}
	else
	{
		m_AfterCreatedCacheTasks.push([this]{ resize(); });
	}
}

JS调用C++截图如下

 

C++调用js成功如下图:

主要的交互代码在上面的代码区已经展示了,具体需要帮助请加qq:295282563 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CEF加载本地H5页面,支持JS和C++交互 的相关文章

随机推荐