
简介这是一份基于CefSharp深度定制的VB.NET多页面浏览器源码项目面向Windows桌面应用开发者及.NET技术进阶学习者解决传统WebBrowser控件功能受限、体验僵硬等问题适用于开发嵌入式网页管理工具、企业内部信息门户或轻量级桌面端浏览器应用。资源为ZIP压缩包共含多个VB.NET工程文件、XAML界面资源、依赖配置及媒体资源整体体积达978.09MB体现其包含大量预置网页组件、OCR识别模型、GIF解析库及音视频解码支持等完整运行依赖。已有1574人学习下载说明其在实际开发中具备较高参考价值。读者可直接复用多页面标签管理、靠边自动隐藏、沉浸式网页渲染、异形可拖拽控件布局等核心模块同时获得OCR文字识别、网页纯文本与GIF资源批量提取、MP3/MP4本地播放等实用功能的完整实现逻辑与调用范例代码结构清晰注释充分适合作为CefSharp二次开发的高质量学习样板。1. CefSharp 集成谷歌浏览器内核不是“套个壳”而是把 Chromium 的完整能力塞进 WinForms/WPF 应用里你有没有遇到过这种场景用 WebBrowser 控件加载一个带 Vue3 的管理后台页面白屏、控制台报window.cefbridge is not defined或者在 VB.NET 窗体里嵌入一个需要 WebSocket 实时通信的监控看板结果连接直接被断开、调试器里连 Network 面板都打不开这不是你代码写错了——是 WebBrowser 用的是 IE 内核早已被时代淘汰。而这篇笔记讲的 CefSharp是真正把 Chromium也就是谷歌浏览器同源内核编译进 .NET 进程的方案。它支持完整的 DevTools、现代 JS/ES6、WebGL、WebAssembly、多页面标签页、自定义协议注册、JS 与 C# 双向调用甚至能拦截请求、注入脚本、接管下载逻辑。这不是“网页嵌入”是让 WinForms 或 WPF 应用获得和 Chrome 浏览器几乎一致的前端运行环境。适合正在维护老旧桌面系统、需要快速复用现有 Web 前端能力、或必须离线部署 Web UI 的 .NET 开发者——尤其是 VB.NET 团队因为 CefSharp 对 VB.NET 的支持比你想象中更稳只是文档少坑得深。2. 为什么选 CefSharp 而不是 WebView2 或 EdgeWebView三个硬指标对比决定选型CefSharp 不是唯一选择但它是目前唯一能同时满足「完全可控」、「离线可用」、「VB.NET 兼容无痛」、「多页面标签页原生支持」这四条的方案。我们不谈抽象概念直接拉出三个关键维度实测对比2.1 内核版本与更新节奏Chromium 版本号就是你的兼容性底线CefSharp 每次发布都明确绑定 Chromium 主版本号如 CefSharp 122.x 对应 Chromium 122.0.6261.94。这意味着你能精准预判Vue3 的defineCustomElement是否可用、AbortSignal.timeout()是否已实现、CSS :has()伪类是否支持。而 WebView2 的运行时依赖系统级 Microsoft Edge 安装用户没装 Edge 或装的是旧版比如企业锁死在 116你的应用就可能白屏或功能降级——你无法打包 Chromium 进安装包但 CefSharp 可以。实测某客户现场 Win10 LTSC 未装 EdgeWebView2 初始化直接抛CoreWebView2InitializationException换 CefSharp 后把CefSettings.BrowserSubprocessPath指向自带的CefSharp.BrowserSubprocess.exe一行不改跑通。2.2 多页面标签页不是靠 TabControl 堆 UI而是 Chromium 原生多渲染进程很多教程教你在 WinForms 里放多个WebBrowser控件模拟多页那是假多页——内存不隔离、JS 上下文共享、一个页面卡死全窗冻结。CefSharp 的ChromiumWebBrowser是真正的 Chromium 渲染实例每个实例对应独立的渲染进程可配置为单进程模式用于调试。你用TabControl 多个ChromiumWebBrowser实例就能做到页面 A 执行无限循环while(true){}页面 B 依然响应点击页面 A 的localStorage和页面 B 完全隔离关闭某个 Tab 时对应渲染进程自动释放内存通过Dispose()触发所有页面共享同一个CefSettings如代理、缓存路径、UserAgent但各自拥有独立RequestHandler和JsObjectRepository。这才是“多页面”的工程意义不是 UI 层的视觉欺骗。2.3 VB.NET 支持度不是“能编译”而是“能调试、能热重载、能跨线程调用”CefSharp 官方 NuGet 包CefSharp.WinForms/CefSharp.Wpf完全基于 .NET Standard 2.0VB.NET 项目引用后无需任何适配即可使用。重点在于RegisterAsyncJsObject注册的 JS 对象VB.NET 中可直接用Public Sub MyMethod(param As String)声明CefSharp 自动处理 COM 互操作封送IRequestHandler.OnBeforeResourceLoad返回True拦截请求时VB.NET 的Return True语义清晰不会像 C# 的return true;那样因分号习惯引发空行误判最关键的是调试在 VB.NET 项目中启用CefSettings.RemoteDebuggingPort 8088Chrome 访问http://localhost:8088即可看到所有ChromiumWebBrowser实例查看 Network、Console、Elements甚至对 VB.NET 注入的 JS 对象打断点——这点 WebView2 目前仍不支持远程调试需依赖 Edge DevTools Protocol且权限受限。提示CefSharp 不是“替代 WebView2”而是“补足 WebView2 做不到的场景”。如果你的应用只需展示静态 HTML、不关心 DevTools、不需离线部署、且目标环境必装 Edge则 WebView2 更轻量但凡涉及 JS/C# 深度交互、多页隔离、企业离线部署、或 VB.NET 遗留系统升级CefSharp 是目前最稳的生产级选择。3. 从零集成VB.NET WinForms 项目中嵌入 CefSharp 并实现多标签页管理我们以一个真实 VB.NET WinForms 项目为例目标主窗体顶部放菜单栏新建标签页、刷新、前进、后退中部用TabControl托管多个ChromiumWebBrowser实例每个 Tab 标题显示当前 URL关闭 Tab 时正确释放资源。全程不依赖任何第三方 UI 框架纯原生控件。3.1 初始化 CEF在 Application.Run 前完成全局配置CefSharp 必须在任何ChromiumWebBrowser实例创建前初始化且只能初始化一次。VB.NET 中最佳位置是Program.vb的Main方法开头Imports CefSharp Imports CefSharp.WinForms Module Program STAThread Sub Main() 1. 设置 CEF 初始化参数 Dim settings As New CefSettings() settings.CachePath Path.Combine(Application.StartupPath, cache) 独立缓存目录避免权限问题 settings.UserDataPath Path.Combine(Application.StartupPath, user_data) 用户数据隔离 settings.Locale zh-CN 强制中文界面避免英文报错误导 settings.LogFile Path.Combine(Application.StartupPath, cef_log.txt) 日志路径排错必备 settings.LogSeverity LogSeverity.Warning 默认只记 Warning 及以上减少 IO 2. 启用远程调试开发阶段必开 settings.RemoteDebuggingPort 8088 3. 初始化 CEF必须在任何浏览器创建前调用 Cef.Initialize(settings) 4. 启动主窗体 Application.EnableVisualStyles() Application.SetCompatibleTextRenderingDefault(False) Application.Run(New MainForm()) End Sub End Module参数说明CachePath和UserDataPath必须设为绝对路径且确保进程有写权限不能设为C:\Program Files\下LogFile路径若不存在CEF 会静默失败务必提前创建目录RemoteDebuggingPort在生产环境应注释掉否则暴露调试端口。3.2 创建主窗体TabControl 动态添加 ChromiumWebBrowserMainForm.vb中我们用TabControl作为容器并在Form_Load中创建首个 TabPublic Class MainForm Private browserList As New List(Of ChromiumWebBrowser)() Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load 创建第一个 Tab CreateNewTab(https://www.google.com) End Sub Private Sub CreateNewTab(url As String) 1. 新建 TabPage Dim tabPage As New TabPage() tabPage.Text Loading... tabPage.Padding New Padding(0) TabControl1.TabPages.Add(tabPage) 2. 创建 ChromiumWebBrowser 实例 Dim browser As New ChromiumWebBrowser() browser.Dock DockStyle.Fill browser.Name $browser_{Guid.NewGuid().ToString(N)} browser.AddressOf url 设置初始地址 browser.FrameLoadStart AddressOf OnFrameLoadStart browser.FrameLoadEnd AddressOf OnFrameLoadEnd browser.ConsoleMessage AddressOf OnConsoleMessage 3. 将浏览器加入 TabPage tabPage.Controls.Add(browser) browserList.Add(browser) 4. 激活新 Tab TabControl1.SelectedTab tabPage End Sub 页面开始加载时更新 Tab 标题为 Loading... Private Sub OnFrameLoadStart(sender As Object, e As FrameLoadStartEventArgs) If e.Frame.IsMain Then Dim tabPage DirectCast(DirectCast(sender, ChromiumWebBrowser).Parent, TabPage) tabPage.Text Loading... End If End Sub 页面加载完成时更新 Tab 标题为页面 title 或 URL Private Sub OnFrameLoadEnd(sender As Object, e As FrameLoadEndEventArgs) If e.Frame.IsMain Then Dim browser DirectCast(sender, ChromiumWebBrowser) Dim tabPage DirectCast(browser.Parent, TabPage) Dim title If(String.IsNullOrEmpty(e.Frame.GetTitle()), e.Url, e.Frame.GetTitle()) tabPage.Text If(title.Length 20, title.Substring(0, 17) ..., title) End If End Sub 捕获 JS Console 输出便于调试 Vue3 应用 Private Sub OnConsoleMessage(sender As Object, e As ConsoleMessageEventArgs) Debug.WriteLine($[JS Console] {e.Message} at {e.Source} line {e.Line}) End Sub End Class关键逻辑说明AddressOf事件绑定在 VB.NET 中必须显式声明不能省略AddressOfe.Frame.IsMain判断确保只响应主框架加载避免 iframe 加载触发多次标题更新GetTitle()是异步方法需在FrameLoadEnd事件中调用不能在FrameLoadStart中读取。3.3 标签页管理关闭 Tab 时正确释放浏览器资源TabControl的TabClosing事件在 .NET Framework 中并不存在我们必须监听TabControl1.MouseDown并判断是否点击了关闭按钮通常用DrawMode TabDrawMode.OwnerDrawFixed自绘但此处用更通用的MouseClickGetTabRect计算坐标Private Sub TabControl1_MouseClick(sender As Object, e As MouseEventArgs) Handles TabControl1.MouseClick If e.Button MouseButtons.Left Then 遍历所有 Tab检查点击点是否在关闭区域右上角 16x16 像素 For i As Integer 0 To TabControl1.TabCount - 1 Dim rect As Rectangle TabControl1.GetTabRect(i) Dim closeRect As New Rectangle(rect.Right - 16, rect.Top 4, 12, 12) If closeRect.Contains(e.Location) Then CloseTabAt(i) Exit For End If Next End If End Sub Private Sub CloseTabAt(index As Integer) If index 0 AndAlso index TabControl1.TabCount Then Dim tabPage TabControl1.TabPages(index) Dim browser TryCast(tabPage.Controls.OfType(Of ChromiumWebBrowser)().FirstOrDefault(), ChromiumWebBrowser) If browser IsNot Nothing Then 1. 移除事件监听防止内存泄漏 RemoveHandler browser.FrameLoadStart, AddressOf OnFrameLoadStart RemoveHandler browser.FrameLoadEnd, AddressOf OnFrameLoadEnd RemoveHandler browser.ConsoleMessage, AddressOf OnConsoleMessage 2. 显式 Dispose 浏览器触发 Chromium 渲染进程退出 browser.Dispose() 3. 从列表中移除 browserList.Remove(browser) End If 4. 移除 TabPage TabControl1.TabPages.RemoveAt(index) 5. 如果关闭后无 Tab创建新 Tab If TabControl1.TabCount 0 Then CreateNewTab(https://www.google.com) End If End If End Sub参数说明browser.Dispose()是核心它会向 Chromium 发送CloseBrowser消息并等待渲染进程退出若跳过此步直接RemoveTab内存永不释放多开 5 个 Tab 后内存飙升 1.2GBRemoveHandler必须显式调用VB.NET 不会自动解绑事件。4. JS 与 C# 深度交互注册window.cefbridge并实现 Vue3 组件调用 .NET 方法CefSharp 最强大的能力不是“显示网页”而是让网页里的 JS 能像调用本地 API 一样调用 .NET 方法。尤其对 Vue3 项目我们常需在组件中调用“打开本地文件”、“读取注册表”、“执行 PowerShell 脚本”等操作。标准做法是注册一个全局 JS 对象如window.cefbridge然后在 Vue 组件中this.$refs.cefbridge.doSomething()。4.1 在 VB.NET 中注册异步 JS 对象支持 Promise 返回CefSharp 提供RegisterAsyncJsObject它会自动将 .NET 方法包装为返回 Promise 的 JS 函数。注意必须在浏览器创建后、页面加载前注册即ChromiumWebBrowser实例化后立即调用 在 CreateNewTab 方法中browser 创建后、Add 到 TabPage 前插入 browser.RegisterAsyncJsObject(cefbridge, New CefBridgeObject(browser)) CefBridgeObject.vb Public Class CefBridgeObject Private ReadOnly browser As ChromiumWebBrowser Public Sub New(browser As ChromiumWebBrowser) Me.browser browser End Sub JS 调用 window.cefbridge.openFile() 时触发 Public Async Function OpenFile() As Task(Of String) Dim dialog As New OpenFileDialog() dialog.Filter All files|*.* If dialog.ShowDialog() DialogResult.OK Then Return dialog.FileName Else Return End If End Function JS 调用 window.cefbridge.execPowerShell(Get-Process) 时触发 Public Async Function ExecPowerShell(command As String) As Task(Of String) Try Dim psi As New ProcessStartInfo(powershell.exe, $-Command \{command}\) psi.UseShellExecute False psi.RedirectStandardOutput True psi.CreateNoWindow True Using process As Process Process.Start(psi) Dim output As String Await process.StandardOutput.ReadToEndAsync() Await process.WaitForExitAsync() Return output End Using Catch ex As Exception Return $Error: {ex.Message} End Try End Function End Class关键逻辑说明RegisterAsyncJsObject第二个参数必须是Class实例不能是 Module且方法必须为PublicAsync Function返回Task(Of T)CefSharp 自动将其转为 JS Promisebrowser参数传入是为了后续在方法中调用browser.EvaluateScriptAsync回传结果本例未用但复杂场景需此能力。4.2 在 Vue3 页面中调用使用 Composition API 封装 cefbridge假设你的 Vue3 页面已通过index.html引入且确保在app.mount(#app)前window.cefbridge已存在CefSharp 注册是同步的!-- index.html -- !DOCTYPE html html head titleVue3 CefSharp/title /head body div idapp/div script typemodule src/src/main.js/script /body /html// src/components/FileSelector.vue template button clickhandleOpenFile选择文件/button p选中的文件{{ selectedFile }}/p /template script setup import { ref, onMounted } from vue const selectedFile ref() // 检查 cefbridge 是否可用CefSharp 注册后才存在 const checkCefBridge () { if (typeof window.cefbridge undefined) { console.warn(window.cefbridge not available. Check CefSharp registration.) return false } return true } const handleOpenFile async () { if (!checkCefBridge()) return try { const result await window.cefbridge.openFile() if (result) { selectedFile.value result console.log(File selected:, result) } else { console.log(User cancelled file dialog) } } catch (error) { console.error(Failed to open file:, error) } } onMounted(() { // 确保 mounted 时 cefbridge 已就绪 if (!checkCefBridge()) { // 降级方案提示用户刷新或检查设置 alert(CEF bridge not ready. Please refresh or contact admin.) } }) /script避坑点Vue3 的onMounted不保证 DOM 已渲染完毕但window.cefbridge是全局对象只要 CefSharp 注册成功就存在await window.cefbridge.openFile()会等待 .NET 方法执行完并返回结果无需手动处理 Promise 链。4.3 处理 JS 调用 .NET 的异常统一错误捕获与日志JS 调用 .NET 方法时若抛出异常CefSharp 默认会静默失败JS 端收到undefined。我们需要在 .NET 方法中主动捕获并返回结构化错误Public Async Function OpenFile() As Task(Of String) Try Dim dialog As New OpenFileDialog() dialog.Filter All files|*.* If dialog.ShowDialog() DialogResult.OK Then Return dialog.FileName Else Return JSON.Serialize(New With {.success False, .message User cancelled}) End If Catch ex As Exception 返回 JSON 错误对象JS 端可解析 Return JSON.Serialize(New With {.success False, .message ex.Message, .stack ex.StackTrace}) End Try End FunctionJS 端调用时const result await window.cefbridge.openFile() const parsed JSON.parse(result) if (!parsed.success) { console.error(CEF call failed:, parsed.message) throw new Error(parsed.message) }参数说明JSON.Serialize来自System.Text.Json需Imports System.Text.Json返回字符串而非对象是因为 CefSharp 的RegisterAsyncJsObject只支持基础类型序列化复杂对象需手动 JSON 化。5. 避坑指南CefSharp 在 VB.NET 多页面场景下的五个血泪经验这些坑都是我在三个不同客户现场翻车后记下的。不是理论推测是真金白银的内存泄漏、白屏、调试失联。5.1 现象新建第 3 个 Tab 后所有页面 JS 报错Cannot read property addEventListener of null原因ChromiumWebBrowser实例未正确 Dispose残留的渲染进程仍在占用 DOM 资源导致新页面的document对象初始化失败。CefSharp 的 GC 不会自动回收渲染进程必须显式Dispose()。解决在CloseTabAt方法中browser.Dispose()必须在RemoveHandler之后、RemoveTabPage 之前执行且Dispose()后不能再访问browser任何属性否则抛ObjectDisposedException。5.2 现象Vue3 页面中window.cefbridge在部分 Tab 中为undefined部分 Tab 正常原因RegisterAsyncJsObject是针对单个ChromiumWebBrowser实例注册的不是全局注册。你在创建第一个 Tab 时注册了cefbridge但第二个 Tab 的ChromiumWebBrowser实例并未注册所以window.cefbridge不存在。解决每个ChromiumWebBrowser实例创建后必须单独调用browser.RegisterAsyncJsObject(cefbridge, New CefBridgeObject(browser))。不能只在第一个实例注册一次。5.3 现象启用RemoteDebuggingPort 8088后Chrome 访问http://localhost:8088显示空白Network 面板无任何请求原因Windows 防火墙默认阻止 8088 端口入站或杀毒软件拦截了CefSharp.BrowserSubprocess.exe的网络行为。解决以管理员身份运行命令提示符执行netsh advfirewall firewall add rule nameCefSharp Debug dirin actionallow protocolTCP localport8088或临时关闭防火墙测试。生产环境务必禁用该端口。5.4 现象VB.NET 中调用browser.EvaluateScriptAsync(console.log(test))无输出ConsoleMessage事件也不触发原因EvaluateScriptAsync执行在渲染进程但ConsoleMessage事件绑定在 UI 线程若browser尚未完成初始化IsBrowserInitialized为False事件不会触发。解决在调用EvaluateScriptAsync前必须等待browser.IsBrowserInitialized True。可靠写法是监听IsBrowserInitializedChanged事件AddHandler browser.IsBrowserInitializedChanged, Sub(s, e) If browser.IsBrowserInitialized Then browser.EvaluateScriptAsync(console.log(Browser ready)) End If End Sub5.5 现象多页面 Tab 中一个页面执行while(true){}导致整个 WinForms 界面卡死鼠标无法移动原因ChromiumWebBrowser默认使用SingleProcess模式为调试方便此时所有 Tab 共享一个渲染进程一个页面卡死整个进程冻结。解决在CefSettings中强制启用多进程模式settings.MultiThreadedMessageLoop True 启用多线程消息循环 settings.CrashReportingEnabled False 生产环境关闭崩溃报告减少干扰 不要设置 settings.SingleProcess True并在ChromiumWebBrowser创建时指定browser.BrowserSettings.WebSecurity False 如需跨域调试可关生产环境务必开 browser.BrowserSettings.ApplicationCache False 禁用应用缓存避免旧 JS 加载提示MultiThreadedMessageLoop True是解决 UI 卡死的关键它让 CEF 在独立线程运行消息循环不阻塞 WinForms UI 线程。6. 进阶技巧用 CefSharp 实现“离线 Vue3 管理系统”并打包为绿色版我最近交付的一个客户项目需求是一台无外网的工业控制 PC需运行基于 Vue3 的设备参数配置系统所有页面、JS、CSS、图片必须打包进安装包启动即用不依赖任何外部服务。最终方案是 CefSharp Vue3 SPA 打包 自解压资源。这里分享核心三步。6.1 构建 Vue3 项目为纯静态资源Vue CLI 默认生成dist/目录但需确保vue.config.js中publicPath设为./相对路径router使用history模式时需在index.html中添加base href./所有 API 请求改为fetch(/api/xxx)由 CefSharp 拦截并转发到本地 .NET 接口。// vue.config.js module.exports { publicPath: ./, devServer: { historyApiFallback: true // 开发时支持 history 模式 } }构建后dist/目录结构为dist/ ├── index.html ├── assets/ │ ├── js/ │ └── css/ └── api/ ← 此目录不存在由 CefSharp 拦截 /api/* 请求6.2 CefSharp 拦截/api/*请求并路由到 .NET 方法在MainForm.vb中为每个ChromiumWebBrowser设置RequestHandler 创建 browser 后设置 browser.RequestHandler New ApiRequestHandler() ApiRequestHandler.vb Public Class ApiRequestHandler Implements IRequestHandler Public Function OnBeforeResourceLoad(browser As IBrowser, frame As IFrame, request As IRequest, requestCallback As IRequestCallback) As Boolean Implements IRequestHandler.OnBeforeResourceLoad Dim url request.Url If url.StartsWith(http://localhost/api/) Then 拦截 /api/ 开头的请求 Dim path url.Substring(http://localhost/api/.Length) HandleApiRequest(path, request, requestCallback) Return True 阻止默认请求 End If Return False 放行其他请求 End Function Private Sub HandleApiRequest(path As String, request As IRequest, callback As IRequestCallback) Try Select Case path Case devices ReturnJson(callback, GetDevicesJson()) Case save-config ReturnJson(callback, SaveConfig(request)) Case Else ReturnJson(callback, New With {.success False, .message Unknown API}) End Select Catch ex As Exception ReturnJson(callback, New With {.success False, .message ex.Message}) End Try End Sub Private Sub ReturnJson(callback As IRequestCallback, data As Object) Dim json JsonSerializer.Serialize(data) Dim response As New Response() response.StatusCode 200 response.StatusText OK response.MimeType application/json response.Data Encoding.UTF8.GetBytes(json) callback.Continue(response, False) End Sub Private Function GetDevicesJson() As String 从本地数据库或文件读取设备列表 Return JsonSerializer.Serialize(New List(Of Device) From { New Device() With {.Id 1, .Name PLC-01, .Status Online}, New Device() With {.Id 2, .Name HMI-02, .Status Offline} }) End Function End Class关键逻辑说明OnBeforeResourceLoad返回True表示已处理请求CefSharp 不再发起网络请求callback.Continue(response, False)将构造的 JSON 响应返回给页面response.Data必须是Byte()不能是字符串。6.3 打包为绿色版资源嵌入 启动时解压为避免用户手动解压dist/目录我们将整个dist/打包为资源文件.resources启动时解压到Application.StartupPath\wwwroot 在 MainForm_Load 中添加 If Not Directory.Exists(Path.Combine(Application.StartupPath, wwwroot)) Then ExtractResourcesToFolder(MyApp.Resources.dist.resources, wwwroot) End If ExtractResourcesToFolder 方法 Private Sub ExtractResourcesToFolder(resourceName As String, targetFolder As String) Dim assembly Assembly.GetExecutingAssembly() Using stream As Stream assembly.GetManifestResourceStream(resourceName) If stream Is Nothing Then Throw New Exception($Resource {resourceName} not found) Using zip As New ZipArchive(stream, ZipArchiveMode.Read) For Each entry As ZipArchiveEntry In zip.Entries Dim fullPath Path.Combine(Application.StartupPath, targetFolder, entry.FullName) Directory.CreateDirectory(Path.GetDirectoryName(fullPath)) If Not entry.FullName.EndsWith(/) Then Using fileStream As FileStream File.Create(fullPath) entry.Open().CopyTo(fileStream) End Using End If Next End Using End Using End Sub然后在CreateNewTab中加载本地文件browser.AddressOf $file:///{Path.Combine(Application.StartupPath, wwwroot, index.html)}参数说明file:///协议必须用三个斜杠Path.Combine确保路径分隔符正确Windows 用\资源文件需在项目属性 → “资源” → 添加现有文件 → 设为“嵌入的资源”。从那以后我每次交付 CefSharp 项目都强制走一遍“三步验证”启动后打开http://localhost:8088看是否能进 DevTools新建 5 个 Tab每个 Tab 打开不同 Vue3 页面反复切换、关闭用任务管理器观察内存是否稳定断网后启动确认所有 API 请求仍能通过OnBeforeResourceLoad拦截并返回数据。这三步过了才算真正落地。希望帮到你。本文还有配套的精品资源点击获取