VS2019 VC++ MFC CEF(Chrome)开发环境搭建及相关功能demo(附源码)

本文章主要介绍CEF如何作为一个控件,加在MFC的窗体中,并实现一些功能,如:打开指定网址、刷新、后退关闭子窗口或页签、关闭全部页签/子窗口和主窗体、浏览器界面自适应窗口大小等等,也会交代会遇到的一些坑的处理办法,最终会附上整个项目的源码。

一、下载/准备cef必须的dll和lib文件

1、下载并生成VS开发环境(详见文章:https://blog.csdn.net/yixiao0307/article/details/119908780),不在此赘述
2、创建最简单的MFC窗口项目(详见文章:https://blog.csdn.net/yixiao0307/article/details/119837989
3、在MFC项目源码目录(主窗口Dlg.app文件所在目录),创建文件夹cefLib,目录结构如下:
在这里插入图片描述
其中bin和lib目录里,都有x86/Debug 和 x86/Release,便于以后扩展x64
在这里插入图片描述
将网上下载下来的文件(cef_binary_92.0.27+g274abcf+chromium-92.0.4515.159_windows32,本文统称:cef binary文件包)中的以下文件和文件夹,

  • include、libcef_dll、tests、cef_paths.gypi、cef_paths2.gypi 复制到 libCef/src
    在这里插入图片描述
  • Debug 中的文件全部复制到 libCef/bin/x86/Debug/
    在这里插入图片描述
  • Release 中的文件全部复制到 libCef/bin/x86/Release/
    在这里插入图片描述
  • Debug/cef_sandbox.lib、Debug/libcef.lib 复制到 libCef/bin/x86/Debug/
    在这里插入图片描述
  • Release/cef_sandbox.lib、Release/libcef.lib 复制到 libCef/bin/x86/Release/
    在这里插入图片描述
  • 最后,再将第1点中(生成VS开发环境,https://blog.csdn.net/yixiao0307/article/details/119908780)用Debug/Release环境产生的 libcef_dll_wrapper.lib 和 libcef_dll_wrapper.pdb ,分别放到libCef/bin/x86/Debug/libCef/bin/x86/Release/,最终这两个目录效果如下,
    在这里插入图片描述
    在这里插入图片描述

二、设置项目属性(Debug/x86)

1、配置属性 - 高级 - MFC的使用 += “在静态库中使用MFC”
在这里插入图片描述
2、配置属性 - VC++目录 - 包含目录 += $(VC_SourcePath)libCEF\src
在这里插入图片描述
3、配置属性 - C/C++ - 预处理器 - 预处理器定义 += _HAS_ITERATOR_DEBUGGING=0
注意:Debug环境的“预处理器定义”中,必须增加此定义语句“_HAS_ITERATOR_DEBUGGING=0”。
在这里插入图片描述
再注意:如果不加此预处理器定义项,Debug编译时,会一直报错(要看cef的版本,有的版本不加也不会报错)
在这里插入图片描述

严重性 代码 说明 项目 文件 行 禁止显示状态
错误 LNK2038 检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项: 值“0”不匹配值“2”(CCefBrowserApp.obj 中) MFCCef D:\website\mfccef\MFCCef\libcef_dll_wrapper.lib(cef_logging.obj) 1

4、重点确认无误:配置属性 - C/C++ - 代码生成 - 运行库 = 多线程调试(/MTd)
在这里插入图片描述
5、配置属性 - 链接器 - 输入 - 附加依赖项 +=

libCEF\lib\x86\Debug\libcef.lib
libCEF\lib\x86\Debug\libcef_dll_wrapper.lib
libCEF\lib\x86\Debug\cef_sandbox.lib

在这里插入图片描述
此处有些人是直接写到主窗口Dlg.cpp文件的代码里的,链接器和代码调用只做一次就可以了,代码如下:

#ifdef _DEBUG
#include "libCEF\lib\x86\Debug\libcef.lib"
#include "libCEF\lib\x86\Debug\libcef_dll_wrapper.lib"
#include "libCEF\lib\x86\Debug\cef_sandbox.lib"
#endif

6、配置属性 - 清单工具 - 输入和输出 - 附加清单文件 += my.manifest
在这里插入图片描述
划重点】并在源码目录(主窗口Dlg.cpp文件所在目录),创建my.manifest的文件,文件内容如下:

<?xml version="1.0" encoding="utf-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">  
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">  
    <application> 
      <!--The ID below indicates application support for Windows 8.1 -->  
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>  
      <!-- 10.0 -->  
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> 
    </application> 
  </compatibility> 
</assembly>

【再划重点】如果以上这个文件不创建,Debug环境运行时,会出现白屏的情况,这个问题困扰了我1天!

三、设置项目属性(Release/x86)

1、配置属性 - 高级 - MFC的使用 += “在静态库中使用MFC”
在这里插入图片描述
2、配置属性 - VC++目录 - 包含目录 += $(VC_SourcePath)libCEF\src
在这里插入图片描述
4、重点确认无误:配置属性 - C/C++ - 代码生成 - 运行库 = 多线程(/MT)
在这里插入图片描述
5、配置属性 - 链接器 - 输入 - 附加依赖项 +=

libCEF\lib\x86\Release\libcef.lib
libCEF\lib\x86\Release\libcef_dll_wrapper.lib
libCEF\lib\x86\Release\cef_sandbox.lib

在这里插入图片描述

四、创建 CefBrowserApp 和 CefBrowserEventHandler 类

在项目的头文件目录上,点击鼠标右键,执行添加类 CCefBrowserApp
在这里插入图片描述
在这里插入图片描述
CCefBrowserApp.h

#pragma once
#include "include\cef_app.h"
class CCefBrowserApp :
    public CefApp, public CefBrowserProcessHandler
{
public:
    CCefBrowserApp(void);
    virtual ~CCefBrowserApp(void);

public:
    virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler();
    virtual void OnContextInitialized();
protected:
    //这句很重要,不写的话需要实现所有虚方法,写了就不用实现全部虚方法
    IMPLEMENT_REFCOUNTING(CCefBrowserApp);
};

CCefBrowserApp.cpp

#include "pch.h"
#include "CCefBrowserApp.h"

CCefBrowserApp::CCefBrowserApp(void) {}
CCefBrowserApp::~CCefBrowserApp(void) {}
CefRefPtr<CefBrowserProcessHandler> CCefBrowserApp::GetBrowserProcessHandler() {return this;}
void CCefBrowserApp::OnContextInitialized() {}

CCefBrowserEventHandler.h

#pragma once
#include "include/cef_client.h"
#include <list>
class CCefBrowserEventHandler:public CefClient,
    public CefDisplayHandler,
    public CefLifeSpanHandler,
    public CefLoadHandler
{
public:
    explicit CCefBrowserEventHandler(bool use_views); //这里的参数use_views可有可无,但需一致
    ~CCefBrowserEventHandler();
    static CCefBrowserEventHandler* GetInstance();
    virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() OVERRIDE { return this; }
    virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE { return this; }
    virtual CefRefPtr<CefLoadHandler> GetLoadHandler() OVERRIDE { return this; }
    void CloseAllBrowsers(bool force_close);
    bool IsClosing() const { return is_closing_; }
    bool CCefBrowserEventHandler::DoClose(CefRefPtr<CefBrowser> browser);
    virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) OVERRIDE;
    virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser) OVERRIDE;
    static bool IsChromeRuntimeEnabled();
private:
    const bool use_views_;
    typedef std::list<CefRefPtr<CefBrowser>> BrowserList;
    BrowserList browser_list_;
    bool is_closing_;
    //这句很重要,不写的话需要实现所有虚方法,写了就不用实现全部虚方法
    IMPLEMENT_REFCOUNTING(CCefBrowserEventHandler);
};

CCefBrowserEventHandler.cpp

#include "pch.h"
#include "CCefBrowserEventHandler.h"

#include "include/base/cef_bind.h"
#include "include/cef_app.h"
#include "include/cef_parser.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"

CCefBrowserEventHandler* g_instance = nullptr;
CCefBrowserEventHandler::CCefBrowserEventHandler(bool use_views):use_views_(use_views),is_closing_(false){
	DCHECK(!g_instance);
	g_instance = this;
}

CCefBrowserEventHandler::~CCefBrowserEventHandler() {
	g_instance = nullptr;
}
CCefBrowserEventHandler* CCefBrowserEventHandler::GetInstance() {
	return g_instance;
}

bool CCefBrowserEventHandler::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 CCefBrowserEventHandler::CloseAllBrowsers(bool force_close) {
	if (!CefCurrentlyOn(TID_UI)) {
		// Execute on the UI thread.
		CefPostTask(TID_UI, base::Bind(&CCefBrowserEventHandler::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);
}

void CCefBrowserEventHandler::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 CCefBrowserEventHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
	CEF_REQUIRE_UI_THREAD();

	// Add to the list of existing browsers.
	browser_list_.push_back(browser);
}

bool CCefBrowserEventHandler::IsChromeRuntimeEnabled() {
	static int value = -1;
	if (value == -1) {
		CefRefPtr<CefCommandLine> command_line =
			CefCommandLine::GetGlobalCommandLine();
		value = command_line->HasSwitch("enable-chrome-runtime") ? 1 : 0;
	}
	return value == 1;
}

有了以上两个类,才能在MFC项目中对CEF进行初始化和调用,这两个类我是从cef binary文件包的这两个类里摘抄过来的。
在这里插入图片描述

五、初始化Cef

1、增加引用

#include "include/cef_client.h"
#include "include/cef_app.h"
#include "CCefBrowserApp.h"

2、在项目主类的InitInstance()方法的顶部,增加cef初始化代码

void* sandbox_info = NULL;
CefMainArgs main_args(m_hInstance);
CefRefPtr<CCefBrowserApp> app(new CCefBrowserApp);

int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);
if (exit_code >= 0)
{
	return exit_code;
}
CefSettings settings;
CefSettingsTraits::init(&settings);
settings.no_sandbox = true;
settings.multi_threaded_message_loop = true;
settings.ignore_certificate_errors = true;
settings.command_line_args_disabled = true;
CefInitialize(main_args, settings, app.get(), sandbox_info);

在这里插入图片描述
3、在InitInstance()方法的底部,返回语句前,增加代码:

//关闭CEF
CefQuitMessageLoop();
CefShutdown();

在这里插入图片描述

六、窗体中调用Cef

1、增加引用

#include <include/internal/cef_win.h>
#include <include/internal/cef_ptr.h>
#include <include/cef_browser.h>
#include <include/cef_app.h>
#include "CCefBrowserEventHandler.h"
#include "include/views/cef_browser_view.h"

2、将窗体中的按钮和字删除,新拖入一个Picture Control控件,并分别设置属性(选中控件,右键-属性)为:

  • ID = IDC_STATIC_BODY
  • 类型 = Rectangle
  • 可见 = False
    在这里插入图片描述
    预览效果:
    在这里插入图片描述
    3、在主窗体的OnInitDialog()方法底部,返回语句上方,增加代码:
//没啥用抄来的
const bool use_views = true;
//占位子用的控件大小描述信息(不显示
CRect rtBody;
//chrome控件信息类
CefWindowInfo cefWindowInfo;

//获取窗口占位控件的坐标和大小
GetDlgItem(IDC_STATIC_BODY)->GetWindowRect(&rtBody);
//减去边框和标题栏的宽度
RECT rcBody = { rtBody.left - 8, rtBody.top - 31, rtBody.Width(), rtBody.Height() };
//将chrome作为控件加载到MFC窗体中
cefWindowInfo.SetAsChild(GetSafeHwnd(), rcBody);
CefBrowserSettings browser_settings;
// CMFCCefDlg 对话框
CefRefPtr<CCefBrowserEventHandler> cef_handler;
cef_handler = new CCefBrowserEventHandler(use_views);
CefBrowserHost::CreateBrowser(cefWindowInfo, cef_handler, "https://blog.csdn.net", browser_settings, nullptr, nullptr);

在这里插入图片描述

七、运行效果

在这里插入图片描述
看到CSDN显示在自己做的窗口中,太漂亮了,美中不足的是,Debug中结束程序运行后,总是会提示“未加载libcef.dll.pdb”的错误(如下图)(暂时无解),但是Release环境中一切正常。
在这里插入图片描述

八、功能应用

以下功能实现详见:https://blog.csdn.net/yixiao0307/article/details/119964356
1、打开指定网址
2、刷新
3、后退
4、调用本地Vue
5、自动登录(自动填充账号和密码)
6、关闭子窗口
7、HTML/JS中关闭主窗体
8、MFC发送消息给CEF中的HTML/JS
9、浏览器自适应窗体大小

九、源码

源码版本库地址:https://gitee.com/kefong/mfccef

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一笑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值