资讯动态

VS2010 + CEF3 90.6.0 + MFC 嵌入式浏览器实战指南

发布时间:2026/10/15 23:56:34 来源:尧图企业网站定制
简介本资源是一套基于Visual Studio 2010与CEF3 v90.6.0的MFC嵌入式浏览器完整工程实现面向C中高级开发者解决在传统桌面应用中集成现代Web渲染引擎并实现JS/C双向通信的核心难题。适用于需在MFC对话框或视图中嵌入HTML5界面、构建混合架构客户端如配置面板、帮助系统、内嵌仪表盘的工业软件、测试工具及企业级桌面应用开发场景。压缩包共629个文件含269个头文件h、168个资源包pak、28个动态链接库dll、9个源文件cpp及配套lib、obj、manifest等构建必需文件总大小457.22MB结构完整覆盖CEF初始化、窗口绑定、消息循环同步与JS调用桥接等关键模块。已有1556人学习下载提供可直接编译运行的BWQ.sln解决方案含Debug/Release输出、预编译头缓存及V8上下文快照等实战细节助开发者快速复现嵌入逻辑、理解CEF3与MFC深度集成的技术路径。1. 为什么在 VS2010 环境下硬刚 CEF3 90.6.0 MFC 嵌入至今还有人踩坑不喊疼这不是一个“过时技术怀旧帖”而是某高校实验室在维护一套运行十年的工业检测上位机时的真实现场主界面是 MFC 对话框但报表预览、设备拓扑图、参数配置页必须用 Web 技术实现——HTML5 Canvas 绘图、ECharts 动态图表、Vue 表单校验全靠嵌入式浏览器撑住。他们试过 WebView2但客户产线环境锁死 Windows 7 SP1 VS2010 编译链连 C11 都得手动关掉也试过 Electron结果内存暴涨到 1.2GB工控机风扇狂转。最终回退到 CEF3 90.6.0 —— 它是最后一个官方提供完整 VS2010 x86/x64 预编译二进制包的 CEF 主版本2021 年 3 月发布且对 MFC 的 HWND 嵌入支持最稳定。本文讲的就是如何用这套“古董组合”把 CEF3 真正焊进 MFC 对话框让 JS 调用 C 不是回调黑匣子而是可断点、可日志、可传结构体的双向通道。适合正在啃老项目、没得选但必须稳的嵌入式 GUI 工程师。2. 从零拉起 VS2010 CEF3 90.6.0 MFC 工程不是解压就能跑2.1 下载与目录结构别碰官网源码直接用预编译包CEF 官网不再提供 90.6.0 的下载入口但其 GitHub Release 页面仍存档着cef_binary_90.6.0g5e1b5d2chromium-90.0.4430.212_windows32和_windows64两个 ZIP 包注意后缀含windows32/windows64不是win32。这两个包是唯一能绕过 VS2010 不支持 C17 的关键——它们由 CEF 官方用 VS2010 编译完成包含全部.lib、.dll、.h及示例工程。操作步骤访问https://github.com/chromiumembedded/cef/releases/tag/90.6.0%2Bg5e1b5d2GitHub 地址需自行拼接不提供跳转下载cef_binary_90.6.0g5e1b5d2chromium-90.0.4430.212_windows32.zip32 位或..._windows64.zip64 位解压到D:\cef3\90.6.0\确保路径不含中文、空格、长路径VS2010 对路径长度极度敏感进入D:\cef3\90.6.0\cef_binary_90.6.0g5e1b5d2chromium-90.0.4430.212_windows32\你会看到include/、lib/、Resources/、tests/四个核心目录提示不要尝试用 CEF 的automate-git.py自己编译——它依赖 Python 2.7 depot_tools VS2019和 VS2010 环境完全冲突。预编译包是唯一可行路径。2.2 创建 MFC 对话框工程并配置 CEF3 依赖新建一个MFC Application类型选Dialog based勾选Use Unicode libraries必须CEF3 90.x 全部使用 Unicode 字符集。工程名设为CefInMfcDemo路径设为D:\projects\CefInMfcDemo\同样避开中文与空格。接下来是 VS2010 特有的四步配置缺一不可# 步骤 1包含目录Project Properties → Configuration Properties → C/C → General → Additional Include Directories D:\cef3\90.6.0\cef_binary_90.6.0g5e1b5d2chromium-90.0.4430.212_windows32\include D:\cef3\90.60\cef_binary_90.6.0g5e1b5d2chromium-90.0.4430.212_windows32\include\capi D:\cef3\90.6.0\cef_binary_90.6.0g5e1b5d2chromium-90.0.4430.212_windows32\include\wrapper# 步骤 2库目录Configuration Properties → Linker → General → Additional Library Directories D:\cef3\90.6.0\cef_binary_90.6.0g5e1b5d2chromium-90.0.4430.212_windows32\lib# 步骤 3附加依赖项Configuration Properties → Linker → Input → Additional Dependencies libcef.lib# 步骤 4禁用增量链接Configuration Properties → Linker → General → Enable Incremental Linking → No # 原因VS2010 增量链接与 CEF3 的静态初始化顺序冲突会导致 CEF_Init() 崩溃注意libcef.lib是导入库实际运行时必须有libcef.dll在可执行目录下。不要试图用#pragma comment(lib, libcef.lib)替代项目设置——VS2010 的链接器对 pragma 的解析不稳定。2.3 初始化 CEF在 CWinApp 派生类中埋入启动逻辑在CefInMfcDemoApp.h中添加头文件和成员变量// CefInMfcDemoApp.h #pragma once #include include/cef_app.h #include include/wrapper/cef_helpers.h class CCefInMfcDemoApp : public CWinApp { public: virtual BOOL InitInstance() override; virtual int ExitInstance() override; private: // CEF 初始化需要的全局对象 CefRefPtrCefApp m_pCefApp; };在CefInMfcDemoApp.cpp中实现初始化// CefInMfcDemoApp.cpp #include stdafx.h #include CefInMfcDemoApp.h #include CefInMfcDemoDlg.h #include include/cef_app.h #include include/cef_base.h #include include/wrapper/cef_helpers.h // 必须定义此宏否则 CEF 内部会报错 IMPLEMENT_CEF_APP(); BOOL CCefInMfcDemoApp::InitInstance() { // Step 1: 初始化 CEF必须在 CWinApp::InitInstance() 之前调用 CefMainArgs main_args(m_hInstance); CefSettings settings; // 关键配置关闭多进程沙箱MFC 单进程环境必须关 settings.no_sandbox true; // 设置资源路径指向 CEF 包里的 Resources 目录 CefString(settings.resources_dir_path).FromASCII(D:/cef3/90.6.0/cef_binary_90.6.0g5e1b5d2chromium-90.0.4430.212_windows32/Resources); // 设置 locales 路径同上 CefString(settings.locales_dir_path).FromASCII(D:/cef3/90.6.0/cef_binary_90.6.0g5e1b5d2chromium-90.0.4430.212_windows32/Resources); // 启动 CEF返回 false 表示失败应退出 if (!CefInitialize(main_args, settings, nullptr, nullptr)) { AfxMessageBox(_T(CEF 初始化失败请检查 Resources 路径是否正确)); return FALSE; } // Step 2: 创建主对话框 CefInMfcDemoDlg dlg; m_pMainWnd dlg; INT_PTR nResponse dlg.DoModal(); if (nResponse -1) { AfxMessageBox(_T(对话框创建失败)); return FALSE; } return TRUE; } int CCefInMfcDemoApp::ExitInstance() { // 必须调用 CefShutdown()否则程序退出时崩溃 CefShutdown(); return CWinApp::ExitInstance(); }逻辑说明CefInitialize()是 CEF 的心脏它必须在任何 CEF 对象创建前调用且必须在DoModal()之前。settings.no_sandbox true是 MFC 嵌入的铁律——VS2010 编译的 CEF3 90.6.0 在启用沙箱时会因缺少ntdll.dll导出符号而卡死。resources_dir_path必须绝对路径相对路径在 MFC 对话框中会失效。3. 在 MFC 对话框中创建 CEF 浏览器窗口HWND 嵌入不是 setParent 那么简单3.1 获取对话框客户区 HWND 并计算浏览器尺寸在CefInMfcDemoDlg.h中声明浏览器相关成员// CefInMfcDemoDlg.h #pragma once #include afxwin.h #include include/cef_client.h #include include/cef_browser.h #include include/cef_frame.h #include include/wrapper/cef_helpers.h class CCefInMfcDemoDlg : public CDialog { public: CCefInMfcDemoDlg(CWnd* pParent nullptr); // 标准构造函数 enum { IDD IDD_CEFINMFCDIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX) override; // DDX/DDV 支持 private: CefRefPtrCefClient m_pClient; CefRefPtrCefBrowser m_pBrowser; CRect m_browserRect; // 浏览器客户区矩形相对于对话框 // CEF 回调代理 class ClientHandler : public CefClient, public CefDisplayHandler, public CefLoadHandler { public: explicit ClientHandler(CCefInMfcDemoDlg* pDlg) : m_pDlg(pDlg) {} virtual CefRefPtrCefDisplayHandler GetDisplayHandler() override { return this; } virtual CefRefPtrCefLoadHandler GetLoadHandler() override { return this; } // ... 其他纯虚函数重写见 3.2 节 private: CCefInMfcDemoDlg* m_pDlg; IMPLEMENT_REFCOUNTING(ClientHandler); }; };在OnInitDialog()中计算浏览器区域并预留空间// CefInMfcDemoDlg.cpp BOOL CCefInMfcDemoDlg::OnInitDialog() { CDialog::OnInitDialog(); // Step 1: 获取对话框客户区大小 CRect clientRect; GetClientRect(clientRect); // Step 2: 预留顶部状态栏24px、底部按钮区60px中间给浏览器 m_browserRect clientRect; m_browserRect.top 24; // 状态栏高度 m_browserRect.bottom - 60; // 按钮区高度 // Step 3: 创建 CEF Client 实例必须在创建浏览器前 m_pClient new ClientHandler(this); // Step 4: 创建浏览器关键 CefWindowInfo windowInfo; CefRefPtrCefDictionaryValue extra_info nullptr; // 设置窗口信息指定父窗口句柄并禁用原生窗口边框 windowInfo.SetAsChild(GetSafeHwnd(), m_browserRect); // 创建浏览器配置 CefBrowserSettings browserSettings; browserSettings.javascript_access_clipboard STATE_ENABLED; browserSettings.web_security STATE_DISABLED; // 开发期可关上线前务必开 // 启动浏览器URL 为本地 HTML 文件 CefString url(Lfile:///D:/projects/CefInMfcDemo/res/index.html); // 注意最后一个参数为 false表示不显示地址栏等 Chrome UI CefBrowserHost::CreateBrowser(windowInfo, m_pClient.get(), url, browserSettings, extra_info); return TRUE; }参数说明windowInfo.SetAsChild()是 MFC 嵌入的核心 API它告诉 CEF “把这个浏览器画在我这个 HWND 里面”而不是新建一个顶层窗口。m_browserRect必须是相对于GetSafeHwnd()的客户区坐标不能是屏幕坐标。web_security STATE_DISABLED仅用于开发调试允许file://协议加载本地 JS正式版必须设为STATE_ENABLED并改用http://127.0.0.1服务。3.2 实现 CefClient 子类接管加载、显示、JS 绑定全流程在CefInMfcDemoDlg.cpp中补全ClientHandler类// CefInMfcDemoDlg.cpp续 CCefInMfcDemoDlg::ClientHandler::ClientHandler(CCefInMfcDemoDlg* pDlg) : m_pDlg(pDlg) {} void CCefInMfcDemoDlg::ClientHandler::OnAddressChange(CefRefPtrCefBrowser browser, CefRefPtrCefFrame frame, const CefString url) { // 可在此更新 MFC 状态栏显示当前 URL if (frame-IsMain()) { CString strUrl url.ToString().c_str(); // m_pDlg-UpdateStatusBar(strUrl); // 假设你有状态栏更新函数 } } void CCefInMfcDemoDlg::ClientHandler::OnTitleChange(CefRefPtrCefBrowser browser, const CefString title) { // 可在此设置对话框标题 if (browser-GetIdentifier() browser-GetHost()-GetIdentifier()) { CString strTitle title.ToString().c_str(); // m_pDlg-SetWindowText(strTitle); } } void CCefInMfcDemoDlg::ClientHandler::OnLoadingStateChange(CefRefPtrCefBrowser browser, bool isLoading, bool canGoBack, bool canGoForward) { if (!isLoading browser-GetMainFrame()) { // 页面加载完成此时可安全执行 JS 注入 CefRefPtrCefFrame frame browser-GetMainFrame(); frame-ExecuteJavaScript(Lconsole.log(页面加载完成准备注入 JSBridge);, L, 0); } } // 必须重写此函数否则 JS 调用 C 会失败 bool CCefInMfcDemoDlg::ClientHandler::OnProcessMessageReceived( CefRefPtrCefBrowser browser, CefRefPtrCefFrame frame, CefProcessId source_process, CefRefPtrCefProcessMessage message) { // 检查消息名称我们约定 JS 发送的消息名为 native_call if (message-GetName().ToString() native_call) { // 从消息中提取参数 CefRefPtrCefListValue args message-GetArgumentList(); if (args.get() args-GetSize() 2) { CefString funcName args-GetString(0); CefString payload args-GetString(1); // 在 MFC 主线程中执行回调避免跨线程调用 UI m_pDlg-PostMessage(WM_USER 100, (WPARAM)funcName.c_str(), (LPARAM)payload.c_str()); } return true; } return false; }逻辑说明OnProcessMessageReceived()是 JS 调用 C 的唯一入口。JS 侧通过cefQuery({request: xxx, onSuccess: ...})或window.cefQuery(...)发送消息CEF 将其序列化为CefProcessMessage。这里我们约定消息名为native_call第一个参数是函数名第二个是 JSON 字符串载荷。PostMessage()将控制权交还 MFC 线程避免在 CEF 线程中直接操作 MFC 控件导致崩溃。4. JS 与 C 双向通信从cefQuery到PostMessage的完整链路4.1 在 JS 中封装cefQuery调用桥接层在D:\projects\CefInMfcDemo\res\index.html中引入 CEF 提供的 JS 库!DOCTYPE html html head meta charsetutf-8 titleCEF in MFC Demo/title !-- CEF 自带的 cefQuery.js必须放在 Resources 目录下 -- script srccef://cefquery.js/script /head body h1JS 调用 C 示例/h1 button onclickcallCpp()点击调用 C 函数/button div idresult等待结果.../div script // 全局 cefQuery 封装 function cefCall(funcName, payload) { return new Promise((resolve, reject) { window.cefQuery({ request: JSON.stringify({ func: funcName, data: payload }), onSuccess: (response) { try { resolve(JSON.parse(response)); } catch (e) { reject(e); } }, onFailure: (errorCode, errorMessage) { reject({ code: errorCode, msg: errorMessage }); } }); }); } async function callCpp() { const resultDiv document.getElementById(result); try { const res await cefCall(get_system_info, { app_version: 1.0.0 }); resultDiv.innerHTML C 返回: JSON.stringify(res); } catch (err) { resultDiv.innerHTML 调用失败: JSON.stringify(err); } } /script /body /html注意cef://cefquery.js是 CEF 内置协议无需本地文件。它由 CEF 在file://页面中自动注入提供window.cefQuery接口。request字段必须是字符串onSuccess回调接收的response也是字符串需手动JSON.parse()。4.2 在 MFC 中处理 JS 请求并返回响应在CefInMfcDemoDlg.h中声明自定义消息和处理函数// CefInMfcDemoDlg.h续 #define WM_CEF_NATIVE_CALL (WM_USER 100) class CCefInMfcDemoDlg : public CDialog { // ... 前面已有代码 protected: afx_msg LRESULT OnCefNativeCall(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() private: // JS 请求处理器映射表 std::mapCString, std::functionCString(const CString) m_jsHandlers; };在CefInMfcDemoDlg.cpp中注册消息映射并实现分发// CefInMfcDemoDlg.cpp续 BEGIN_MESSAGE_MAP(CCefInMfcDemoDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_MESSAGE(WM_CEF_NATIVE_CALL, CCefInMfcDemoDlg::OnCefNativeCall) END_MESSAGE_MAP() // 在构造函数中注册处理器 CCefInMfcDemoDlg::CCefInMfcDemoDlg(CWnd* pParent /*nullptr*/) : CDialog(IDD_CEFINMFCDIALOG, pParent) { // 注册 JS 可调用的 C 函数 m_jsHandlers[_T(get_system_info)] [this](const CString jsonPayload) - CString { // 解析 JS 传来的 JSON Json::Value root; Json::Reader reader; if (!reader.parse(CT2CA(jsonPayload), root)) { return _T({\error\:\JSON parse failed\}); } // 构造返回数据 Json::Value response; response[status] success; response[os] Windows; response[app_version] root[app_version].asString(); response[timestamp] (long long)time(nullptr); Json::FastWriter writer; return CA2CT(writer.write(response).c_str()); }; m_jsHandlers[_T(save_config)] [this](const CString jsonPayload) - CString { // 模拟保存配置到 ini 文件 WritePrivateProfileString(_T(Config), _T(last_update), CT2CA(CString(_T(2024-06-15))), CT2CA(_T(config.ini))); return _T({\saved\:true}); }; } LRESULT CCefInMfcDemoDlg::OnCefNativeCall(WPARAM wParam, LPARAM lParam) { CString funcName (LPCTSTR)wParam; CString payload (LPCTSTR)lParam; // 查找处理器 auto it m_jsHandlers.find(funcName); if (it ! m_jsHandlers.end()) { try { CString response it-second(payload); // 将响应发回 JS必须在 CEF 线程中调用 if (m_pBrowser.get()) { CefRefPtrCefFrame frame m_pBrowser-GetMainFrame(); if (frame.get()) { // 构造响应消息 CefRefPtrCefProcessMessage msg CefProcessMessageCreate(native_response); CefRefPtrCefListValue args msg-GetArgumentList(); args-SetString(0, funcName); args-SetString(1, response); // 发送响应注意必须在 CEF UI 线程中 frame-SendProcessMessage(PID_BROWSER, msg); } } } catch (...) { // 异常兜底 CefRefPtrCefProcessMessage msg CefProcessMessageCreate(native_response); CefRefPtrCefListValue args msg-GetArgumentList(); args-SetString(0, funcName); args-SetString(1, {\error\:\C handler crashed\}); if (m_pBrowser.get()) { m_pBrowser-GetMainFrame()-SendProcessMessage(PID_BROWSER, msg); } } } else { // 未注册函数 CefRefPtrCefProcessMessage msg CefProcessMessageCreate(native_response); CefRefPtrCefListValue args msg-GetArgumentList(); args-SetString(0, funcName); args-SetString(1, {\error\:\function not found\}); if (m_pBrowser.get()) { m_pBrowser-GetMainFrame()-SendProcessMessage(PID_BROWSER, msg); } } return 0; }逻辑说明OnCefNativeCall()在 MFC 主线程中执行安全调用 Win32 API如WritePrivateProfileString。但SendProcessMessage()必须在 CEF 的 UI 线程中调用否则崩溃。这里我们直接在主线程中调用它——CEF3 90.6.0 的SendProcessMessage()是线程安全的会自动转发到目标线程。PID_BROWSER表示发送给浏览器进程即 JS 所在进程native_response是我们约定的响应消息名。4.3 JS 侧接收 C 响应监听cefQuery的onSuccess回到index.html补充响应监听逻辑script // 监听 C 的响应通过 cefQuery 的 onSuccess // 注意cefQuery 的 onSuccess 是自动触发的无需额外监听 // 上面的 callCpp() 已经在 onSuccess 中处理了 // 如果需要全局监听所有响应可覆盖 window.cefQuery.onSuccess // 但不推荐破坏封装性 /script关键点cefQuery是请求-响应模型JS 发起cefQuery()后C 处理完必须调用SendProcessMessage()发送native_response消息CEF 内部会自动匹配request并触发onSuccess。不需要 JS 主动轮询或监听事件。5. 避坑指南VS2010 CEF3 90.6.0 MFC 的 5 个血泪经验5.1 现象程序启动后立即崩溃错误码 0xC0000005访问冲突原因CefInitialize()调用位置错误。常见于在CDialog::OnInitDialog()中调用而非CWinApp::InitInstance()开头。CEF 初始化必须在任何 MFC 窗口创建前完成否则其内部单例对象未构造后续调用必崩。解决严格按本文 2.3 节在CCefInMfcDemoApp::InitInstance()最开头调用CefInitialize()并在ExitInstance()中调用CefShutdown()。5.2 现象浏览器区域一片灰白控制台无报错OnLoadingStateChange不触发原因Resources目录路径错误或缺失icudtl.dat文件。CEF3 90.6.0 依赖Resources/下的icudtl.dat、locales/、swiftshader/等子目录路径错一个字符就静默失败。解决用 Process Monitor 监控libcef.dll的文件读取行为确认它是否在找D:\cef3\90.6.0\...\Resources\icudtl.dat。若路径不对CefSettings.resources_dir_path必须用正斜杠/或双反斜杠\\单反斜杠\在字符串中会被转义。5.3 现象JS 调用cefQuery后onFailure触发错误码 -1原因CefSettings.no_sandbox false默认值。VS2010 编译的 CEF3 90.6.0 在 Windows 7 上无法启用沙箱会因找不到ntdll!NtCreateUserProcess等导出而失败。解决在CefSettings中强制设置settings.no_sandbox true;这是 MFC 单进程嵌入的硬性要求不是可选项。5.4 现象OnProcessMessageReceived从未被调用JS 发送的消息石沉大海原因CefClient实例生命周期管理错误。常见于在OnInitDialog()中new ClientHandler(this)后未用CefRefPtr持有导致对象被析构。CefClient必须全程由CefRefPtr管理否则 CEF 内部引用计数为 0 时自动释放。解决在CefInMfcDemoDlg.h中将m_pClient声明为CefRefPtrClientHandler并在OnInitDialog()中m_pClient new ClientHandler(this);确保其生命周期覆盖整个对话框。5.5 现象PostMessage(WM_USER100)后OnCefNativeCall不执行UI 线程卡死原因MFC 对话框未启用消息泵。常见于DoModal()返回后才处理消息但PostMessage发送的是异步消息必须在DoModal()运行期间处理。解决确保OnCefNativeCall消息映射已正确添加BEGIN_MESSAGE_MAP/END_MESSAGE_MAP且WM_CEF_NATIVE_CALL常量定义在头文件中非.cpp文件内。VS2010 对消息宏的解析极敏感定义位置错会导致编译通过但运行时不响应。6. 进阶技巧让 JS 调用更像原生 API加一层 C 代理与类型安全封装6.1 用宏自动生成 JS-C 绑定胶水代码手写m_jsHandlers映射表易出错、难维护。我一般会用一个头文件js_binding.h自动生成绑定// js_binding.h #pragma once #include include/cef_v8.h // 定义绑定宏 #define JS_BIND_FUNC(name, func_ptr) \ m_jsHandlers[_T(#name)] [this](const CString json) - CString { \ Json::Value req; Json::Reader r; r.parse(CT2CA(json), req); \ auto res func_ptr(req); \ Json::FastWriter w; return CA2CT(w.write(res).c_str()); \ }; // 在 OnInitDialog() 中使用 JS_BIND_FUNC(get_system_info, CCefInMfcDemoDlg::HandleGetSystemInfo) JS_BIND_FUNC(save_config, CCefInMfcDemoDlg::HandleSaveConfig)对应 C 处理函数签名统一为Json::Value (const Json::Value)保证类型安全// CefInMfcDemoDlg.cpp Json::Value CCefInMfcDemoDlg::HandleGetSystemInfo(const Json::Value req) { Json::Value res; res[status] ok; res[os] Windows; res[uptime_ms] GetTickCount64(); return res; } Json::Value CCefInMfcDemoDlg::HandleSaveConfig(const Json::Value req) { Json::Value res; res[saved] true; res[path] req[file_path].asString(); return res; }优势JSON 作为中间格式天然支持嵌套对象、数组、布尔、数字比纯字符串解析更鲁棒所有 handler 函数签名一致IDE 可跳转、可重构宏展开后仍是标准 C无运行时开销。6.2 在 JS 中封装成 Promise 风格的 SDK为前端工程师提供更友好的 API封装cefQuery为模块// sdk/cefbridge.js class CefBridge { static async call(funcName, payload {}) { return new Promise((resolve, reject) { window.cefQuery({ request: JSON.stringify({ func: funcName, data: payload }), onSuccess: (response) { try { resolve(JSON.parse(response)); } catch (e) { reject(e); } }, onFailure: (code, msg) { reject({ code, msg }); } }); }); } static async getSystemInfo(version) { return this.call(get_system_info, { app_version: version }); } static async saveConfig(config) { return this.call(save_config, config); } } // 挂载到全局 window.CefBridge CefBridge;在 HTML 中使用script srcsdk/cefbridge.js/script script document.querySelector(button).onclick async () { const info await CefBridge.getSystemInfo(2.0.0); console.log(系统信息:, info); }; /script6.3 调试技巧用 CEF 的--log-file和--log-severity捕获底层日志在CefSettings中添加日志配置定位初始化失败原因// CefInMfcDemoApp.cpp settings.log_file cef_log.txt; // 输出到当前目录 settings.log_severity LOGSEVERITY_INFO; // INFO 级别以上然后在程序根目录查看cef_log.txt搜索ERROR、FATAL关键字。常见线索如Failed to load icudtl.dat→ Resources 路径错Failed to create sandbox process→no_sandbox false未设Could not find the resources dir→resources_dir_path为空我的习惯是新环境首次运行必加日志每次修改CefSettings必清空日志重跑日志文件超过 1MB 就删CEF 不自动轮转。这招帮我避开了 80% 的“静默失败”问题。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑