简介:Delphi 开发者可借助这份基于 CEF(Chromium Embedded Framework)技术的控件包,通过 CEF4Delphi 封装库将 Chrome 核心引擎嵌入桌面程序,实现网页加载、JavaScript 交互、导航拦截,以及安全与缓存策略的定制。资源共 503 个文件,压缩包约 1.66MB,以 206 个单元源码、48 个窗体定义和 47 个工程文件为主体,另含批量构建脚本、inc 配置和 html/png 等界面辅助资源,目录结构清晰,几乎无需额外整理即可在 IDE 中打开运行。包内演示项目覆盖 TChromium 控件的 URL 设置、BeforeNavigate2 导航拦截、LoadError 错误处理、TitleChanged 标题同步等关键场景,并展示如何调用 ExecuteJavaScript 实现双向通信,以及缓存路径、证书检查等安全配置;借助这些源码,开发者既可快速生成自定义浏览器窗口,也可学习 CEF 与 Delphi 的底层交互机制。目前已有 1870 人学习下载,对需要为传统 Delphi 应用补齐现代 Web 能力的中高级桌面开发者,是一份完整的工程级参考。
1. 为什么要在Delphi里塞一个Chrome内核
接触Delphi的老开发者应该都有体会:VCL那套自带的TWebBrowser控件,本质上是IE内核的壳,遇到现代的网页布局、CSS3动画、WebGL等等,渲染效果一言难尽。前两年我做一个桌面端的报表展示系统,数据本身倒不复杂,但客户非要塞一个带大量前端图表库的网页进去,TWebBrowser渲染出来整个页面错位,折线图直接糊成一片。后来实在忍不了,开始调研在Delphi里嵌入Chrome浏览器控件,才彻底把这个问题解决掉。
所谓Chrome浏览器控件,说白了就是把Chromium内核封装成组件,塞进Delphi的窗体里用。它能干什么?简单说:你的Delphi程序里可以实时显示任何网页,并且可以和网页里的JavaScript互相通信,实现双向交互。这种需求在如今的桌面应用开发里非常常见——比如做电商ERP的,需要内嵌后台管理页面;做即时通讯的,要展示Web端的聊天窗口;做报表系统的,要把网页版的图表集成到原生窗体里。甚至是做工具类软件的,直接在界面上内嵌一个完整浏览器,用户体验和Chrome本尊几乎一致。
从技术实现来看,目前Delphi开发者能走的路主要有三条:CEF(Chromium Embedded Framework)的Delphi封装、微软官方的WebView2、以及一些商业控件库自带的Chromium组件。每一条路都有自己的脾气和适用场景,我下面会详细拆解。
这篇博文适合的人群很明确——正在被TWebBrowser折磨、想给Delphi应用加上现代浏览器能力的桌面端开发者。从一个踩过不少坑的过来人角度,我会把控件选型、环境搭建、初始化操作、JS交互和常见问题全部过一遍,提供可以直接复制的代码片段和配置方案。
2. 技术选型:CEF、WebView2怎么选才不后悔
2.1 三条技术路线对比
先说结论:如果是在Windows平台上做Delphi桌面开发,绝大多数场景下优先推荐CEF方案,具体封装是DCEF3(Delphi Chromium Embedded Framework 3)。原因后面细说。WebView2则是微软官方力推的控件,基于Edge Chromium内核,但它在Delphi里的封装成熟度不如CEF。
我自己实际测试过三条路,列个对比表给大家参考:
| 对比项 | DCEF3(CEF封装) | WebView2(Delphi封装) | TWebBrowser(IE内核) |
|---|---|---|---|
| 内核版本 | Chromium内核,可随包分发 | Edge Chromium,依赖系统或运行时 | IE内核,老旧 |
| HTML5/CSS3支持 | 完整支持 | 完整支持 | 部分支持,效果差 |
| JS双向交互 | 支持,封装较成熟 | 支持,但Delphi封装资料少 | 基本可用,但受限多 |
| 安装部署 | 需带运行库文件 | 需安装WebView2 Runtime | 系统自带 |
| 插件/扩展支持 | 支持 | 部分支持 | 不支持现代扩展 |
| Delphi版本兼容性 | 从老版本到新版本都有对应分支 | 主要支持新版Delphi | 全版本通用但性能差 |
从实际开发体验来说,DCEF3在Delphi社区里积累了十多年的使用案例,网上能搜到大量Demo和讨论帖,遇到问题往往能直接找到答案。WebView2虽然是微软亲儿子,但Delphi封装的TEdgeWebView2组件出现得比较晚,相关的稳定方案还在磨合期。
还有一个很重要的考量——部署环境。如果你的软件要发给Windows 7的用户用,WebView2对Win7的支持已经基本停止更新,而CEF内核可以自己选定一个兼容Win7的版本随包分发,这是很多做传统行业软件的人特别看重的点。热搜词里也出现了“chrome win7”这样的字眼,说明确实还有不少人在旧系统上折腾浏览器兼容问题。
2.2 为什么DCEF3是Delphi社区的“事实标准”
DCEF3实际上是CEF的Delphi语言绑定,底层通过C API调用Chromium内核,上面封装成VCL组件TChromium。它最大的优势在于——你不需要懂C++,不需要自己维护CEF的C++工程,直接在Delphi里拖一个控件,写几行代码,一个完整的Chromium浏览器就跑起来了。
我个人的体会是,DCEF3的学习曲线可以拆成两层:底层CEF的架构概念(Browser、Frame、Process)和上层的Delphi封装接口。对于不搞浏览器内核原理的普通业务开发者,其实只需要掌握TChromium控件的几个关键事件和属性,就能cover掉90%的需求。
另外,DCEF3支持扩展功能,比如拦截请求、注入JavaScript、处理下载、启用远程调试端口等。这些能力在很多偏门需求里非常能打。下面我会实际操作一遍最核心的流程。
3. 环境准备与DCEF3的安装配置
3.1 下载合适的DCEF3版本
这里有一个特别值得注意的点:DCEF3不同版本对应不同的Chromium内核版本和不同的Delphi版本,用错了编译直接报错。我在搜索资料时看到不少人在吐槽“delphi 13.1控件”相关的问题,其实很大程度上就是版本匹配没做对。
以我目前的环境为例,在Delphi 11或Delphi 12上,建议直接找DCEF3的较新分支,比如基于CEF 109或更高内核的版本。如果你的客户还在用Windows 7,就要留意内核版本不能太新——Chromium 109之后已经不再支持Win7,所以需要在“功能完整性”和“系统兼容性”之间做取舍。
下载完成后,一般会得到一个包含源码和运行库的压缩包,里面通常有cef3目录和uCEF*.pas这两个关键部分。需要把运行库文件(libcef.dll、chrome_elf.dll、icudtl.dat等)放到程序运行目录下,否则exe启动会报找不到DLL。
3.2 在Delphi中添加库路径和编译验证
操作步骤不复杂,但顺序有讲究:
- 打开Delphi,点击“Tools -> Options -> Library”,把DCEF3源码目录添加到Library path里。
- 新建一个VCL Application,在窗体上放下TChromium控件。
- 在FormCreate里写上初始化代码,设置首页地址并启动。
很多新手第一次编译DCEF3会被一堆warning吓到,其实大部分无伤大雅。真正要留意的是,如果编译报错提示找不到uCEFInterfaces之类的单元,基本就是Library path没配置正确,或者DCEF3的Delphi版本分支选错了。
procedure TForm1.FormCreate(Sender: TObject); begin // 设置浏览器主页 Chromium1.LoadURL('https://www.example.com'); end;这段代码跑起来后,界面上就会显示一个完整的Chromium浏览器窗口。不过这只是开始——实际项目里你大概率需要处理加载事件、JS交互、弹窗处理等问题。
4. 核心功能实现:加载页面、JS交互与事件控制
4.1 页面加载与生命周期管理
DCEF3的页面加载是通过Chromium1.LoadURL来触发的,但真正开发中你需要关注的往往不是加载本身,而是加载过程中的状态变化。比如在网页加载完成后再执行后续操作,或者在加载失败时给出提示,这些都要借助事件来监听。
以我做的报表项目为例,我需要等网页全部加载完,再调用JavaScript把后端传过来的JSON数据塞进图表里去。核心事件就是OnLoadEnd:
procedure TForm1.Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; httpStatusCode: Integer); begin // 确保主框架加载完成后再执行JS if (frame <> nil) and frame.IsMain then begin Memo1.Lines.Add('页面加载完成,状态码:' + IntToStr(httpStatusCode)); // 在这里执行后续JS调用 end; end;这里有个往坑里跳过的经历:开始我没判断frame.IsMain,结果网页里嵌了iframe,子框架加载完也触发了事件,导致JS被重复执行,图表被初始化了两次。后来加了主框架判断就一切正常了。
另外一个很常见的需求是自定义UserAgent,尤其是需要伪装成手机端去访问页面时。在DCEF3里可以通过OnBeforeBrowse或者初始化时的TCefSettings来设置。这是很多网页兼容性问题的“万能钥匙”——比如有些网页只给Chrome桌面端展示完整布局,而给手机UA展示精简版,调整UA就能拿到想要的效果。
4.2 JavaScript与Delphi双向通信
DCEF3的JS交互机制是整个控件最值钱的部分。它支持两个方向的数据传递:Delphi调用JS代码,以及JS调用Delphi函数。
Delphi调用JS很简单,使用frame.ExecuteJavaScript:
Chromium1.Browser.MainFrame.ExecuteJavaScript( 'document.getElementById("myDiv").innerText = "hello from delphi";', 'about:blank', 0);第二个参数是脚本来源URL,第三个是起始行号,实际使用中通常填'about:blank'和0即可。
反过来,JS要调用Delphi的函数,需要通过CEF的OnProcessMessageReceived事件配合扩展机制来实现。但更简单的方案是使用DCEF3封装的TChromium.OnConsoleMessage或者通过修改DOM事件来触发——不过正经做法还是用CEF的扩展API。
在DCEF3中,一个比较粗暴但有效的做法是在OnBeforeBrowse事件里拦截自定义协议的URL。比如JS执行:
window.location.href = 'myapp://doSomething?param=123';然后Delphi这边在OnBeforeBrowse里解析这个URL,截取参数执行对应操作。这种方法虽然不够优雅,但胜在简单直接,适合快速开发。更规范的做法是用CEF的OnProcessMessageReceived,但代码量会明显增加。
4.3 处理弹窗和新窗口
用DCEF3的开发者几乎都会遇到一个经典问题:网页里的window.open或target="_blank"链接,默认行为可能会弹出新窗口或者干脆没反应。因为DCEF3默认限制弹窗,需要自己在OnBeforePopup事件里处理。
常规的处理策略有两种:一是拦截弹窗,把新页面加载到当前控件里;二是在原窗口浏览器中新建标签页加载。对于桌面程序来说,如果只内嵌一个页面,通常选择第一种,否则会让用户感觉像开了两个浏览器。
procedure TForm1.Chromium1BeforePopup(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; const targetUrl, targetFrameName: uCEFTypes.ustring); begin // 阻止弹出新窗口,在当前浏览器中打开 Chromium1.LoadURL(targetUrl); // 设置一个标志让OnBeforePopup最后取消弹窗 end;写到这里必须提醒一个容易忽略的点:DCEF3在关闭程序时如果没处理好释放逻辑,很容易崩溃或产生僵尸进程。我的做法是在FormCloseQuery里先调用Chromium1.ShutdownDragAndDrop或类似的清理方法,然后再关闭窗体,并用任务管理器确认进程已经退出。
5. 实战经验:Chrome插件机制与桌面控件的深度整合
5.1 DCEF3支持Chrome扩展吗
很多人在搜索“chrome插件”和“chrome://extensions”相关词时,其实是希望让内嵌浏览器也能支持插件。这里必须说明:DCEF3基于CEF,而CEF对Chrome扩展API的支持并不完整。它支持部分扩展能力,尤其是一些注入型的脚本扩展(通过--load-extension参数加载),但像浏览器级别的书签管理、下载管理这类扩展是不完整的。
我实际的测试结果是这样的:普通的内容脚本(content_script)类型的插件,比如自动填充、页面样式修改,在DCEF3里基本能跑。但如果你依赖Chrome的高级API(比如chrome.tabs、chrome.storage),在CEF内嵌环境里就不一定好使。热搜词里那条“chrome更新之后历史记录还在但是书签插件全没了”,恰恰说明浏览器插件生态和浏览器内核版本深度绑定,稍有变动就可能失效——在内嵌控件里,这种脆弱性一样存在。
所以我的建议是:如果你的业务确实需要完整的Chrome插件生态,那不该用内嵌控件,老老实实用Google Chrome本体。但如果只是需要“网页展示+轻量自定义脚本+原生窗体交互”,DCEF3完全够用,没必要贪插件功能而引入不必要的复杂度。
5.2 密码自动填充与Cookie保持
热搜词里还出现了一条非常典型的场景:“chrome保持网站登录状态不掉线”。在Delphi内嵌浏览器里实现类似功能,本质上就是管理Cookie的持久化。
DCEF3支持通过TCefCookieManager来管理Cookie。你可以指定一个持久化目录,让浏览器记住登录状态。关键是在初始化CEF时设置cache_path参数——只要设置了有效的缓存目录,Cookie和LocalStorage都会被持久化到磁盘,下次启动程序时登录状态自然还在。
// 在初始化CEF之前,设置cache路径 CefCachePath := '.\webcache\';但这里有一个陷阱:如果同时开了多个Chromium实例指向同一个cache目录,会导致Cookie互相覆盖甚至文件锁死。解决方案很简单——根据用户或业务模块设置不同的缓存子目录。比如webcache\user001\和webcache\user002\,互不干扰。
热搜词里还有“chrome保持网站登录状态不掉线”“chrome 允许 apple 事件中的 javascript”这类词,后者说的是苹果生态里的JS桥接问题,和Delphi关系不大,但思路是一致的——浏览器控件和系统之间的交互,本质上都是通过桥接层完成的,DCEF3的OnProcessMessageReceived就是Delphi版本的那个桥。
5.3 截屏与页面录制应用场景
桌面应用集成浏览器控件之后,一个特别实用的衍生功能就是页面截图。DCEF3可以使用TakeSnapshot方法对当前页面进行截图,这在做自动化测试、网页数据留存、合同签署留痕等场景下非常有用。
Chromium1.TakeSnapshot(0, True, 0);截图完成的结果会通过OnSnapshot事件返回。这个方法在跑无头浏览器任务时尤其好用——比如程序后台定时打开某个监控页面,截个图存档,然后关掉页面。整个过程不需要真的弹出一个可见窗口。
同理,如果做OCR需求(热搜词“delphi ocr”),经常需要先对页面截屏,再做图像识别。这一步和内嵌控件结合起来就很顺畅:先加载页面,等数据渲染完,然后截屏,最后把图片交给OCR引擎处理。整个链路在Delphi里完全跑得通。
6. 常见问题排查与性能优化技巧
6.1 崩溃、白屏和加载失败的排查思路
DCEF3在实际使用中最大的问题就是崩溃。这种崩溃往往不是Delphi代码导致的,而是Chromium多进程模型的子进程崩了。遇到这种情况,先别急着改代码,按下面几步排查:
- 检查运行目录下有没有完整的运行库文件,特别是
libcef.dll是否匹配DCEF3版本。 - 检查是否有多个CEF实例占用了同一个
cache_path目录。 - 关闭杀毒软件或把程序目录加入白名单试试,部分杀软会拦截CEF的行为导致白屏。
- 试试在
OnRenderProcessTerminated事件里弹个提示,至少让用户知道页面崩了而不是程序卡死。
DCEF3崩溃后的恢复方案,我目前最常用的是检测到崩溃后自动重启浏览器控件并重新加载页面。虽然不太优雅,但能够保住应用的整体可用性。
6.2 内存占用过高和进程残留
内嵌Chromium意味着你的应用会多出几个进程——主进程、GPU进程、渲染进程、网络进程。热搜词里有一句“chrome已阻止不安全的下载怎么关闭”,其实是Chrome本身的一个安全策略,但在内嵌控件里,类似的进程管理策略也会影响你的程序表现。
内存优化方面,一个高效的做法是在OnBeforeClose里确保进程被正确清理。另一个务实方案是:如果应用长时间不操作网页,可以主动调用SaveAs类似的缓存操作或者干脆销毁控件、按需重建。毕竟客户看重的是稳定,不是任务管理器里那一两个进程的数目。
还有个小坑是定期检查是否有残留的chrome.exe类进程——CEF异常退出时,子进程有时候不会被自动回收。我在开发环境就遇到过明明程序都关了,但CPU还时不时占用10%的情况,最后发现就是残留的CEF子进程在作怪。解决方法是启动时先检测并清理同目录下上次运行遗留的子进程,或者用Job Object把子进程绑定到主进程生命周期上。
6.3 JS注入的时机与执行失败
热搜词里有一条“delphi 正则表达式”,通常会和浏览器控件结合做页面内容提取。但注入JS时最常出现的问题就是时机不对——网页还没加载完就去执行JS,自然拿不到想要的DOM元素。
我一般习惯用OnLoadEnd+ 等待条件轮询的双保险策略:在OnLoadEnd触发后,用ExecuteJavaScript检查目标元素是否存在,如果不存在则隔几百毫秒再执行一次,最多重试10次左右。这样可以兼容不同网页的异步加载逻辑,尤其是那些用了Vue、React这类SPA框架的页面,DOM渲染是异步的,单靠OnLoadEnd完全不够。
7. 从控件到落地:一个典型业务场景的完整演练
7.1 场景设定
为了把前面这些知识点串起来,我拿一个真实做过的需求给大家拆解:开发一个企业内部的数据决策系统,要求桌面客户端(Delphi开发)内嵌一个数据可视化平台,能展示实时报表,同时保留登录状态,并且业务人员可以通过“一键导出”按钮把当前报表页面截图保存到本地。
这个需求拆解下来,正好对应了前面讲的所有功能点:内嵌Chromium、Cookie持久化、JS注入(触发图表渲染)、截图、保存文件。整个链路用DCEF3实现,核心代码量不超过200行。
7.2 实现过程与关键代码
第一步,搭建窗体,放一个TChromium控件和一个“截图导出”按钮。第二步,在初始化时设置缓存目录。第三步,在OnLoadEnd里注入业务JS,让页面自动选择今天的数据并渲染图表。第四步,在按钮事件里调用截图方法,并把截图保存为PNG。
procedure TForm1.btnSaveScreenshotClick(Sender: TObject); var Bmp: TBitmap; begin // 等待截图事件返回后保存 FWaitForSnapshot := True; Chromium1.TakeSnapshot(0, True, 0); end; procedure TForm1.Chromium1Snapshot(Sender: TObject; const browser: ICefBrowser; const bitmap: TBitmap); begin if FWaitForSnapshot then begin bitmap.SaveToFile('D:\reports\screenshot_' + FormatDateTime('yyyymmddhhnnss', Now) + '.png'); FWaitForSnapshot := False; end; end;这套代码在实际项目中运行得非常稳定,客户用了一年多,几乎没有因为浏览器控件本身出现过问题。唯一一次翻车是Windows更新后,有个客户机器的显卡驱动不兼容导致WebGL渲染异常,后来在CEF初始化参数里关闭了GPU硬件加速,就恢复了。这也是一个值得留意的小参数:
// 初始化时设置禁用GPU加速 CefSettings.MultiThreadedMessageLoop := True; // 在某些环境可以尝试关闭硬件加速避免渲染问题 // CefSettings.EnableGPUAcceleration := False;8. 最后补充几个我在实际项目里总结的偏方
DCEF3的官方文档其实是比较少的,很多细节要靠社区和猜。我把自己沉淀下来的一些偏方列在这里,算不上什么高深理论,但确实能在关键时候救命。
第一个偏方:开发时打开Chrome的开发者工具调试。DCEF3可以通过设置remote_debugging_port来开启远程调试,然后在外部Chrome浏览器访问http://localhost:端口,就能看到内嵌页面的DOM结构和控制台输出。这对排查网页渲染问题帮助巨大,比在Delphi里各种猜测高效得多。
第二个偏方:如果网页里有SSL证书校验的问题(热搜词里那条“chrome 您的连接不是私密连接提示后无法强制打开”非常典型),在内嵌控件里是不能像浏览器那样点“高级->继续前往”的。解决方案有两个方向:一是调整CEF的SSL设置允许忽略证书错误,二是在初始化时加载自定义证书或使用HTTP测试环境。生产环境我建议必须处理好证书链,不要为了省事全局忽略SSL校验,否则迟早掉坑。
第三个偏方:多人开发时,DCEF3的源码改动很容易出现同步问题。建议把DCEF3的代码作为一个独立的submodule或静态库管理,业务层只引入编译后的DCU文件,避免多人同时改DCEF3导致各种莫名其妙的编译错误。
我在实际项目里摸索出来的经验是,DCEF3这套方案的上限在于你对Chromium本身的理解。如果你愿意花时间研究CEF官方文档,能解锁的能力远不止“显示网页”这么简单——比如自定义协议处理、拦截并改写网络请求、渲染离线页面、多浏览器实例协同等。这些都是内嵌浏览器控件的高级玩法,一旦掌握,Delphi在桌面端的表现力会直接拉开同龄人一个档次。
本文还有配套的精品资源,点击获取