You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.16 16:15:41