CEF极简示例:如何无渲染请求HTML页面并存入std::string
核心问题
你当前的代码仅完成了CEF多进程架构的基础启动逻辑,缺少浏览器实例创建、生命周期回调、异步资源获取三个核心模块。直接调用LoadURL无效的原因有两个:一是调用时机过早,CEF浏览器上下文尚未完成初始化;二是你没有获取到有效的CefBrowser实例,未初始化实例调用方法自然无法运行。
要实现无窗口加载页面、获取HTML存入std::string的需求,不需要创建任何可见渲染窗口,开启CEF的无窗口渲染模式即可,具体实现如下。
完整实现代码
#include <iostream> #include <string> #include <windows.h> #include <include/cef_app.h> #include <include/cef_client.h> #include <include/cef_render_handler.h> #include <include/cef_string_visitor.h> #include <include/wrapper/cef_helpers.h> // 全局变量存储获取到的HTML,正式项目建议用成员变量替代全局变量 std::string g_html_content; // 1. 字符串访问器:用于异步接收页面源码 class SourceVisitor : public CefStringVisitor { public: SourceVisitor() = default; void Visit(const CefString& string) override { CEF_REQUIRE_UI_THREAD(); // 将CEF字符串转成std::string存储 g_html_content = string.ToString(); // 拿到内容后退出消息循环,结束程序 CefQuitMessageLoop(); } private: IMPLEMENT_REFCOUNTING(SourceVisitor); DISALLOW_COPY_AND_ASSIGN(SourceVisitor); }; // 2. 无窗口渲染Handler:不启用可见窗口时,需要提供空实现的渲染Handler class MinimalRenderHandler : public CefRenderHandler { public: MinimalRenderHandler() = default; void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) override { // 给默认1x1尺寸即可,不需要实际渲染 rect.Set(0, 0, 1, 1); } void OnPaint(CefRefPtr<CefBrowser> browser, PaintElementType type, const RectList& dirtyRects, const void* buffer, int width, int height) override { // 空实现,不需要处理渲染数据 } private: IMPLEMENT_REFCOUNTING(MinimalRenderHandler); DISALLOW_COPY_AND_ASSIGN(MinimalRenderHandler); }; // 3. 自定义Client:处理浏览器加载事件 class MinimalCefClient : public CefClient, public CefLoadHandler { public: MinimalCefClient() : render_handler_(new MinimalRenderHandler()) {} CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; } CefRefPtr<CefRenderHandler> GetRenderHandler() override { return render_handler_; } // 页面加载完成回调 void OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, int httpStatusCode) override { CEF_REQUIRE_UI_THREAD(); // 只处理主框架的加载完成事件,忽略iframe if (frame->IsMain()) { // 异步获取主框架源码,结果通过SourceVisitor返回 frame->GetSource(new SourceVisitor()); } } private: CefRefPtr<MinimalRenderHandler> render_handler_; IMPLEMENT_REFCOUNTING(MinimalCefClient); DISALLOW_COPY_AND_ASSIGN(MinimalCefClient); }; // 4. 自定义App:处理浏览器进程初始化事件 class MinimalCefApp : public CefApp, public CefBrowserProcessHandler { public: MinimalCefApp() = default; CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; } // 浏览器上下文初始化完成回调,此时可以安全创建浏览器实例 void OnContextInitialized() override { CEF_REQUIRE_UI_THREAD(); CefWindowInfo window_info; // 配置无窗口模式,不创建原生桌面窗口 window_info.SetAsWindowless(nullptr); CefBrowserSettings browser_settings; // 替换为你要请求的目标URL std::string target_url = "https://example.com"; // 创建浏览器实例 CefBrowserHost::CreateBrowser( window_info, new MinimalCefClient(), target_url, browser_settings, nullptr, nullptr ); } private: IMPLEMENT_REFCOUNTING(MinimalCefApp); DISALLOW_COPY_AND_ASSIGN(MinimalCefApp); }; int main() { HINSTANCE hInstance = GetModuleHandleA(nullptr); CefMainArgs main_args(hInstance); // 处理CEF子进程(渲染进程、GPU进程等)启动逻辑 int exit_code = CefExecuteProcess(main_args, nullptr, nullptr); if (exit_code >= 0) { return exit_code; } CefSettings settings; settings = {0}; // 开启无窗口渲染模式 settings.windowless_rendering_enabled = true; // 测试阶段关闭沙箱,正式发布可按需开启,需链接对应沙箱库 settings.no_sandbox = true; // 若CEF资源文件(locales、icudtl.dat等)不在exe同目录,需手动指定路径 // settings.resources_dir_path = "你的CEF资源目录绝对路径"; // settings.locales_dir_path = "你的CEF locales目录绝对路径"; // 初始化CEF,传入自定义App实例 CefInitialize(main_args, settings, new MinimalCefApp(), nullptr); // 启动CEF消息循环 CefRunMessageLoop(); // 消息循环退出后关闭CEF CefShutdown(); // 此时g_html_content已存储目标页面完整HTML std::cout << "获取到HTML长度:" << g_html_content.size() << std::endl; std::cout << "HTML前100字符:" << g_html_content.substr(0, 100) << std::endl; return 0; }
关键注意点
CefInitialize调用后不会立刻完成浏览器上下文初始化,所有创建浏览器、加载URL的操作必须放在OnContextInitialized回调中执行,提前调用必然失败。- CEF所有IO操作、浏览器操作都是异步设计,
GetSource不会直接返回字符串,必须通过CefStringVisitor的回调接收结果,不要写同步等待逻辑阻塞CEF UI线程。 - 无窗口模式必须实现一个最简的
CefRenderHandler,哪怕完全不需要渲染内容,否则创建浏览器会失败。GetViewRect返回任意非0尺寸即可,OnPaint留空不用处理渲染数据。 OnLoadEnd回调会在页面所有子资源(图片、脚本、样式)加载完成后触发,如果只需要HTML不需要等子资源加载,可以替换成OnLoadingStateChange回调,在isLoading == false且当前框架为主框架时获取源码。- 代码用全局变量存HTML是简化示例,正式使用建议把存储逻辑封装在Client类的成员变量里,避免全局变量污染。
内容的提问来源于stack exchange,提问作者Optimus1
相关产品推荐
相关产品推荐

