简介:这份资源是面向Delphi开发者的CEF4Delphi控件包,用于在Delphi 7等版本中嵌入Chrome内核,实现浏览器功能集成。包内包含CEF4Delphi-master源码工程与cef_binary_101.0.18对应Windows 32位运行库,覆盖从控件安装到Demo编译的完整链路,适合需要在桌面程序中调用网页渲染、做混合开发的中高级开发者。资源共1330个文件,以pas源码、bat脚本、inc头文件、dproj与dpr工程文件、dfm与lfm窗体文件为主,另有res、png、ico等资源文件及html、json、txt说明文档,压缩包约229.27MB,目录按源码、运行库、示例分层组织。目前已有2819人学习下载。按说明解压后安装控件,将Release与Resources内容拷入bin目录,即可编译demo验证效果,便于快速上手并二次开发。
1. 老 Delphi 项目想用 Chromium 内核,CEF4Delphi 到底解决了什么
手上维护着一套跑了十几年的 Delphi 7 业务系统,界面还停留在 TStringGrid 加 TButton 的年代,客户却开始要求内嵌网页、加载在线报表、对接第三方 Web 端页面。直接换框架重写不现实,成本高、风险大,最省事的路径就是往现有窗体里塞一个浏览器控件。系统自带的 TWebBrowser 走的是 IE 内核,很多现代前端页面直接白屏或者脚本报错,这条路基本堵死。CEF4Delphi 就是在这种场景下被翻出来的:它把 Chromium Embedded Framework 封装成 Delphi 能直接引用的组件,让老版本 Delphi 也能用上 Chromium 内核。标题里说的「支持 Delphi7」,对还在用老 IDE 的团队来说,意味着不用升级编译器就能把浏览器能力接进来。这篇笔记就按「怎么选版本、怎么装、怎么跑通第一个页面、参数怎么调、坑在哪」的顺序,把这条路径讲清楚,适合手上有存量 Delphi 项目、又必须嵌入现代网页的开发者。
2. 选型与准备:CEF4Delphi 在 Delphi7 下的版本匹配逻辑
2.1 为什么老 Delphi 只能用特定分支
CEF4Delphi 本身是一套 Pascal 封装,但它的可用性受两个东西夹击:一是 Delphi 编译器版本,二是 CEF 二进制运行库版本。Delphi 7 的编译器不支持泛型、不支持 Unicode 字符串(默认 AnsiString),而新版 CEF4Delphi 大量使用了泛型和 Unicode 相关特性,直接拿最新源码在 D7 下编译会报一堆语法错误。所以「支持 Delphi7」并不是指任意版本都能编,而是指仓库里保留了兼容老编译器的分支或历史版本。常见做法是找那些明确标注支持 D7 的发布包,或者用较早期的 CEF4Delphi 版本配合对应版本的 CEF 运行库。这里有个硬约束:CEF 运行库的版本不能随便挑最新的,越新的 CEF 对编译器和系统 API 要求越高,D7 能带动的往往是偏旧的 CEF 分支。
选型时我一般按这个顺序确认:先确定目标机器的最低 Windows 版本(XP 还是 Win7 起步),再反推能用的 CEF 大版本,最后找与该 CEF 版本配套的 CEF4Delphi 发布包。三者对不上,后面全是玄学崩溃。
2.2 目录结构与必须存在的文件
CEF4Delphi 跑起来不是只有几个 .pas 就够,它依赖一整套 CEF 二进制文件。典型目录长这样:
| 目录/文件 | 作用 | 缺失后果 |
|---|---|---|
| source/ | 封装单元的 .pas 源码 | 无法编译 |
| bin/ | libcef.dll 等核心运行库 | 启动即报错 |
| bin/locales/ | 各语言资源包 | 界面文字异常 |
| bin/*.pak | 资源包 | 页面渲染异常 |
| bin/subprocess.exe | 子进程宿主 | 多进程模式起不来 |
Delphi 7 项目编译出来的 exe 必须和这些文件放在同一目录,或者把 bin 目录加入系统 PATH。很多人第一次跑失败,就是只拷了 exe 没拷 libcef.dll。
2.3 安装到 IDE 的最小步骤
把封装单元加入项目有两种方式:一是直接把 source 目录下的 .pas 文件 Add to Project,二是编译成包安装到组件面板。D7 下我更推荐第一种,减少包安装带来的路径问题。操作步骤:
- 新建或打开现有 VCL 工程。
- 把 CEF4Delphi 的 source 目录加入工程搜索路径(Project → Options → Directories/Conditionals)。
- 在需要用到浏览器的单元 uses 里引用核心单元,例如 uCEFApplication、uCEFChromium、uCEFWindowParent。
- 把 bin 目录下的运行库复制到工程输出目录。
// 在工程主单元的 uses 中加入 CEF 相关单元 uses uCEFApplication, // 应用级初始化与关闭 uCEFChromium, // 浏览器核心组件 uCEFWindowParent; // 承载浏览器的窗口容器这段 uses 是后续所有操作的前提。uCEFApplication 负责全局的 CEF 初始化和消息循环,uCEFChromium 是真正干活的浏览器对象,uCEFWindowParent 提供一个能嵌进 VCL 窗体的父容器。三者缺一,要么编译不过,要么运行时空指针。
3. 跑通第一个页面:从初始化到加载 URL 的完整链路
3.1 全局初始化放在哪里
CEF 要求进程启动时做一次全局初始化,位置很关键。放在主窗体的 FormCreate 里通常来不及,因为此时消息循环还没建立。常见做法是在工程文件(.dpr)里,Application.Initialize 之后、创建主窗体之前调用初始化函数。
// 工程文件中的初始化顺序 begin Application.Initialize; // 初始化 CEF,必须在创建任何浏览器组件之前 if not CefLoadLibDefault then begin MessageBox(0, 'CEF 初始化失败', '错误', MB_ICONERROR); Exit; end; Application.CreateForm(TMainForm, MainForm); Application.Run; end.CefLoadLibDefault 会按默认规则去当前目录找 libcef.dll 和资源文件。返回 False 说明运行库没找齐,先查 bin 目录是否完整。这里不要急着往下走,初始化失败还强行创建浏览器组件,后面必然是访问违例。
3.2 在主窗体里创建浏览器组件
初始化通过后,就可以在主窗体上放一个 TCEFWindowParent 和一个 TCEFChromium,用代码把它们关联起来。设计期拖控件也行,但 D7 下我倾向代码创建,路径更可控。
procedure TMainForm.FormCreate(Sender: TObject); begin // 创建承载窗口,Parent 指向窗体 FWindowParent := TCEFWindowParent.Create(Self); FWindowParent.Parent := Self; FWindowParent.Align := alClient; // 创建浏览器核心 FChromium := TCEFChromium.Create(Self); FChromium.WindowParent := FWindowParent; // 创建浏览器并加载页面 FChromium.CreateBrowser(FWindowParent); FChromium.LoadURL('https://www.example.com'); end;CreateBrowser 负责在指定父窗口里建立浏览器实例,LoadURL 触发导航。注意 CreateBrowser 是异步的,调用返回不代表页面已经渲染完成,后续要拿页面状态得靠事件。
3.3 用事件确认页面真的加载成功
判断「跑通」不能只看窗口有没有弹出来,要看加载事件。绑定 OnLoadEnd 是最直接的方式。
procedure TMainForm.FormCreate(Sender: TObject); begin // ... 前面的创建代码 ... FChromium.OnLoadEnd := ChromiumLoadEnd; end; procedure TMainForm.ChromiumLoadEnd(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; httpStatusCode: Integer); begin // 只在主框架加载结束时提示,避免子框架重复触发 if (frame <> nil) and frame.IsMain then Caption := '页面加载完成,状态码:' + IntToStr(httpStatusCode); end;frame.IsMain 这个判断很重要。现代页面里 iframe 很多,不加判断会在每个子框架加载完都触发一次,日志刷屏还容易误判。httpStatusCode 能帮你区分是页面正常返回还是服务器报错。
3.4 关闭时的资源释放顺序
CEF 对释放顺序敏感,顺序错了退出时容易卡死或报内存错误。正确顺序是先关浏览器,再关承载窗口,最后做全局清理。
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction); begin // 先销毁浏览器 if FChromium <> nil then begin FChromium.CloseBrowser(True); FChromium := nil; end; // 再销毁承载窗口 if FWindowParent <> nil then begin FWindowParent.Free; FWindowParent := nil; end; end;CloseBrowser(True) 的参数表示强制关闭,会等待浏览器进程退出。如果这里传 False,某些情况下浏览器进程会残留,任务管理器里能看到一堆子进程。
4. 参数调优与常见能力接入
4.1 多进程还是单进程
CEF 默认多进程,渲染、GPU、网络各跑各的进程,稳定但内存占用高。老机器或者需要简化部署时,可以切单进程模式。
// 在全局初始化之前设置单进程模式 CefSingleProcess := True; if not CefLoadLibDefault then ...单进程模式下所有工作在一个进程里,调试方便,但一个页面崩溃会拖垮整个应用。生产环境我一般还是用多进程,除非目标机器资源实在紧张。这个开关必须在 CefLoadLibDefault 之前设置,之后再改无效。
4.2 缓存目录与用户数据隔离
默认缓存会写到系统临时目录,多实例运行时可能互相干扰。指定独立缓存目录能避免这类问题。
// 设置缓存路径,需在初始化前完成 CefCache := ExtractFilePath(Application.ExeName) + 'cache';CefCache 指向的目录要有写权限。如果放在 Program Files 下,普通用户没写权限,缓存会静默失败,表现为每次打开都重新加载、登录状态丢失。放到用户目录或程序自己的可写目录更稳妥。
4.3 常用参数速查
| 参数 | 作用 | 建议值 |
|---|---|---|
| CefSingleProcess | 单/多进程模式 | 生产用 False |
| CefCache | 缓存目录 | 程序可写目录 |
| CefLogFile | 日志文件路径 | 排错时开启 |
| CefRemoteDebuggingPort | 远程调试端口 | 排错时设 9222 |
| CefBackgroundColor | 页面背景色 | 按 UI 配色 |
CefRemoteDebuggingPort 在排查页面问题时特别有用,设好端口后用 Chromium 内核的浏览器访问调试地址,能看到页面控制台和网络请求,相当于给内嵌浏览器开了个黑匣子。
4.4 与 Delphi 代码双向通信
页面里的 JS 要调 Delphi,或者 Delphi 要执行页面 JS,靠的是扩展机制。注册一个扩展对象,页面通过 window.xxx 访问。
// 注册一个供 JS 调用的对象 procedure TMainForm.FormCreate(Sender: TObject); begin // ... 创建浏览器 ... FChromium.OnRegisterCustomSchemes := nil; end; // 在浏览器创建后注册扩展 procedure TMainForm.ChromiumAfterCreated(Sender: TObject; const browser: ICefBrowser); begin // 注册扩展对象,JS 侧通过 window.myExt 调用 FChromium.RegisterExtension('myExt', 'native function ping();'); end;RegisterExtension 的第二个参数是 JS 侧的声明,实际函数实现要配合 OnProcessMessageReceived 处理消息。这套机制比直接注入脚本稳定,适合做长期维护的接口。
5. 避坑与排查:D7 下最容易翻车的几个点
5.1 现象:编译报「Unsupported language feature」
原因:拿到的 CEF4Delphi 源码用了泛型或 Unicode 字符串,D7 编译器不认。 解决:换用明确支持 D7 的历史版本,或者只引入不依赖这些特性的核心单元,把用不到的封装文件从工程里移除。
5.2 现象:程序启动直接退出,无任何提示
原因:libcef.dll 缺失或版本与封装单元不匹配。 解决:确认 bin 目录完整,且 CEF 运行库版本与 CEF4Delphi 版本对应。用 CefLogFile 指定日志文件,看初始化阶段的具体报错。
5.3 现象:页面能打开但中文乱码
原因:D7 默认 AnsiString,而 CEF 内部走 UTF-8,字符串转换环节丢了编码。 解决:所有传给 CEF 的字符串显式转成 UTF-8,接收回来的也按 UTF-8 解码。封装单元里通常有转换函数,别直接传 AnsiString。
5.4 现象:关闭程序后进程残留
原因:浏览器没走正常关闭流程,或者 CloseBrowser 参数传了 False。 解决:在 FormClose 里按顺序释放,CloseBrowser 传 True,必要时在工程退出前调用全局清理函数。
5.5 现象:多开几个页面内存暴涨
原因:多进程模式下每个浏览器实例都有独立进程,缓存没限制。 解决:限制同时打开的浏览器数量,复用同一个浏览器实例切换 URL,而不是反复创建销毁。
6. 进阶:把内嵌浏览器做成可维护的模块
跑通第一个页面只是起点,真正要落地得考虑维护性。我的习惯是把浏览器相关逻辑收进一个独立的单元,对外只暴露「打开 URL」「执行 JS」「注册回调」几个方法,主窗体不直接碰 CEF 组件。这样以后升级 CEF4Delphi 版本,改动范围可控。
验证模块是否健康,我一般看三个指标:连续打开关闭 50 次不残留进程、切换 20 个不同页面不崩溃、内存占用在稳定后不再持续增长。这三个过了,基本能上生产。
// 封装后的对外接口示意 type TBrowserHost = class public procedure Open(const AUrl: string); procedure ExecJS(const AScript: string); property OnPageLoaded: TNotifyEvent; end;这个封装层把 CEF 的细节挡在外面,业务代码只跟 TBrowserHost 打交道。升级时只改这一层,业务逻辑不动。血泪经验是:千万别让 CEF 组件散落在各个窗体里,后期维护会非常痛苦。
最后一个具体技巧:调试页面问题时,先开 CefRemoteDebuggingPort,用调试工具看控制台报错,比在 Delphi 里加日志快得多。很多「页面白屏」其实是 JS 报错,跟 Delphi 侧没关系。希望帮到你。
本文还有配套的精品资源,点击获取