今天闲来无事,写个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