因为项目需要,需要将游戏手机助手中的朋友圈给移植到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组件可能显示不正确。)
  • 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制作内嵌浏览器相关推荐

  1. C++混合编程:QCefView、QWebKit,QWebengine、miniblink内嵌浏览器对比

       随着前端技术的发展,PC端程序越来越倾向于做第三方嵌入浏览器的应用程序.为什么手机软件安装包越来越大,大概率是由于偷偷嵌入了一个浏览器内核,例如:腾讯视频.虾米音乐等主流PC端程序都是内嵌浏览器 ...

  2. C# WPF使用CefSharp客户端内嵌浏览器做一个开小差工具

    前言 CefSharp是一个C#客户端内嵌入chromium开源项目浏览器的工具,方便在客户端中自然的访问网页内容,十分好用.当然,网上有很多使用CefSharp的教程了,怎么使用都很详尽.我这里只是 ...

  3. C# 内嵌 浏览器 CEF 、FireFox、Miniblink

    项目 地址  https://github.com/TL-GuiZhou/windows.Browers Windows 平台下,C# 内嵌网页的 demo,包括当前流行的 CEF .FireFox. ...

  4. unity内嵌浏览器——UniWebView插件

    这次突然被要求实现内嵌浏览器.在网上到处找资料参考,下面几个链接的内容感觉很实用 https://blog.csdn.net/qq_37310110/article/details/79761844 ...

  5. js 判断当前浏览器类型 判断当前是否是微信内嵌浏览器(是否是APP内嵌webView)

    js的浏览器navigator头信息 myBrowserType(){var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串var isOp ...

  6. winform内嵌浏览器的2种实现方式

    可使用WebBrowser或axWebBrowser实现winform窗体内嵌浏览器 一 使用axWebBrowser打开浏览器 1.新建个winform项目 2.添加axWebBrowser控件 打 ...

  7. Java实现内嵌浏览器

    创建项目 ---->   导入需要的jar ---->  代码实现 需要的jar: https://pan.baidu.com/s/1MEZ1S0LnKSMGQm24QWgmCw 代码: ...

  8. vscode预览html插件,VSCode插件推荐-VSCode内嵌浏览器插件-Browser Preview

    很多小伙伴在开发html页面的时候觉得很不方便.因为显示屏的大小是有限的,只能展示代码或者浏览器窗口的其中一种,哪怕用上了LiveServer,也需要一直切换页面,接下来小编带来的这款插件,可以在VS ...

  9. 【java】本地客户端内嵌浏览器3 - Swing 使用 Spring 框架 + 打包项目 + 转exe + 源码

    目录 ★☆★ 写在前面 ★☆★ ★☆★ 本系列文章 ★☆★ ★☆★ 开源网址 ★☆★ 一.给 Swing 加上 Spring 0.前期努力 I. SpringBoot II. SpringMVC 1. ...

最新文章

  1. Visual C# 2010从入门到精通
  2. 关于保存状态的Fragment,setRetainInstance(true)
  3. H - Message Bomb Gym - 102798H
  4. dubbo单元测试调用_使用LocalTestServer对HTTP调用进行单元测试
  5. 使用OTA绕过AppStore安装App
  6. jquery实现图片放大效果
  7. 诗歌rails之Hacking ActiveRecord
  8. 《Linux命令行与shell脚本编程大全 第3版》Linux命令行---31
  9. 数据分类是否一定要求样本均衡
  10. l455在线清零服务器,爱普生epson l455清零软件官方版
  11. LVDS,接口,时序讲解
  12. 循环神经网络--RNN GRU LSTM 对比分析
  13. 不叫 Andromeda?Google 新系统疑为 Fuchsia
  14. C#和JQ判断移动端还是PC端
  15. 正规的股票交易软件有哪些?
  16. 华为裁员1100人!任正非痛批管理层:这种领导鼠目寸光
  17. Come Clear - Hilary Duff(希拉瑞.达芙)
  18. 如何开展微博营销--开展微博营销的步骤
  19. linux支持xfs文件系统,LINUX下使用XFS文件系统
  20. STM32的8种IO口的模式

热门文章

  1. 利用ImageJ的3D Script插件重建盆腔三维模型
  2. MacOS 10.13.6 下装xcode 流程
  3. 公众号榜单 | 2020·8月公众号地区排行榜重磅发布
  4. 引擎之旅 Chapter.3 文件系统
  5. Android与Linux的区别
  6. 【速通指南】《信息资源管理》信息系统资源管理,第3章
  7. 【2021.3】 LeetCode每日一题复盘
  8. 乐视android版本怎么升级,乐视网android手机客户端升级
  9. ByteCTF2021安全范儿高校挑战赛线上Misc-《HearingNotBelieving》
  10. xshell上传文件出现:unknown error的解决办法