ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Delphi集成Edge Browser:WebView2内核实战指南

Delphi集成Edge Browser:WebView2内核实战指南 有些日子没回 Delphi 圈子分享东西了今天来聊聊一个已经成熟但很多人还在观望的方案用 Delphi 的 Edge Browser 组件把微软的 WebView2 内核直接嵌进你自己的界面里。如果你还在用老旧的 TWebBrowser或者为了个内置网页功能去套第三方内核那这篇东西值得你花十分钟看完。先说结论Delphi 在新版本里提供的 TEdgeBrowserEdge Browser 组件不是简单包了一层壳它是基于 WebView2 Runtime 的官方封装底层跑的是 Chromium 内核对应微软 Edge 浏览器同款引擎。这意味着你可以在 Delphi 程序里同时拥有原生界面的速度和现代 Web 技术栈的渲染能力而且不像 IE 内核那样渲染落后、问题成堆。它适合做带 GIS 展示的内部系统、数据大屏、混合应用客户端、带复杂前端交互的配置工具……一句话凡是以前想在 Delphi 里塞一个“差不多能用的网页”的场景现在都能换成这个方案重新评估一遍。这篇文章我会从组件选型思路、环境搭建、API 使用、实战落盘到避坑排障全部过一遍保证你看完能直接上手做项目。1. 组件选型与整体设计思路1.1 为什么是 Edge Browser 而不是 TWebBrowser 或第三方内核很多老项目里还在用 TWebBrowser为什么我劝你别再往新项目里带因为 TWebBrowser 本质上是 IE 内核MSHTML的封装已经停止实质演进。如今很多前端框架默认不做旧内核兼容你拿 TWebBrowser 打开一个用了 ES6 的页面轻则样式错乱重则白屏。更别提 WebSocket、WebGL、Service Worker 这些现代能力IE 内核几乎没法正常用。第三方嵌入式浏览器方案我也都试过一圈典型的像 CEFChromium Embedded Framework和 WebView 系列。CEF 的优点是可控性强但缺点也明显dll 体积巨大、多进程模型下内存占用高、升级维护要靠自己跟进版本。更重要的是在 Delphi 里集成 CEF 你得引入一堆接口单元编译链一拉长团队上手成本就上去了。反而是微软官方在 Delphi 里实现了 TEdgeBrowser直接依赖本机安装的 WebView2 Runtime你不用随程序分发几十上百 MB 的浏览器内核文件。Runtime 体积小、自动更新还和系统里的 Edge 内核同源兼容性有保障。你要做的就是在 Delphi 里拖一个组件设置好 UserDataFolder然后调用导航方法。从维护角度看TEdgeBrowser 还有个优势WebView2 的 API 是微软长期支持的主线不会像老 WebBrowser 组件那样被收进兼容性冷宫。选择了它等于把你应用里“网页这块的寿命”拉长到和官方产品一致的周期。1.2 Edge Browser 组件的核心架构TEdgeBrowser 组件在 Delphi 里的封装层次是这样的最底层是 WebView2 Runtime也就是和 Edge 同款的 Chromium 内核。中间是 WebView2 SDK 的 COM 接口负责 C 和托管语言与内核通信。Delphi 的 TEdgeBrowser 组件把这套 COM 接口又包了一层使用户可以在 Delphi 中直接通过属性、方法和事件操作。这种分层设计带来的直接好处是你不需要深入 COM 的细节也不需要手动管理引用计数。TEdgeBrowser 背后维护了 CoreWebView2 的接口实例并且把 NavigationStarting、NavigationCompleted、WebMessageReceived 等事件都映射成了 Delphi 事件。你干的是 Delphi 老本行写事件处理函数处理业务逻辑。不过要注意一个重要概念TEdgeBrowser 内部有两条线程协作。UI 线程负责组件交互和事件触发Chromium 的渲染进程独立在后台运行。你千万别在事件里干耗时操作也不要去阻断 UI 线程等渲染结果否则很容易出现界面卡死和回调不触发的情况。这个后面我会在排障环节重点展开。1.3 和现有 Delphi 项目的融合方式说句实在话Edge Browser 组件不是要你放弃原生开发它是来补短板的。我推荐的最佳融合实践是把你的原生界面变成“主骨架 局部 Web 化”。骨架部分继续用 Delphi 的 VCL 或 FMX 窗体、菜单、工具栏、状态栏保证原生操作手感和系统集成度。复杂的、迭代频繁的功能则用 WebView2 承载比如报表展示、地图交互、流程配置页面。这样你既保住了原生客户端对系统权限的掌控力又让页面侧能快速迭代不必每次改布局都重新编译一次客户端。另外一个比较妙的用法是“数据双轨制”。让 WebView2 只管渲染所有数据交换通过 Delphi 代码统一管理使用 PostWebMessageAsJson 把数据推给页面页面用 window.chrome.webview.postMessage 把交互结果传回 Delphi。这套路我们团队运用到生产环境后前后端联调效率提了一截问题定位也清楚得多。2. 环境准备与快速建项目2.1 开发环境和运行时依赖检查要顺利使用 TEdgeBrowser第一个前提是 Delphi 版本。据我所知比较完整的官方支持是从 Delphi 10.3 Rio 还是 10.4 Sydney 开始有的稳妥起见我建议你至少使用 RAD Studio 10.4 或更高版本。Xe 系的老版本你也不用惦记了那时候还没有 TEdgeBrowser 组件属于时代的眼泪。第二个前提是 WebView2 Runtime。微软对 Windows 10、Windows 11 系统的预装情况不完全一样Windows 11 基本都带但 Windows 10 不一定。稳妥的做法是在程序安装包或启动逻辑里检查 Runtime 是否存在。检查方法很简单定位到HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}或者直接找pv版本号键值。如果缺失用官方引导安装包静默安装即可。注意安装完成后可能需要重启一次资源管理器否则某些环境下运行时句柄建立不了。第三个细节是目标平台。TEdgeBrowser 是基于系统级 WebView2 COM 接口的所以它天然支持 32 位和 64 位程序。不过我在实际编译中发现如果你的安装环境比较杂乱32 位和 64 位 WebView2 Runtime 可能各自独立存在理论上装任何一个都可以满足对应位数程序的需求但开发机建议两个都装上以免调试时碰到奇怪问题。2.2 新建工程并放置组件IDE 操作其实没什么可悬疑的就是在组件面板里找 “Edge Browser” 或者 “TEdgeBrowser”新建一个 VCL 应用程序FMX 也支持但 VCL 更常见在 “Internet” 或 “Web” 分类下找到 TEdgeBrowser拖到窗体随便调整大小留好布局放入组件后我建议你立刻在属性面板里设置两个东西一是UserDataFolder别让它空着否则运行时 WebView2 会尝试用默认临时目录出现权限不足的概率会高不少。二是DefaultBackgroundColor如果是深色客户端界面趁早改成一致的颜色避免页面加载瞬间白屏闪一下。需要特别提醒的是TEdgeBrowser 初始化是异步的。组件有个CreateBrowserAsync方法调用后不能马上执行 Navigate一定要等OnBrowserCreated事件触发后再导航。这一点网上很多人踩坑觉得“运行时初始化慢”其实是没处理这个事件页面地址被“静默丢弃”。2.3 初始化、导航和浏览器版本获取等OnBrowserCreated触发后你才算真正拿到了 WebView2 的浏览器实例。这时候可以做一些基础操作procedure TForm1.FormCreate(Sender: TObject); begin EdgeBrowser1.UserDataFolder : .\WebView2Data; EdgeBrowser1.OnBrowserCreated : EdgeBrowserCreated; EdgeBrowser1.CreateBrowserAsync; end; procedure TForm1.EdgeBrowserCreated(Sender: TObject); begin // 到这里才能安全导航 EdgeBrowser1.Navigate(https://example.com); // 可选把内核版本显示在标题栏 Caption : EdgeBrowser1.Source; // 或者输出 EdgeBrowser1.CoreWebView2 相关信息 end;想获取当前 Runtime 版本可以借助 TEdgeBrowser 的AvailableBrowserVersion方法不同版本签名有差异建议编译环境里按提示来。这个信息在发版前做兼容性环境自检非常有用可以在程序启动时打个日志记录 Runtime 版本排障时直接能定位是内核太老还是代码问题。3. 核心 API 拆解与实际应用3.1 页面导航与加载控制TEdgeBrowser 的导航方法和小伙伴们熟悉的 WebBrowser 很像Navigate(URL)直接跳转到指定 URLNavigateToString(HTML)不发起网络请求直接把 HTML 文本渲染到页面这个对本地报表和调试极友好Stop()停止加载GoBack()、GoForward()历史前进后退Reload()刷新页面这几组 API 看着简单但使用时机有讲究。前端页面如果正在做表单提交你突然Stop或者跳转可能让页面内部的 Ajax 请求状态错乱。我的经验是做页面跳转前先通过CanGoBack、CanGoForward判断一下有没有历史记录再决定按钮的可用性避免用户在空白状态乱点。加载过程的事件链是NavigationStarting- 多次SourceChanged-NavigationCompleted。你可以在NavigationStarting里做白名单校验比如只允许加载https://内部域名.com开头的地址不符合的一律取消并提示在NavigationCompleted里判断IsSuccess属性是否为真假就弹错误提示或重试。“校验结果反馈”这套组合拳放在内部系统里非常实用能避免用户误操作进入外网。3.2 JavaScript 双向交互PostWebMessage 与 WebMessageReceived要说 Edge Browser 组件在 Delphi 里最香的能力那一定是和页面做双向通信。它有两种主流姿势一种是用ExecuteScript直接往页面注入 JavaScript 代码并拿返回值另一种是走 WebMessage 通道用 JSON 做结构化消息传递。先看ExecuteScript的用法procedure TForm1.btnCallJS(Sender: TObject); begin EdgeBrowser1.ExecuteScript(document.getElementById(txtName).value Delphi;); end;需要注意的是ExecuteScript是异步的返回值通过回调事件或 Future 机制获取。在 Delphi 里你可以用TWebView2ScriptResultReceiver或者直接注册回调函数不同 SDK 版本 API 略有差异。早年我在 CEF 里写同样的逻辑要管理 C 回调Delphi 这边封装得还算友好基本就是“传入 JS 代码字符串结果异步回来”。但是前面那种方式用来做“系统调用”还行用来做复杂数据交换就太脆了字符串拼出来不仅难维护转义还能让你怀疑人生。所以我强烈建议走 WebMessage 通道Delphi 端注册事件每一条来自页面的消息都会触发procedure TForm1.EdgeBrowser1WebMessageReceived(Sender: TObject; const AArgs: TWebView2StringEventArgs); var msg: string; begin msg : AArgs.Value; // 假设是 JSON 字符串 // 解析后分发业务 end;页面端直接用浏览器内置的 Channel 发消息window.chrome.webview.postMessage( JSON.stringify({ type: user_action, payload: { id: 1024, name: DelphiClient } }) );从 Delphi 端推数据给页面用PostWebMessageAsJson方法。我给一个保守建议所有业务消息统一用 JSON 对象包裹带type和payload两个字段。这样你收消息时只要看type就能决定路由到哪个业务函数真做出了前后端微服务的味道而且涨代码量有限收益非常明显。3.3 用户数据目录、Cookies 与本地状态管理刚才提过UserDataFolder这玩意儿对实际项目太重要了。它就是 WebView2 的 profile 目录Cookies、LocalStorage、IndexedDB、缓存全放这里。做内部系统时我强烈建议你按登录用户建立一个子目录例如EdgeBrowser1.UserDataFolder : Format(%s\WebView2_%s, [AppPath, CurrentUserName]);这样每个用户都有独立的会话状态互不干扰也方便做用户级缓存清理。要彻底实现“退出登录”把对应子目录删掉再重新初始化即可不用和 Cookies 纠缠。还有一个值得留意的点WebView2 默认情况下会启用磁盘缓存。如果某些机密页面你不希望数据残留可以在启动时设置--inprivate参数或者单独配置 profile 的 in-memory 模式。不过这种配置需要在创建内核前完成我在下一个小节里讲参数注入的时候会用到。3.4 自定义内核参数与启动选项TEdgeBrowser 底层是 Chromium很多默认行为可以通过参数调整。比如禁用 GPU 加速某些虚拟机环境下 GPU 相关的渲染问题让人头疼关闭自动播放策略限制或者指定 HTTPS 证书校验策略。在创建 Browser 之前你可以通过特定接口或设置环境变量来修改启动选项。不同 Delphi 版本暴露的细节不同有的版本里你需要在CreateBrowserAsync之后、内核真正创建之前调用底层 COM 接口的AddScriptToExecuteOnDocumentCreatedAsync或设置AdditionalBrowserArguments属性。如果属性不存在也可以通过以下方式设置// 某些版本在 CreateBrowserAsync 后可获取 CoreWebView2EnvironmentOptions var envOptions: ICoreWebView2EnvironmentOptions; begin envOptions : UserDataFolder; envOptions.AdditionalBrowserArguments : --disable-gpu --autoplay-policyno-user-gesture-required; // 然后用 envOptions 创建环境 end;需要注意的是浏览器内核实例的环境配置一旦创建就不能动态改了必须重启组件才能应用新参数。所以如果项目中存在需要切换代理、切换证书策略的场景设计上要做成“重启浏览器引擎”而不是“动态改配置”。4. 实战场景构建一个混合架构客户端4.1 场景设计内部数据看板客户端拿我们最近交付的一个项目举例。需求方要求开发一个数据看板桌面客户端左侧是原生树形菜单右侧是大屏 ECharts 数据展示。传统做法是用 Delphi 整棵树用原生 TreeView右侧塞一个浏览器组件想办法调用页面。用 TEdgeBrowser 落地之后左侧还是 Delphi 原生右侧直接加载前端同事开发的看板页面。实现上树形菜单点击后把节点 ID 通过PostWebMessageAsJson发给页面procedure TForm1.TreeView1Change(Sender: TObject; Node: TTreeNode); var msg: string; begin msg : Format({type:change_board,payload:{nodeId:%s}}, [Node.Text]); EdgeBrowser1.PostWebMessageAsJson(msg); end;前端页面里监听消息重新拉数据渲染图表。渲染完成后再向 Delphi 发送一条“加载完成数据时间戳是xxx”的消息Delphi 端更新状态栏保持原生 UI 和页面动作同步。这套流程下来前端改版根本不需要重新发布 Delphi 程序只要把页面静态文件更新到对应目录下次启动加载新版本即可。4.2 本地页面隔离与发布部署策略看板页面如果放在服务器上客户端每次打开都要等网络加载而且断网就变白板。稳妥的做法是把前端资源打包到本地用NavigateToString或直接加载本地文件。不过本地文件有个头疼点如果走file://协议某些浏览器安全策略会把你的页面隔离成独立源Ajax 请求要发到后端 API 就遇到了跨域问题。我的解法是开一个本地回调中转接口。后端 API 的代码从浏览器页面里长轮询改成 Delphi 端直接请求然后通过 WebMessage 把数据发给页面。这样本地页面无论什么源都无所谓数据完全由原生端进出。安全性也更好凭证保存在 Delphi 进程里页面代码根本拿不到敏感 token。4.3 加载进度、错误页与超时处理像做产品一样对待 WebView2 的体验问题。NavigationCompleted事件的IsSuccess为假时不要只写日志要给用户一个友好的加载失败界面。最简单的是NavigateToString渲染一段内嵌 HTML 错误页再提供一个“重试”按钮。超时方面我习惯在NavigationStarting里启动一个TTimer或记录时间戳在NavigationCompleted里判断耗时超过 15 秒就在状态栏预警。这个逻辑在“离线状态下的内部系统”特别好用能快速判断是网络断了还是后端崩了而不是让用户在那对着白屏干瞪眼。5. 常见问题、排查与避坑技巧5.1 表格高频异常速查异常现象可能原因解决方向导航前调用 Navigate 无效内核未初始化完成务必等 OnBrowserCreated 后再导航页面白屏/渲染错乱WebView2 Runtime 版本过旧检测并更新 Runtime内存持续上涨本地缓存/profile 文件增长定期清理 UserDataFolder 或按会话重建JS 消息收不到页面不知道 channel 规范页面内检查 window.chrome.webviewExecuteScript 结果拿不到未注册异步回调使用官方异步接收机制跟踪返回值程序启动崩溃缺少运行库或权限不足检查事件日志确认 WebView2 Runtime 已装5.2 崩溃与内存问题排查实录我调试过程中遇到最典型的崩溃是在 32 位程序里频繁打开/关闭大量页面GPU 进程偶尔崩掉导致整个 View 黑屏。排查思路是把 GPU 加速关掉再做一个测试用例重新跑一遍。如果问题依旧再用事件查看器锁定崩溃模块是否为 msedgewebview2.exe。如果是那基本可以确认 WebView2 版本或者系统图形驱动的问题解法是升级 Runtime、更新显卡驱动或在启动参数里禁用相关硬件加速能力。内存问题也很常见。WebView2 默认会为了流畅性缓存很多东西长时间运行的看板程序如果不做任何清理内存占用可能稳步增长。解决办法是定期调用CallDevToolsProtocolMethodAsync的缓存清理方法或者定期重建 profile。我这边线上做得最狠的方案是每天凌晨用定时任务杀掉客户端进程清理 profile 再重启简单粗暴但有效。5.3 线程模型与 UI 卡死WebView2 事件回调大部分发生在一个独立于渲染进程的“UI 线程”上。在 Delphi 里你的事件处理函数虽然看起来像普通 VCL 事件但千万记得不要在回调里直接做耗时的网络同步请求或文件操作否则整个界面会卡到怀疑人生。正确姿势是把耗时任务转发到后台线程或者用异步模式。另外从非 UI 线程里调用PostWebMessageAsJson或者Navigate前一定要用TThread.Synchronize或者TThread.Queue切回主线程再操作。不经同步直接改 TEdgeBrowser 的状态轻则表现怪异重则直接抛异常。5.4 覆盖安装与路径限制问题安装程序方面网上有个热词提到了“覆盖安装暂不支持更改路径”这个场景其实在 WebView2 Runtime 同样存在。部署组件安装包时如果用户机器上已有旧版 Runtime而你新版本安装包又用了不同的安装路径微软的静默升级在特定情况下会提示需要先卸载旧版。作为开发者我们悠着点的做法是检测到已有更高版本 Runtime 就直接跳过安装旧版则强制走更新流程不要去想换路径安装的骚操作省得跟系统组件较劲。6. 进阶玩法与项目扩展建议6.1 和 Tesseract OCR 结合做增强功能搜热词时看到不少人在研究 Delphi 和 Tesseract OCR 的联动。TEdgeBrowser 这种现代渲染引擎里你可以先用 JavaScript 截图canvas.toDataURL再把 Base64 图片传给 Delphi 侧做 OCR 识别最后把识别结果回填进页面。这套链路适合做“扫码枪识别 本地页面联动”之类的业务比纯前端 OCR 的可控性高识别速度也不慢。6.2 结合自绘组件打造统一 UI 规范如果你原本就在用 ListBox 自绘、TPngImage 旋转这类高阶 Delphi 技巧Edge Browser 组件不但不会破坏你的 UI 观感反而可以把它塞进无边框窗体的自绘区域里。只要把 TEdgeBrowser 的父容器设置成自绘面板它的区域由你控制旁边再摆几个自绘按钮整体界面照样是“原生质感 现代 Web 内容”的混搭风。6.3 从 WebService 到 WebView2 的渐进式迁移我见到不少老 Delphi 项目还在用 WebService 做前后端数据交互。这条路不是不能走但界面侧如果永远停留在老框架迟早要出维护包袱。一个稳妥的迁移路线是先保留 WebService 接口不动新界面用 TEdgeBrowser 加载前端页面前端页面向 WebService 发请求做数据获取。过渡稳定后再逐步把接口层从 WebService 换成更轻的 REST/JSON API。这样每步都可回滚风险小很多。写在最后的一点体会我实际用下来TEdgeBrowser 在 Delphi 生态里算是“迟到但到位”的一个组件。它把原本需要 CEF 或 WebView 封装库才能做到事情收敛成了几个属性、几个事件的问题。你要做的只是理解它的事件时序遵守异步回调的规矩剩下的交给 Chromium 内核。对于做内部系统、工具类软件、数据看板的团队它值得纳入标准技术栈。唯一要上心的是运行时依赖和用户数据目录的规划早点定好规范后面维护会舒服很多。如果你正准备接手一个遗留的 WebBrowser 项目我建议你把迁移到 Edge Browser 提上日程越早动手收益越早兑现。
返回列表