PC游戏中用CEF3制作内嵌浏览器
因为项目需要,需要将游戏手机助手中的朋友圈给移植到PC游戏中,而以前游戏中的内嵌浏览器采用的是IE6内核,满足不了我们的需求,于是决定把Cef3内嵌到游戏中,在完成正常工作之余,利用闲散时间不断地查找各种资料,初步完成了项目需求,决定写一篇文章来记录我碰到的各种坑。
一开始我只是在网上找了一个最后支持XP系统的版本–2623版本,利用这个版本进行开发,后来发现不支持MP3、MP4,是需要重新编译一个支持MP3、MP4的版本的,在网上找一个已编译好了的版本(在Windows下编译Cef3.2623并加入mp3、mp4支持(附带源码包和最终DLL)),最后把一些dll、资源等替换下就行,并不需要更改工程代码,非常感谢Redrain!附上链接:http://blog.csdn.net/zhuhongshu/article/details/54193842
一、首先来了解下Cef3
CEF全称Chromium Embedded Framework,是一个基于Google Chromium 的开源项目。CEF的典型应用场景包括:
嵌入一个兼容HTML5的浏览器控件到一个已经存在的本地应用。
· 创建一个轻量化的壳浏览器,用以托管主要用Web技术开发的应用。
· 有些应用有独立的绘制框架,使用CEF对Web内容做离线渲染。
· 使用CEF做自动化Web测试。
CEF3是基于Chomuim Content API多进程构架的下一代CEF,拥有下列优势:
· 改进的性能和稳定性(JavaScript和插件在一个独立的进程内执行)。
· 支持Retina显示器。
· 支持WebGL和3D CSS的GPU加速。
· 类似WebRTC和语音输入这样的前卫特性。
· 通过DevTools远程调试协议以及ChromeDriver2提供更好的自动化UI测试。
· 更快获得当前以及未来的Web特性和标准的能力。
翻译自https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md
看不懂英文的可以看这篇GeneralUsage翻译文:https://github.com/fanfeilong/cefutil/blob/master/doc/CEF General Usage-zh-cn.md#important-concepts
二、二进制文件及资源文件说明
有些文件是必须的,否则无法运行
CEF core library.
libcef.dll(Cef核心文件,含js引擎、网页加载渲染逻辑等)Crash reporting library.
chrome_elf.dll(我还没用上)Unicode support data.
icudtl.dat(支持Unicode需要)V8 snapshot data.
natives_blob.bin
snapshot_blob.bin(V8引擎快照数据需要)
有些是根据情况可删减的,cef可以继续运行,或缺失部分功能:
Localized resources.
Locale file loading can be disabled completely using
CefSettings.pack_loading_disabled. The locales directory path can be
customized using CefSettings.locales_dir_path. (可设置本地资源的路径)- locales/
Directory containing localized resources used by CEF, Chromium and Blink. A
.pak file is loaded from this directory based on the CefSettings.locale
value. Only configured locales need to be distributed. If no locale is
configured the default locale of “en-US” will be used. Without these files
arbitrary Web components may display incorrectly.(CEF,Chromium和Blink使用的本地化资源。如果没有区域设置将配置默认语言环境“en-US”。如果没有这些文件任意Web组件可能显示不正确。)
- locales/
Other resources.
Pack file loading can be disabled completely using
CefSettings.pack_loading_disabled. The resources directory path can be
customized using CefSettings.resources_dir_path.cef.pak
cef_100_percent.pak
cef_200_percent.pak
These files contain non-localized resources used by CEF, Chromium and Blink.
Without these files arbitrary Web components may display incorrectly.(这些文件包含了供CEF使用的区域无关资源,缺少这些文件任意Web组件可能显示不正确。)cef_extensions.pak
This file contains non-localized resources required for extension loading.
Pass the--disable-extensions
command-line flag to disable use of this
file. Without this file components that depend on the extension system,
such as the PDF viewer, will not function.(此文件包含扩展加载所需的非本地化资源传递--disable-extensions
命令行标志来禁止使用文件。没有这个文件,依赖于扩展系统的组件将不起作用,如PDF查看器。)devtools_resources.pak
This file contains non-localized resources required for Chrome Developer
Tools. Without this file Chrome Developer Tools will not function.( 此文件包含Chrome开发者工具所需的非本地化资源,缺少这个文件,Chrome开发者工具将无法运行。)
Angle and Direct3D support.
- d3dcompiler_43.dll (required for Windows XP)
- d3dcompiler_47.dll (required for Windows Vista and newer)
- libEGL.dll
- libGLESv2.dll
Without these files HTML5 accelerated content like 2D canvas, 3D CSS and WebGL
will not function.(Direct3D支持文件,如果缺少这些文件,HTML5在渲染2D画布,3D CSS,WebGL时将不起作用。)
Widevine CDM support.
widevinecdmadapter.dll(DRM数字版权管理功能Widevine需要)
Without this file playback of Widevine projected content will not function.
See the CefRegisterWidevineCdm() function in cef_web_plugin.h for usage.附上英文链接:https://bitbucket.org/chromiumembedded/cef/src/816f700d3ea42bedc5ca5a2314c27b761b69abc5/tools/distrib/win/README.redistrib.txt?at=master&fileviewer=file-view-default
三、引入到工程中
因为是内嵌在我们自己的项目中,所以只需要给工程中引入cef3,将一些dll等资源文件加入到工程exe所在目录下,在我们自己的第三方库文件夹中加入了cef3文件夹,里面加入cef的include文件夹,和lib文件夹,lib文件夹里分别放入的是debug版本和release版本的libcef.lib文件和libcef_dll_wrapper.lib文件。
配置项目属性:
C/C++ ->常规附加包含目录中加入cef的include目录所在路径
链接器->输入添加附加依赖项libcef.lib和libcef_dll_wrapper.lib
利用CMAKE创建cef3的demo工程,找到cefsimole工程,改造cefsimple工程的代码,是满足我们的需求。当然你的需求很复杂,还是建议看看cefclient工程。cefclient是一个完整的CEF客户端应用程序示例,并且它的源码包含在CEF每个二进制发布包中。使用CEF创建一个新的应用程序,最简单的方法是先从cefclient应用程序开始,删除你不需要的部分。本文档中许多示例都是来源于cefclient应用程序。
在我们在开发之前,需要知道一些cef的基础概念。如果你和我一样是一个新手,建议还是先了解了解基础知识。
四、Cef基础概念
1、多进程架构
首先你要知道cef3是多进程架构的,这点非常重要。Browser被定义为主进程,负责窗口管理,界面绘制和网络交互。Blink的渲染和Js的执行被放在一个独立的Render 进程中;除此之外,Render进程还负责Js Binding和对Dom节点的访问。 默认的进程模型中,会为每个标签页创建一个新的Render进程。其他进程按需创建,例如管理插件的进程以及处理合成加速的进程等都是按需创建。
默认情况下,主应用程序会被多次启动运行各自独立的进程。这是通过传递不同的命令行参数给CefExecuteProcess函数做到的。如果主应用程序很大,加载时间比较长,或者不能在非浏览器进程里使用,则宿主程序可使用独立的可执行文件去运行这些进程。这可以通过配置CefSettings.browser_subprocess_path变量做到。
CEF3的进程之间可以通过IPC进行通信。Browser和Render进程可以通过发送异步消息进行双向通信。甚至在Render进程可以注册在Browser进程响应的异步JavaScript API。
通过设置命令行的–single-process,CEF3就可以支持用于调试目的的单进程运行模型。
2、多线程
在CEF3中,每个进程都会运行多个线程。完整的线程类型表请参照cef_thread_id_t。例如,在Browser进程中包含如下主要的线程:
TID_UI 线程是浏览器的主线程。如果应用程序在调用调用CefInitialize()时,传递CefSettings.multi_threaded_message_loop=false,这个线程也是应用程序的主线程。
TID_IO 线程主要负责处理IPC消息以及网络通信。
TID_FILE 线程负责与文件系统交互。
由于CEF采用多线程架构,有必要使用锁和闭包来保证数据的线程安全语义。IMPLEMENT_LOCKING定义提供了Lock()和Unlock()方法以及AutoLock对象来保证不同代码块同步访问数据。CefPostTask函数组支持简易的线程间异步消息传递。
可以通过CefCurrentlyOn()方法判断当前所在的线程环境,cefclient工程使用下面的定义来确保方法在期望的线程中被执行。
#define REQUIRE_UI_THREAD() ASSERT(CefCurrentlyOn(TID_UI));
#define REQUIRE_IO_THREAD() ASSERT(CefCurrentlyOn(TID_IO));
#define REQUIRE_FILE_THREAD() ASSERT(CefCurrentlyOn(TID_FILE));
3、字符串
在C++中,通常使用CefString类来管理CEF的字符串。CefString支持与std::string(UTF8)、std::wstring(wide)类型的相互转换。也可以用来包裹一个cef_string_t结构来对其进行赋值。
和std::string的相互转换:
std::string str = “Some UTF8 string”;
// Equivalent ways of assigning |str| to |cef_str|. Conversion from UTF8 will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromString(str);// Equivalent ways of assigning |cef_str| to |str|. Conversion to UTF8 will occur if necessary.
str = cef_str;
str = cef_str.ToString();
和std::wstring的相互转换:
std::wstring str = “Some wide string”;
// Equivalent ways of assigning |str| to |cef_str|. Conversion from wide will occur if necessary.
CefString cef_str(str);
cef_str = str;
cef_str.FromWString(str);// Equivalent ways of assigning |cef_str| to |str|. Conversion to wide will occur if necessary.
str = cef_str;
str = cef_str.ToWString();
4、入口函数
一个CEF3应用程序会运行多个进程,这些进程能够使用同一个执行器或者为子进程定制的、单独的执行器。进程的执行从入口函数开始。当执行子进程时,CEF将使用命令行参数指定配置信息,这些命令行参数必须通过CefMainArgs结构体传入到CefExecuteProcess函数。
在Windows平台下,它接收wWinMain函数传入的参数:实例句柄(HINSTANCE),这个实例能够通过函数GetModuleHandle(NULL)获取。
CefMainArgs main_args(hInstance);
5、单一执行体
当以单一执行体运行时,根据不同的进程类型,入口函数有差异。
int main(int argc, char* argv[]) {// Structure for passing command-line arguments.// The definition of this structure is platform-specific.CefMainArgs main_args(argc, argv);// Optional implementation of the CefApp interface.CefRefPtr<SimpleApp> app(new SimpleApp);// Execute the sub-process logic, if any. This will either return immediately for the browser// process or block until the sub-process should exit.int exit_code = CefExecuteProcess(main_args, app.get());if (exit_code >= 0) {// The sub-process terminated, exit now.return exit_code;}// Populate this structure to customize CEF behavior.CefSettings settings;// Initialize CEF in the main process.CefInitialize(main_args, settings, app.get());// Run the CEF message loop. This will block until CefQuitMessageLoop() is called.CefRunMessageLoop();// Shut down CEF.CefShutdown();return 0;}
6、分离子进程执行体
当使用独立的子进程执行体时,你需要2个分开的可执行工程和2个分开的入口函数。
在我们的项目中我采用的就是分离子进程执行体。主程序的入口就是游戏的入口,而子程序入口就是新建的一个工程(zt2asHelp)的入口。
主程序的入口函数:
// Program entry-point function.
// 程序入口函数
int main(int argc, char* argv[]) {// Structure for passing command-line arguments.// The definition of this structure is platform-specific.// 传递命令行参数的结构体。// 这个结构体的定义与平台相关。CefMainArgs main_args(argc, argv);// Optional implementation of the CefApp interface.// 可选择性地实现CefApp接口CefRefPtr<SimpleApp> app(new SimpleApp);// Populate this structure to customize CEF behavior.// 填充这个结构体,用于定制CEF的行为。CefSettings settings;// Specify the path for the sub-process executable.// 指定子进程的执行路径CefString(&settings.browser_subprocess_path).FromASCII(“/path/to/subprocess”);// Initialize CEF in the main process.// 在主进程中初始化CEF CefInitialize(main_args, settings, app.get());// Run the CEF message loop. This will block until CefQuitMessageLoop() is called.// 执行消息循环,此时会堵塞,直到CefQuitMessageLoop()函数被调用。CefRunMessageLoop();// Shut down CEF.// 关闭CEFCefShutdown();return 0;
}
子进程程序的入口函数:
// Program entry-point function.// 程序入口函数int main(int argc, char* argv[]) {// Structure for passing command-line arguments.// The definition of this structure is platform-specific.// 传递命令行参数的结构体。// 这个结构体的定义与平台相关。CefMainArgs main_args(argc, argv);// Optional implementation of the CefApp interface.// 可选择性地实现CefApp接口CefRefPtr<SimpleApp> app(new SimpleApp);// Execute the sub-process logic. This will block until the sub-process should exit.// 执行子进程逻辑,此时会堵塞直到子进程退出。return CefExecuteProcess(main_args, app.get());}
7、CefSettings
结构体允许定义全局的CEF配置,经常用到的配置项如下:
single_process 设置为true时,Browser和Renderer使用一个进程。此项也可以通过命令行参数“single-process”配置。(single_process == true 时cef3不稳定,调试可以用下)
browser_subprocess_path 设置用于启动子进程单独执行器的路径。
8、CefApp
CefApp接口提供了不同进程的可定制回调函数。毕竟重要的回调函数如下:
GetBrowserProcessHandler 返回定制Browser进程的Handler,该Handler包括了诸如OnContextInitialized的回调。
GetRenderProcessHandler 返回定制Render进程的Handler,该Handler包含了JavaScript相关的一些回调以及消息处理的回调。
9、CefClient
CefClient提供访问Browser实例的回调接口。一个CefClient实现可以在任意数量的Browser进程中共享。以下为几个重要的回调:
比如处理Browser的生命周期,右键菜单,对话框,通知显示, 拖曳事件,焦点事件,键盘事件等等。如果没有对某个特定的处理接口进行实现会造成什么影响,请查看cef_client.h文件中相关说明。
OnProcessMessageReceived在Browser收到Render进程的消息时被调用。
到这你差不多就知道cef3是怎么一回事了。我们可以上代码了
五、上代码
先说两点:
1、在游戏窗口之前初始化Cef
2、在关闭游戏之前先关闭Cef
在游戏工程中添加两个函数分别进行初始化和关闭Cef
// 初始化函数:void GameAppation::InitializeCef(){// Enable High-DPI support on Windows 7 or newer.CefEnableHighDPISupport();void* sandbox_info = NULL;#if defined(CEF_USE_SANDBOX)// Manage the life span of the sandbox information object. This is necessary// for sandbox support on Windows. See cef_sandbox_win.h for complete details.CefScopedSandboxInfo scoped_sandbox;sandbox_info = scoped_sandbox.sandbox_info();#endif// Provide CEF with command-line arguments.CefMainArgs main_args(hInstance); // hInstance是游戏窗口的实例句柄(HINSTANCE)// Parse command-line arguments.CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();command_line->InitFromString(::GetCommandLineW());// Create a ClientApp of the correct type.CefRefPtr<CefApp> app;// The command-line flag won't be specified for the browser process.if (!command_line->HasSwitch("type")){app = new SimpleApp();}else{const std::string& processType = command_line->GetSwitchValue("type");if (processType == "renderer"){app = new SimpleApp();}else{// app = new SimpleOtherApp();}}// CEF applications have multiple sub-processes (render, plugin, GPU, etc)// that share the same executable. This function checks the command-line and,// if this is a sub-process, executes the appropriate logic.int exit_code = CefExecuteProcess(main_args, NULL, sandbox_info);if (exit_code >= 0) {// The sub-process has completed so return here.return exit_code;}// Specify CEF global settings here.CefSettings settings;#if !defined(CEF_USE_SANDBOX)settings.no_sandbox = true;#endif// 在这设置用于启动子进程单独执行器的路径WCHAR subProcessPath[260] = { 0 };GetModuleFileNameW(hInstance, subProcessPath, 260);*(wcsrchr(subProcessPath, L'\\') + 1) = L'\0';LPCWSTR SUB_PROCESS_NAME = L"zt2asHelp.exe";wcsncat_s(subProcessPath, 260, SUB_PROCESS_NAME, wcslen(SUB_PROCESS_NAME));cef_string_from_wide(subProcessPath, 260, &settings.browser_subprocess_path);// SimpleApp implements application-level callbacks for the browser process.// It will create the first browser instance in OnContextInitialized() after// CEF has initialized.//CefRefPtr<SimpleApp> app(new SimpleApp);// Initialize CEF.CefInitialize(main_args, settings, app.get(), sandbox_info);}// 关闭函数void GameAppation::UnInitializeCef(){CefShutdown();}
将cefsimple工程中的simple_app.cc、simple_handle.cc、simle_handle_win.cc及simple_app.h、simple_handle.h添加进游戏工程中。并添加两个文件simple_v8_handler.cc、simple_v8_handler.h。在分离子进程执行体的工程里新加一个文件zt2asHelp.cpp,并将上面几个文件添加进工程。
先上代码,后面再分析。
修改文件代码:
simple_app.h
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.#ifndef CEF_TESTS_CEFSIMPLE_SIMPLE_APP_H_
#define CEF_TESTS_CEFSIMPLE_SIMPLE_APP_H_#include "include/cef_app.h"// Implement application-level callbacks for the browser process.
class SimpleApp : public CefApp,public CefBrowserProcessHandler,public CefRenderProcessHandler {public:SimpleApp();// CefApp methods:virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() OVERRIDE { return this; }virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() OVERRIDE { return this; }virtual void OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) OVERRIDE;virtual void OnWebKitInitialized() OVERRIDE;// CefBrowserProcessHandler methods:virtual void OnContextInitialized() OVERRIDE;virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,CefProcessId source_process,CefRefPtr<CefProcessMessage> message) OVERRIDE;private:CefRefPtr<SimpleV8JsHandler> m_MyJShander = nullptr;// Include the default reference counting implementation.IMPLEMENT_REFCOUNTING(SimpleApp);
};#endif // CEF_TESTS_CEFSIMPLE_SIMPLE_APP_H_
simple_app.cc
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.#include "simple_app.h"
#include "Simple_V8_JS_Handler.h"
#include <tchar.h>#include <string>#include "simple_handler.h"
#include "include/cef_browser.h"
#include "include/cef_command_line.h"
#include "include/wrapper/cef_helpers.h"SimpleApp::SimpleApp() {
}void SimpleApp::OnContextInitialized() {// 这块的内容由游戏里去创建
}void SimpleApp::OnContextCreated(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context)
{// The var type can accept all object or variableCefRefPtr<CefV8Value> window = context->GetGlobal();if (!m_MyJShander){m_MyJShander = new SimpleV8JsHandler();}CefRefPtr<CefV8Value> strValue = CefV8Value::CreateString("");window->SetValue("token", strValue, V8_PROPERTY_ATTRIBUTE_NONE);CefRefPtr<CefV8Value> strValue1 = CefV8Value::CreateUInt(0);window->SetValue("accid", strValue, V8_PROPERTY_ATTRIBUTE_NONE);CefRefPtr<CefV8Value> myFunc = CefV8Value::CreateFunction(_T("youjin"), m_MyJShander);window->SetValue(_T("youjin"), myFunc, V8_PROPERTY_ATTRIBUTE_NONE);CefRefPtr<CefV8Value> myFunc2 = CefV8Value::CreateFunction(_T("mimang"), m_MyJShander);window->SetValue(_T("mimang"), myFunc2, V8_PROPERTY_ATTRIBUTE_NONE);CefRefPtr<CefV8Value> myFunc3 = CefV8Value::CreateFunction(_T("dejin"), m_MyJShander);window->SetValue(_T("dejin"), myFunc3, V8_PROPERTY_ATTRIBUTE_NONE);CefRefPtr<CefV8Value> myFunc4 = CefV8Value::CreateFunction(_T("mubiao"), m_MyJShander);window->SetValue(_T("mubiao"), myFunc4, V8_PROPERTY_ATTRIBUTE_NONE);
}void SimpleApp::OnWebKitInitialized()
{std::string extensionCode ="var g_value=\"global value here\";""var zt2as;""if (!zt2as)"" zt2as = {};""(function() {"" zt2as.youjin = function() {"" native function youjin();"" return youjin();"" };""})();""(function() {"" zt2as.mimang = function() {"" native function mimang();"" return mimang();"" };""})();""(function() {"" zt2as.dejin = function() {"" native function dejin();"" return dejin();"" };""})();""(function() {"" zt2as.mubiao = function() {"" native function mubiao();"" return mubiao();"" };""})();";// 声明本地函数 native function hehe();" 如果有参数列表需要写具体的类型,而不能写var类型!与本地声明一直// 调用本地函数 return hehe();"// Create an instance of my CefV8Handler object.CefRefPtr<CefV8Handler> handler = new SimpleV8JsHandler();// Register the extension.CefRegisterExtension("v8/mycode", extensionCode, handler);
}bool SimpleApp::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,CefProcessId source_process,CefRefPtr<CefProcessMessage> message)
{const std::string& messageName = message->GetName();if (messageName == "func_back"){CefRefPtr<CefListValue> argList = message->GetArgumentList();std::string token_ = argList->GetString(0);uint32_t accid_ = argList->GetInt(1);CefRefPtr<CefV8Value> strValue = CefV8Value::CreateString(token_);browser->GetMainFrame()->GetV8Context()->GetGlobal()->SetValue("token", strValue, V8_PROPERTY_ATTRIBUTE_NONE);CefRefPtr<CefV8Value> strValue1 = CefV8Value::CreateUInt(accid_);browser->GetMainFrame()->GetV8Context()->GetGlobal()->SetValue("accid", strValue1, V8_PROPERTY_ATTRIBUTE_NONE);return true;}return false;
}
simple_handle.h
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.#ifndef CEF_TESTS_CEFSIMPLE_SIMPLE_HANDLER_H_
#define CEF_TESTS_CEFSIMPLE_SIMPLE_HANDLER_H_#include "include/cef_client.h"#include <list>class SimpleHandler : public CefClient,public CefDisplayHandler,public CefLifeSpanHandler,public CefLoadHandler,public CefContextMenuHandler,public CefJSDialogHandler
{public:SimpleHandler();~SimpleHandler();enum MY_MENU {MENU_ID_USER_OPENLINK = MENU_ID_USER_FIRST + 200,MENU_ID_USER_SHOWDEVTOOLS,};// Provide access to the single global instance of this object.static SimpleHandler* GetInstance();// CefClient methods:virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE {return this;}virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {return this;}virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE {return this;}virtual CefRefPtr<CefContextMenuHandler> GetContextMenuHandler() OVERRIDE {return this;}virtual CefRefPtr<CefJSDialogHandler> GetJSDialogHandler() OVERRIDE {return this;}// CefDisplayHandler methods:virtual void OnTitleChange(CefRefPtr<CefBrowser> browser,const CefString& title) OVERRIDE;// CefLifeSpanHandler methods:virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;virtual bool DoClose(CefRefPtr<CefBrowser> browser) OVERRIDE;virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;// CefLoadHandler methods:virtual void OnLoadError(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,ErrorCode errorCode,const CefString& errorText,const CefString& failedUrl) OVERRIDE;virtual void OnBeforeContextMenu(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefContextMenuParams> params,CefRefPtr<CefMenuModel> model) OVERRIDE;virtual bool OnContextMenuCommand(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefContextMenuParams> params,int command_id,EventFlags event_flags) OVERRIDE;virtual bool OnJSDialog(CefRefPtr<CefBrowser> browser,const CefString& origin_url,const CefString& accept_lang,JSDialogType dialog_type,const CefString& message_text,const CefString& default_prompt_text,CefRefPtr<CefJSDialogCallback> callback,bool& suppress_message) OVERRIDE;// Request that all existing browser windows close.void CloseAllBrowsers(bool force_close);bool IsClosing() const { return is_closing_; }virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,CefProcessId source_process,CefRefPtr<CefProcessMessage> message) OVERRIDE;void SetToken(std::string str) { token_ = str; }std::string GetToken() { return token_; }void SetAccid(uint32_t accid) { accid_ = accid; }uint32_t GetAccid() { return accid_; }CefRefPtr<CefBrowser> GetBrowser() { return browser_; }void ShowDevelopTools(CefRefPtr<CefBrowser> browser);HWND GetGameHwnd() { return gameHwnd; }void SetGameHwnd(HWND h) { gameHwnd = h; }private:// List of existing browser windows. Only accessed on the CEF UI thread.typedef std::list<CefRefPtr<CefBrowser> > BrowserList;BrowserList browser_list_;bool is_closing_;CefRefPtr<CefBrowser> browser_ = nullptr;std::string token_ = "";uint32_t accid_ = 0;HWND gameHwnd = NULL;// Include the default reference counting implementation.IMPLEMENT_REFCOUNTING(SimpleHandler);
};#endif // CEF_TESTS_CEFSIMPLE_SIMPLE_HANDLER_H_
simple_handle.cc
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.#include "simple_handler.h"#include <sstream>
#include <string>#include "include/base/cef_bind.h"
#include "include/cef_app.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"#include <fstream>
#include <Windows.h>
#include <tchar.h>namespace {SimpleHandler* g_instance = NULL;} // namespaceSimpleHandler::SimpleHandler(): is_closing_(false) {DCHECK(!g_instance);g_instance = this;
}SimpleHandler::~SimpleHandler() {g_instance = NULL;
}// static
SimpleHandler* SimpleHandler::GetInstance() {return g_instance;
}void SimpleHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {CEF_REQUIRE_UI_THREAD();// Add to the list of existing browsers.browser_list_.push_back(browser);browser_ = browser;
}bool SimpleHandler::DoClose(CefRefPtr<CefBrowser> browser) {CEF_REQUIRE_UI_THREAD();// Closing the main window requires special handling. See the DoClose()// documentation in the CEF header for a detailed destription of this// process.if (browser_list_.size() == 1) {// Set a flag to indicate that the window close should be allowed.is_closing_ = true;}// Allow the close. For windowed browsers this will result in the OS close// event being sent.return false;
}void SimpleHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {CEF_REQUIRE_UI_THREAD();// Remove from the list of existing browsers.BrowserList::iterator bit = browser_list_.begin();for (; bit != browser_list_.end(); ++bit) {if ((*bit)->IsSame(browser)) {browser_list_.erase(bit);break;}}if (browser_list_.empty()) {// All browser windows have closed. Quit the application message loop.CefQuitMessageLoop();}
}void SimpleHandler::OnLoadError(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,ErrorCode errorCode,const CefString& errorText,const CefString& failedUrl) {CEF_REQUIRE_UI_THREAD();// Don't display an error for downloaded files.if (errorCode == ERR_ABORTED)return;// Display a load error message.std::stringstream ss;ss << "<html><body bgcolor=\"white\">""<h2>Failed to load URL " << std::string(failedUrl) <<" with error " << std::string(errorText) << " (" << errorCode <<").</h2></body></html>";frame->LoadString(ss.str(), failedUrl);
}void SimpleHandler::CloseAllBrowsers(bool force_close) {if (!CefCurrentlyOn(TID_UI)) {// Execute on the UI thread.CefPostTask(TID_UI,base::Bind(&SimpleHandler::CloseAllBrowsers, this, force_close));return;}if (browser_list_.empty())return;BrowserList::const_iterator it = browser_list_.begin();for (; it != browser_list_.end(); ++it)(*it)->GetHost()->CloseBrowser(force_close);
}bool SimpleHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,CefProcessId source_process,CefRefPtr<CefProcessMessage> message)
{const std::string& messageName = message->GetName();if (messageName == "msg_from_render"){CefRefPtr<CefListValue> argList = message->GetArgumentList();CefRefPtr<CefProcessMessage> msg_back = CefProcessMessage::Create("func_back");msg_back->GetArgumentList()->SetString(0, token_);msg_back->GetArgumentList()->SetInt(1, accid_);browser->SendProcessMessage(PID_RENDERER, msg_back);return true;}return false;
}void SimpleHandler::OnBeforeContextMenu(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefContextMenuParams> params,CefRefPtr<CefMenuModel> model)
{//model->Clear(); // 禁止菜单只需这一句model->Remove(MENU_ID_PRINT);model->Remove(MENU_ID_VIEW_SOURCE);if ((params->GetTypeFlags() & (CM_TYPEFLAG_PAGE | CM_TYPEFLAG_FRAME)) != 0){if (model->GetCount() > 0) {model->RemoveAt(2);model->Remove(MENU_ID_BACK);model->Remove(MENU_ID_FORWARD);model->AddItem(MENU_ID_USER_SHOWDEVTOOLS,"developerTools");}}
}bool SimpleHandler::OnContextMenuCommand(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,CefRefPtr<CefContextMenuParams> params,int command_id,EventFlags event_flags)
{switch (command_id){case MENU_ID_USER_SHOWDEVTOOLS: {ShowDevelopTools(browser);return true;}break;}return false;
}void SimpleHandler::ShowDevelopTools(CefRefPtr<CefBrowser> browser)
{CefWindowInfo windowinfo;CefBrowserSettings settings;windowinfo.SetAsPopup(NULL, "DevTools");browser->GetHost()->ShowDevTools(windowinfo, this, settings, CefPoint());
}bool SimpleHandler::OnJSDialog(CefRefPtr<CefBrowser> browser,const CefString& origin_url,const CefString& accept_lang,JSDialogType dialog_type,const CefString& message_text,const CefString& default_prompt_text,CefRefPtr<CefJSDialogCallback> callback,bool& suppress_message)
{CEF_REQUIRE_UI_THREAD();std::wstring msg = message_text.ToWString();if (dialog_type == JSDIALOGTYPE_ALERT){::MessageBoxW(gameHwnd, (LPCWSTR)msg.c_str(), L"提示", MB_OK | MB_TOPMOST);suppress_message = true;return false;}else if(dialog_type == JSDIALOGTYPE_CONFIRM){int a = ::MessageBoxW(gameHwnd, (LPCWSTR)msg.c_str(), L"提示", MB_OKCANCEL | MB_TOPMOST);if (a == IDOK){callback->Continue(true, "");}else{callback->Continue(false, "");}suppress_message = false;return true;}return false;
}
simple_v8_handler.h
#pragma once#include <include/cef_v8.h>
class SimpleV8JsHandler :public CefV8Handler
{
public:SimpleV8JsHandler(void);virtual ~SimpleV8JsHandler(void);public:virtual bool Execute(const CefString& name,CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments,CefRefPtr<CefV8Value>& retval,CefString& exception) OVERRIDE;IMPLEMENT_REFCOUNTING(SimpleV8JsHandler);
};
simple_v8_handler.cc
#include "Simple_V8_JS_Handler.h"
#include <tchar.h>SimpleV8JsHandler::SimpleV8JsHandler(void)
{
}SimpleV8JsHandler::~SimpleV8JsHandler(void)
{
}bool SimpleV8JsHandler::Execute(const CefString& func_name,CefRefPtr<CefV8Value> object,const CefV8ValueList& arguments,CefRefPtr<CefV8Value>& retval,CefString& exception)
{if (func_name == _T("youjin")){if (arguments.size() == 0){CefRefPtr<CefFrame> frame = CefV8Context::GetCurrentContext()->GetBrowser()->GetMainFrame();retval = frame->GetV8Context()->GetGlobal()->GetValue("token");}return true;}else if (func_name == _T("mimang")){if (arguments.size() == 0){CefRefPtr<CefFrame> frame = CefV8Context::GetCurrentContext()->GetBrowser()->GetMainFrame();retval = frame->GetV8Context()->GetGlobal()->GetValue("accid");}return true;}else if (func_name == _T("dejin")){retval = CefV8Value::CreateString("To Hit My Stride");return true;}else if (func_name == _T("mubiao")){CefRefPtr<CefProcessMessage> msg = CefProcessMessage::Create("msg_from_render");CefRefPtr<CefListValue> args = msg->GetArgumentList();args->SetString(0, name);CefV8Context::GetCurrentContext()->GetBrowser()->SendProcessMessage(PID_BROWSER, msg);}return false;
}
zh2asHelp.cpp
#include "include/cef_app.h"
#include "include/internal/cef_win.h"
#include <windows.h>
#include "simple_app.h"
#include "include/cef_base.h"int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{CefMainArgs main_args(hInstance);CefRefPtr<SimpleApp> app(new SimpleApp);return CefExecuteProcess(main_args, app.get(), NULL);
}
至此,几乎所有的核心代码都准备好了,除了在游戏里创建我们的浏览器。
六、坑
因为我们PC游戏只创建一个windows窗口,游戏里面的dialog都是模拟出来的,并非windows窗口。要把cef直接显示游戏里,并且是可以拖动位置,我的做法是在游戏里面创建一个新的windows子窗口,同时在游戏里创建一个游戏窗口,把windows子窗口的位置绑定到新建的游戏窗口上,当拖动游戏窗口时,同时也移动windows子窗口,然后再把cef的浏览器内容显示在windows子窗口上。
这样就简单实现了在游戏里显示网页内容。但是我们要求网页是有账号登陆的,一个游戏id是绑定一个账号的,你在游戏内打开网页,是要求自动登陆的,也就是要把游戏里的token值和accid值发送给网页。而token值和accid值是在我们游戏exe进程里的,要知道cef是多进程架构的,Browser被定义为主进程,负责窗口管理,界面绘制和网络交互。Blink的渲染和Js的执行被放在一个独立的Render 进程中。我们的问题就变成了把数据从游戏进程传给Render进程,Render进程里的js调用我们给的数据。见下图
关于Cef的C++和Js通信的文章很多,附上一篇英文版:https://bitbucket.org/chromiumembedded/cef/wiki/JavaScriptIntegration.md;
对应的中文翻译版:https://www.cnblogs.com/guolixiucai/p/4943748.html
七、还有很多坑
像游戏里的焦点问题,键盘事件问题等等。
附上JS测试代码
<!DOCTYPE HTML>
<html><head><meta charset="utf-8" /><script type="text/javascript" >function youjin(){alert(zt2as.getToken());}function mimang(){alert(zt2as.getAccId());}function dejin(){alert(zt2as.getDevice());}function mubiao(){alert(zt2as.initData());alert(zt2as.getAccId());alert(zt2as.getToken());}</script></head><body style="width:100%;height:100%;background-color:white;"><p>回望2018 奔向2019</p><div ><button onclick="youjin();">getToken</button><button onclick="mimang();">getAccId</button><button onclick="dejin();">getDevice</button><button onclick="mubiao();">initData</button></div></body>
</html>
PC游戏中用CEF3制作内嵌浏览器相关推荐
- C++混合编程:QCefView、QWebKit,QWebengine、miniblink内嵌浏览器对比
随着前端技术的发展,PC端程序越来越倾向于做第三方嵌入浏览器的应用程序.为什么手机软件安装包越来越大,大概率是由于偷偷嵌入了一个浏览器内核,例如:腾讯视频.虾米音乐等主流PC端程序都是内嵌浏览器 ...
- C# WPF使用CefSharp客户端内嵌浏览器做一个开小差工具
前言 CefSharp是一个C#客户端内嵌入chromium开源项目浏览器的工具,方便在客户端中自然的访问网页内容,十分好用.当然,网上有很多使用CefSharp的教程了,怎么使用都很详尽.我这里只是 ...
- C# 内嵌 浏览器 CEF 、FireFox、Miniblink
项目 地址 https://github.com/TL-GuiZhou/windows.Browers Windows 平台下,C# 内嵌网页的 demo,包括当前流行的 CEF .FireFox. ...
- unity内嵌浏览器——UniWebView插件
这次突然被要求实现内嵌浏览器.在网上到处找资料参考,下面几个链接的内容感觉很实用 https://blog.csdn.net/qq_37310110/article/details/79761844 ...
- js 判断当前浏览器类型 判断当前是否是微信内嵌浏览器(是否是APP内嵌webView)
js的浏览器navigator头信息 myBrowserType(){var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串var isOp ...
- winform内嵌浏览器的2种实现方式
可使用WebBrowser或axWebBrowser实现winform窗体内嵌浏览器 一 使用axWebBrowser打开浏览器 1.新建个winform项目 2.添加axWebBrowser控件 打 ...
- Java实现内嵌浏览器
创建项目 ----> 导入需要的jar ----> 代码实现 需要的jar: https://pan.baidu.com/s/1MEZ1S0LnKSMGQm24QWgmCw 代码: ...
- vscode预览html插件,VSCode插件推荐-VSCode内嵌浏览器插件-Browser Preview
很多小伙伴在开发html页面的时候觉得很不方便.因为显示屏的大小是有限的,只能展示代码或者浏览器窗口的其中一种,哪怕用上了LiveServer,也需要一直切换页面,接下来小编带来的这款插件,可以在VS ...
- 【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码
目录 ★☆★ 写在前面 ★☆★ ★☆★ 本系列文章 ★☆★ ★☆★ 开源网址 ★☆★ 一.给 Swing 加上 Spring 0.前期努力 I. SpringBoot II. SpringMVC 1. ...
最新文章
- Visual C# 2010从入门到精通
- 关于保存状态的Fragment,setRetainInstance(true)
- H - Message Bomb Gym - 102798H
- dubbo单元测试调用_使用LocalTestServer对HTTP调用进行单元测试
- 使用OTA绕过AppStore安装App
- jquery实现图片放大效果
- 诗歌rails之Hacking ActiveRecord
- 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---31
- 数据分类是否一定要求样本均衡
- l455在线清零服务器,爱普生epson l455清零软件官方版
- LVDS,接口,时序讲解
- 循环神经网络--RNN GRU LSTM 对比分析
- 不叫 Andromeda?Google 新系统疑为 Fuchsia
- C#和JQ判断移动端还是PC端
- 正规的股票交易软件有哪些?
- 华为裁员1100人!任正非痛批管理层:这种领导鼠目寸光
- Come Clear - Hilary Duff(希拉瑞.达芙)
- 如何开展微博营销--开展微博营销的步骤
- linux支持xfs文件系统,LINUX下使用XFS文件系统
- STM32的8种IO口的模式
热门文章
- 利用ImageJ的3D Script插件重建盆腔三维模型
- MacOS 10.13.6 下装xcode 流程
- 公众号榜单 | 2020·8月公众号地区排行榜重磅发布
- 引擎之旅 Chapter.3 文件系统
- Android与Linux的区别
- 【速通指南】《信息资源管理》信息系统资源管理,第3章
- 【2021.3】 LeetCode每日一题复盘
- 乐视android版本怎么升级,乐视网android手机客户端升级
- ByteCTF2021安全范儿高校挑战赛线上Misc-《HearingNotBelieving》
- xshell上传文件出现:unknown error的解决办法