ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Delphi集成CEF4Delphi:用Chromium内核替换TWebBrowser的实战指南

2026/9/7 1:51:03 拓冰建站 浏览量
Delphi集成CEF4Delphi:用Chromium内核替换TWebBrowser的实战指南 简介Chrome浏览器控件Delphi版是一份基于CEFChromium Embedded Framework技术封装的开发资源包面向需要在Delphi应用中嵌入现代Web浏览器的中高级开发者。借助CEF4Delphi库开发者可快速集成Chromium核心引擎使用TChromium控件完成页面加载、导航拦截、JavaScript交互、界面定制等常见任务适合构建办公自动化、混合应用或对Web兼容性要求较高的桌面程序。资源包共503个文件以206个pas源文件、47个dproj工程文件、41个inc配置为主辅以dfm窗体定义、bmp图标、html演示页面及批处理脚本等压缩包仅1.66MB目录结构清晰便于移植和二次开发。已有1870人学习下载。通过该包可省去自行编译与适配CEF的大量时间还能参考其工程组织方式快速打造具备HTML5支持的自定义浏览器窗口并利用事件机制优化交互体验为稳定、安全的嵌入式浏览器方案奠定基础。 去年我接手一个维护了十多年的Delphi进销存系统客户提的第一个需求就让我卡住了把网页端的实时销售大屏嵌进老程序里。下意识拖了一个TWebBrowser上去结果页面打开白屏一片——人家前端用的是Vue EChartsIE内核根本没法完整解析ES6语法我折腾了快两周才意识到再也不能指望这个自带控件承载现代Web内容了。后来我把目光转向Chrome浏览器控件方案最终用CEF4Delphi把这套老系统从IE内核平滑迁移到了Chromium内核整个过程涉及选型对比、内核机制理解、消息循环接入、Win7兼容、JS双向通信等一系列问题。这篇文章就把这次改造的完整过程整理出来重点说清楚为什么这么选、底层原理是什么、实际代码怎么写以及你一定会遇到的几个坑。如果你也要在Delphi里嵌入现代网页、做混合应用或者单纯想给老MIS系统换上现代前端界面这篇值得存下来慢慢看。1. 为什么要在Delphi里塞一个Chrome内核1.1 TWebBrowser退场的真正原因先说结论TWebBrowser本身没做错什么错的是它绑定的MSHTML内核。Delphi从很早就提供了TWebBrowser开发时确实省事拖上去就能显示网页。但这个控件本质是IE的宿主外壳内核版本跟着操作系统走Windows 10上它用的还是老旧版本的Trident/MSHTML引擎。现代前端开发基本默认Chromium内核的渲染行为和V8引擎于是问题就集中爆发了。我在这个项目里遇到的典型症状可以列几张纸Vue/React单页应用打开后白屏、ECharts图表加载不出、flex和grid布局错乱、CSS3动画卡顿、WebSocket连不上、WebGL直接黑屏。还有一次更离谱客户用了一个带shadow DOM的组件库页面在Chrome里完全正常放到TWebBrowser里连组件影子树的样式都加载不出来。你很难说是谁的错但结果就是老系统要接新需求嵌入的网页必须是现代WebTWebBrowser已经不够用了。这个问题的本质是运行时环境的错位。你可以把TWebBrowser理解成一个老款点唱机IE内核就是里面的唱片机芯而现在市面上的唱片都是为现代播放器刻录的强行播放要么无声要么走调。Chrome浏览器控件的作用就是给Delphi程序装上一台现代播放器——它把Chromium内核整体打包成可嵌入的组件你的程序拿到的是一个完整、独立、不依赖系统版本的浏览器运行时。1.2 这里的Chrome控件不是Chrome浏览器有一点需要先掰扯清楚我们说的Chrome浏览器控件并不是把Chrome浏览器本体嵌进程序里而是使用Chromium Embedded FrameworkCEF这套开源框架。Chrome是Google基于Chromium开源内核做出来的商业化产品CEF则是把Chromium内核抽出来做成可嵌入库让第三方桌面程序直接加载渲染网页、执行JavaScript、获取页面事件。所以你在Delphi里集成的是Chromium内核 CEF API封装这不是套壳浏览器而是一套可编程的浏览器运行时。CEF的能力覆盖很全面HTML5/CSS3/ES6渲染、V8 JavaScript引擎、离线缓存管理、Cookie控制、网络请求拦截、扩展机制chrome://extensions/ 对应的扩展API有一部分也能用、远程调试协议等。对于Delphi生态来说社区里基于CEF出的封装组件就是我们要选的Chrome浏览器控件。它们解决的是同一个问题让Delphi程序拥有现代浏览器内核同时保留Delphi原生程序对窗体、数据库、业务逻辑的掌控能力。2. 选型对比DCEF、CEF4Delphi、TEmbeddedChrome到底该用哪个2.1 三款主流封装的差异在具体动手之前我把当时能找到的Delphi版CEF封装都过了一遍主要是三款DCEF3、CEF4Delphi、TEmbeddedChrome。它们的定位不太一样做个对比表看得更清楚。方案维护状态封装程度可定制性典型适用场景DCEF3基本停更社区零星维护中等中老项目维护、不想换依赖CEF4Delphi活跃持续跟进CEF版本中等偏底层高新项目、需要深度定制TEmbeddedChrome更新较慢很高类TWebBrowser操作方式低快速上手、功能需求简单DCEF3算是早期出名的封装很多老项目都在用但它的更新已经明显跟不上CEF版本节奏。如果你只是想把现有项目里的TWebBrowser换掉不怎么需要新特性DCEF3还能用前提是别碰太新的网页功能。TEmbeddedChrome封装得更傻瓜接口风格接近TWebBrowser上手最快但代价是深度定制能力弱遇到要改请求头、做协议拦截、接入扩展这类需求会很难受。CEF4Delphi是我最终的选择。它由Salvador Díaz Fau持续维护GitHub上非常活跃会跟着CEF官方版本更新支持从Delphi老版本到新版的全系。它最大的优点是示例工程齐全官方带了一个MiniBrowser完整Demo包含窗体创建、多标签、加载事件、JS回调等几乎所有场景的参考代码。对于需要深度控制浏览器行为的项目来说这是难得的财富。2.2 为什么我最终押在CEF4Delphi上选型逻辑其实很朴素我给自己列了三项硬指标能不能长期维护、能不能满足深度定制、能不能兼容目标部署环境。第一项直接pass掉DCEF3第三项让WebView2方案出局了。关于WebView2多说一句它是微软基于Edge Chromium内核推出的现代控件功能确实不错设计思路也先进但它对Windows 7的支持非常有限。客户这边还有一批老工控机跑着Win7有些机器连系统补丁都没法打这种情况下WebView2的Runtime分发和系统版本限制就成了硬伤。CEF方案则不同它是把你的exe同目录的DLL和资源文件作为运行时系统只负责提供基础Win32 API所以只要CEF版本选对Win7上完全能稳定跑。另外CEF4Delphi保持了较高的底层可访问性它没有把CEF的API全部藏起来而是封装成VCL组件但保留了事件和回调入口。这对我后面做请求拦截、JS双向通信、页面截图帮助很大。总而言之如果你的项目需要长期维护、要部署到老系统又需要一定程度的浏览器定制能力CEF4Delphi是当前最稳妥的方案。3. 嵌入前必须搞懂的三个Chromium底层机制3.1 多进程架构任务管理器里多出来的进程不是bug第一次集成CEF时运行程序后打开任务管理器看到进程列表里一下子多出好几个同名的子进程我第一反应是资源没释放。其实这是Chromium的正常架构和Chrome浏览器本身一样CEF跑起来后会按职责拆分为多个进程主进程Browser Process、渲染进程Renderer Process、GPU进程、网络进程等。对Delphi开发者来说这意味着两件事。第一你的exe既可能是主进程也可能是某个子进程——当Windows按CEF命令行为exe传入特殊参数启动它时这次启动不会进入Delphi的Application逻辑而是直接进入CEF内置的子进程消息循环跑完即退出。这就是为什么工程初始化代码里必须判断StartMainProcess的返回值。第二渲染进程崩溃不一定会带走主程序但页面会异常你需要注册相关的事件回调做恢复处理。这个架构还决定了你打包部署时要注意的细节exe所在目录需要放完整的CEF运行时文件DLL、.pak资源、locales目录、icudtl.dat等一个都不能漏。我后来发现很多白屏问题根源就是部署时图省事只拷了主DLL丢了资源文件。3.2 线程模型与消息循环Delphi和CEF怎么和平共处CEF是多线程模型UI线程、IO线程、渲染线程各司其职。嵌入Delphi程序时最关键的问题是如何让CEF的消息循环和VCL的消息循环共存。VCL是典型的Win32消息循环Application.Run里不断取消息、分发消息CEF也有自己的消息处理需求如果两边各转各的界面上会表现为页面卡死、事件不响应。CEF4Delphi的解决思路是让VCL在空闲时驱动CEF处理消息。官方Demo里通常会在TApplicationEvents.OnIdle事件里调用驱动函数确保Application的消息队列空闲时CEF有机会处理自己的内部消息。这里你要记住一个原则不要阻塞OnIdle事件不要在里面写耗时操作否则CEF消息处理会被饿死。我踩过一次坑在OnIdle里习惯性地加了一句日志写文件结果页面加载明显变卡滚动掉帧。后来把日志挪到单独的线程队列里才恢复流畅。原因是OnIdle触发频率很高磁盘IO直接拖慢了CEF消息循环。3.3 生命周期管理全局只能初始化一次CEF是全局运行库一条生命周期约束很硬进程内只能初始化一次、只能Shutdown一次。在Delphi工程文件里负责初始化的GlobalCEFApp对象必须在Application.Run之前创建并初始化然后在Application.Run结束之后再调用Shutdown清理。更具体地说初始化过程由GlobalCEFApp.StartMainProcess完成。这个函数会解析命令行参数判断当前进程是主进程还是子进程。如果是子进程渲染进程等它会进入CEF自带的消息循环函数不会返回你的Delphi程序也就不会进入Application.Initialize只有主进程会带着True返回值继续走正常VCL启动流程。这个设计容易被忽略特别是当你看到程序编译后运行没报错却没有任何窗体出现时先想想是不是这个流程没写对。4. 从下载到跑通CEF4Delphi的完整落地步骤4.1 环境准备与二进制匹配先准备好Delphi开发环境我用的是Delphi 10.4后来在11和12上也验证过都没问题。然后从CEF4Delphi官方仓库的Release页面下载两样东西一是对应你Delphi版本的源码/组件包二是CEF官方构建的二进制运行时包。这里有一个很容易翻车的点二进制包分win32和win64必须和你Delphi程序的目标平台严格匹配。如果程序是32位就下32位CEF包64位程序就用64位包一旦混用运行时会因为DLL加载失败而直接报错或白屏。下载后先解压CEF4Delphi组件包打开其中的Delphi版本分组比如packages目录下对应10.4的路径编译并安装运行时包和设计时包。安装成功后Delphi组件面板里会出现TChromium等相关组件。这时候先别急着写功能建议先把官方MiniBrowser示例编译跑一遍确认CEF二进制文件放置在示例输出目录下能正常显示网页再往自己项目里迁移。这一步能帮你把环境问题和代码问题隔离开。4.2 工程文件改造把CEF的初始化嵌进启动流程在自己的项目里改造的第一步是工程文件DPR。下面是我实际使用的启动结构需要注意调用顺序program MyBrowser; uses Vcl.Forms, uCEFApplication, UnitMain in UnitMain.pas {frmMain}; {$R *.res} begin GlobalCEFApp : TCefApplication.Create; if not GlobalCEFApp.StartMainProcess then Halt(1); Application.Initialize; Application.CreateForm(TfrmMain, frmMain); Application.Run; GlobalCEFApp.Shutdown; end.这段代码的逻辑线很清楚先创建全局CEF应用对象然后调用StartMainProcess判断当前进程身份。如果返回False说明当前进程是CEF的子进程程序在这里就halt由CEF接管后续处理只有主进程才会继续走到VCL初始化创建主窗体进入消息循环。等Application.Run结束后最后调用GlobalCEFApp.Shutdown做全局清理。4.3 窗体上嵌入TChromium并加载页面窗体设计时把TChromium控件拖到窗体上设置Align为Client让它填满整个客户区。然后在FormCreate里配置缓存路径并加载目标地址procedure TfrmMain.FormCreate(Sender: TObject); begin GlobalCEFApp.CachePath : ExtractFilePath(Application.ExeName) cefcache; Chromium1.LoadURL(https://www.example.com); end;缓存路径这个细节容易踩坑不要把缓存目录设置到Program Files这类系统受保护目录否则运行时没有写权限表现为页面偶尔加载缓慢、Cookie保存失败。我习惯把缓存放到exe同级目录下的子文件夹部署时整个目录拷贝即可。页面加载过程中几个常用事件值得挂上OnTitleChange用于把网页标题同步到窗体标题栏OnLoadStart和OnLoadEnd用于跟踪加载状态OnBeforePopup用于拦截新窗口弹出。这里重点讲一下OnBeforePopup。网页里如果执行了window.open或者某些链接带target_blank默认行为是触发CEF的弹窗流程处理不好会直接打开一个新的Chrome窗口脱离你的程序。我在这个项目里就遇到过点了一下页面上的打印报表按钮结果弹出一个独立的浏览器窗口客户以为程序出bug了。正确做法是在OnBeforePopup事件里判断弹窗的目标如果是当前标签页类型的跳转就把URL交给当前TChromium加载并置aHandled为True阻止默认行为procedure TfrmMain.Chromium1BeforePopup(Sender: TObject; const aURL: string; const aTargetDisposition: TCefStringList; ...); begin if aTargetDisposition WOD_NEW_FOREGROUND_TAB then begin Chromium1.LoadURL(aURL); aHandled : True; end; end;4.4 处理CEF消息驱动的最后一块拼图窗体上放一个TApplicationEvents组件在OnIdle事件里调用CEF的消息处理函数让它有机会处理内部消息。代码大概是这样procedure TfrmMain.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean); begin if Assigned(GlobalCEFApp) then GlobalCEFApp.DoOnIdle; end;具体方法名以你下载的CEF4Delphi版本官方Demo为准不同版本略有差异。这类函数的核心作用就是给CEF一个空闲时处理消息的时机。如果你跳过这一步常见表现是程序启动后窗口能显示但网页区域一直空白或者点击页面上没有反应。5. 实战中遇到的高发问题与排查链路5.1 进程残留关掉程序后浏览器进程还在跑这个问题几乎每个用CEF的人都会遇到。现象是程序退出了任务管理器里还有几个浏览器子进程存活占着内存不释放。我第一次遇到时做了完整排查捋出来的原因主要是两个。第一个原因是GlobalCEFApp.Shutdown没有被执行。只要Application.Run后面没跟上Shutdown或者中途有异常跳过了CEF就不会主动清理子进程。第二个原因是动态创建的TChromium或窗体没有正确释放。CEF4Delphi的组件在Destroy时会通知CEF释放对应的浏览器实例如果组件被泄漏了浏览器实例自然也就没有释放。排查链路是这样的正常退出程序后立刻看任务管理器里残留进程数量如果残留的是带命令行参数的子进程先确认Shutdown是否执行再检查代码里有没有动态Create的TChromium没Free。我的修复方案是在工程文件里用try/finally包住整个流程确保任何异常情况下Shutdown都会被执行try Application.Run; finally GlobalCEFApp.Shutdown; end;5.2 白屏问题DLL位数不匹配和GPU加速白屏是第二个高发问题而且排查链路更长。第一次启动整个窗体都出来了但网页区域一片白打开开发者工具看控制台也没有报错。我按这个顺序排查先确认CEF二进制包的位数和exe目标平台一致。这个口子最常见解决方法就是重新下一份匹配的包替换掉执行目录里的DLL。然后是资源文件完整性检查确认locales目录、.pak文件、icudtl.dat都在。这两个排查完如果还白屏就要考虑GPU加速问题了。老显卡、虚拟机、远程桌面环境下Chromium的GPU进程可能启动失败导致渲染异常此时在初始化GlobalCEFApp时关闭GPU加速GlobalCEFApp.DisableGPUAcceleration : True;这个配置对大多数业务系统没有影响普通页面渲染用的是CPU合成损失的性能几乎感知不到但稳定性提升很大。5.3 Win7兼容性CEF版本锁定很重要客户那批Win7机器着实让我头疼了一轮。Chrome 109是支持Windows 7的最后一个版本从110开始官方停止了对Win7的支持CEF二进制也跟着这个节奏。如果贸然用了新版CEF包在Win7上运行时会出现各种奇怪的错误最典型的是进程启动后立刻崩溃。这个场景下的处理思路是如果你还需要支持Win7就把CEF二进制版本锁定在109这个节点对应的CEF4Delphi版本也选当时匹配的release。我用的是一个老版本的CEF4Delphi release配109的CEF二进制在Win7工控机上跑了大半年没有出现内核级问题。需要提醒的是锁定版本意味着你享受不到后续的安全修复和特性更新所以建议在项目里把目标系统是否包含Win7作为选型前置条件明确写下来。5.4 回调线程与操作时序不要直接在事件里操作数据库我在OnLoadEnd事件里直接执行了一个数据库查询来同步页面数据结果偶发地报出cannot perform this operation on an open dataset的错误。这类问题很迷惑人因为错误信息和浏览器控件看起来毫无关系。排查后发现根因是事件回调的线程上下文。CEF4Delphi大部分事件会投递回VCL主线程但某些时机点并不完全等同于VCL消息循环里的安全时刻如果你在回调里操作了当前正在使用的DataSet可能和另一个消息处理逻辑产生竞争。最稳妥的做法是在回调事件里不做重活用TThread.Queue把操作投递回主线程的消息队列或者先设置标志位在主窗体定时器里再做查询。procedure TfrmMain.Chromium1LoadEnd(Sender: TObject; const aBrowser: ICefBrowser; const aFrame: ICefFrame; httpStatusCode: Integer); begin if aFrame.IsMain then TThread.Queue(nil, procedure begin DoSyncData; // 实际的数据操作 end); end;这个习惯后来帮我避免了不少潜在崩溃凡是CEF回调里要触碰VCL组件或数据库的地方我都统一走TThread.Queue中转。6. 进阶玩法双向通信、请求拦截、截图与OCR采集6.1 从Delphi调用JavaScript执行页面操作页面加载完成之后Delphi需要能主动操作页面元素。CEF4Delphi提供了ExecuteJavaScript方法可以直接在指定frame里执行JavaScript代码。比如我要把一个字符串写入页面里的输入框并触发点击事件Chromium1.ExecuteJavaScript( var el document.getElementById(keyword); el.value 入库单; document.getElementById(searchBtn).click();, , 0);第三个和第四个参数是脚本URL和起始行号日常使用传空字符串和0即可。这种调用方式在页面稳定的内部系统里非常好用我甚至用它实现了自动补录从旧系统的DataGrid里读出数据拼接JS代码把数据填进网页表单再触发保存。整个过程看起来就像有人在页面上操作一样。6.2 从JavaScript调用Delphi自定义协议拦截反过来页面上的JavaScript怎么调Delphi方法最稳妥也最容易理解的方式是自定义协议。在JS里把目标地址赋值为自定义协议URLDelphi在OnBeforeBrowse事件里拦截这个URL并解析参数procedure TfrmMain.Chromium1BeforeBrowse(Sender: TObject; const aBrowser: ICefBrowser; const aFrame: ICefFrame; const aURL: string; const aIsRedirect: Boolean; out aHandled: Boolean); begin if Pos(myapp://action/saveData?, aURL) 1 then begin HandleSaveAction(aURL); aHandled : True; end; end;对应在页面JS里只需要一行window.location.href myapp://action/saveData?nameabcvalue123;。这种写法跨版本稳定不依赖CEF的扩展注册机制理解起来也直观。等整个通信机制跑通之后页面相当于成了前端UI层Delphi成了业务逻辑层两者通过协议无缝衔接老系统的改造空间一下就打开了。6.3 请求拦截注入Token和修改请求头系统接入了新的接口认证后所有XHR请求都要带头Token。改造第三方页面是不可能的但可以在OnBeforeResourceLoad事件里给所有请求注入请求头procedure TfrmMain.Chromium1BeforeResourceLoad(Sender: TObject; const aBrowser: ICefBrowser; const aFrame: ICefFrame; const aURL: string; var aResourceType: TCefResourceType; ...); begin if aURL.Contains(/api/) then begin aHeaders.Add(Authorization: Bearer GetToken); aHeaders.Add(X-Client-App: Delphi); end; end;这个能力的本质是CEF在请求发出前给了你一次修改机会。做单点登录、接口鉴权、统一报文头的时候这个回调几乎是必经之路。6.4 页面截图与OCR数据采集最后一个我觉得很值钱的玩法是结合页面截图和OCR做数据采集。CEF4Delphi可以获取当前页面内容我把网页里的验证码区域截取成位图再做OCR识别替代人工输入。具体思路是在页面指定位置截取区域图像保存为PNG文件然后交给OCR组件识别。虽然现在很多系统有更好的验证码方案但在内部老系统里不少还是四位数字字母验证码这种套路依然实用。另外如果是采集网页表格数据我更推荐用ExecuteJavaScript把DOM内容序列化成JSON字符串通过自定义协议传回Delphi解析而不是用截图OCR。截图OCR的适用场景是页面本身提供不了结构化数据或者要采集的是图形界面上的内容。两条路线我都走过都记录下来供你按需选。最后说个我自己的习惯新项目接CEF之前先做一次10分钟的冒烟验证拿官方MiniBrowser在目标部署环境上跑一圈确认页面显示、加载、退出都正常了再开始写自己的功能。这一下能过滤掉90%的环境问题。后面再遇到疑难杂症优先开CEF日志配合远程调试端口定位。你别指望一次集成完完全全不出岔子但把这里面的机制和坑提前捋清楚至少能帮你省下好几个周末的排查时间。本文还有配套的精品资源点击获取