
简介这份资源是面向Delphi开发者的CEF4Delphi控件包用于在Delphi 7等版本中嵌入Chrome内核实现浏览器控件的调用与网页渲染。包内包含CEF4Delphi-master源码工程与cef_binary_101.0.18对应Windows 32位运行库覆盖从控件安装到Demo编译的完整链路适合需要在桌面程序中集成现代浏览器、处理HTML5页面或做前端混合开发的中高级开发者。资源共1330个文件以pas源码、bat批处理、inc头文件、dproj与dpr工程文件、dfm与lfm窗体文件为主另有res、png、ico等资源文件及少量html、json、sh脚本压缩包约229.27MB目录按源码、运行库、示例工程分层组织。目前已有2819人学习下载。按说明解压并拷贝Release与Resources内容至bin目录后即可编译demo文件夹中的示例快速验证浏览器控件在Delphi中的加载、导航与交互效果为二次封装和项目集成提供可运行的基础环境。1. 在 Delphi 里塞进一个 ChromeCEF4Delphi 最新版到底能解决什么如果你手上维护着一套 Delphi 老系统界面还停留在 TWebBrowser 或者 EmbeddedWB 那个年代那你一定遇到过这些场景客户要求内嵌一个能跑现代前端框架的页面结果 TWebBrowser 直接白屏想加载一个带 ES6 语法的后台管理界面IE 内核连箭头函数都不认识更别提 WebGL、WebSocket、CSS Grid 这些现代前端标配了。CEF4Delphi 就是冲着这个痛点来的——它把 Chromium Embedded Framework 封装成 Delphi 组件让你在 Delphi 的 Form 上直接放一个基于 Chromium 内核的浏览器窗口前端能跑什么它就能跑什么。这个最新版最让我意外的一点是它仍然保留了对 Delphi 7 的支持。要知道 Delphi 7 是 2002 年的产物很多现代组件库早就把它踢出支持列表了。但 CEF4Delphi 的源码里依然能看到为老版本编译器准备的兼容代码条件编译指令写得密密麻麻。这意味着那些还在用 Delphi 7 维护存量项目的团队不需要整体迁移到 Delphi 11 或 12就能把 Chrome 内核嵌进现有系统。适合谁一是维护 Delphi 老项目的工程师二是想用 Delphi 快速做桌面端壳子但需要现代 Web 渲染能力的开发者三是需要把 Web 页面和本地 Delphi 逻辑做深度交互的场景。2. CEF4Delphi 的架构拆解从 Chromium 进程模型到 Delphi 组件封装2.1 为什么不是简单包一层 TWebBrowser很多人第一次接触 CEF4Delphi 会有一个误解以为它跟 TWebBrowser 一样就是一个 ActiveX 控件的 Delphi 包装。实际上完全不是一回事。TWebBrowser 用的是系统自带的 IE 内核进程模型是单进程的你的 Delphi 程序和网页渲染跑在同一个进程空间里。网页一崩你的程序跟着崩网页里一个死循环你的主线程直接卡死。CEF4Delphi 走的是 Chromium 的多进程架构。你的 Delphi 程序是主进程Browser ProcessChromium 会另外拉起渲染进程Render Process、GPU 进程、网络进程等。网页的 HTML 解析、JavaScript 执行、CSS 布局都在渲染进程里完成主进程只负责窗口管理和消息调度。这个架构带来的直接好处是网页崩溃不会拖垮你的 Delphi 程序你可以在渲染进程挂掉之后重新加载页面。但代价也很明显。多进程意味着更多的内存占用一个简单的 CEF4Delphi 窗口起步就是 100MB 以上的内存。而且进程间通信IPC有延迟如果你在 Delphi 端和 JavaScript 端之间频繁传大量数据性能会比单进程方案差。我一般会建议如果只是显示一个静态的本地 HTML 帮助文档TWebBrowser 够用了但凡涉及现代前端框架、WebGL 或者需要跟 JS 做复杂交互才上 CEF4Delphi。2.2 核心单元和组件结构CEF4Delphi 的源码包里有几个关键单元理解它们的分工能帮你少走很多弯路单元名职责你什么时候会用到uCEFApplication全局初始化和关闭程序启动时调用 GlobalCEFApp.StartMainProcessuCEFChromium核心浏览器组件放在 Form 上承载网页uCEFWindowParent窗口容器配合 TChromium 使用处理窗口句柄uCEFBrowser浏览器实例封装需要多标签页时用uCEFInterfaces接口定义自定义事件处理时引用uCEFConstants常量定义设置参数时查常量值最常见的用法是在 Form 上放一个 TChromium 和一个 TCEFWindowParent然后在 FormCreate 里调用 TChromium.CreateBrowser。TCEFWindowParent 负责提供一个窗口句柄给 Chromium 渲染用TChromium 负责实际的浏览器逻辑。2.3 初始化流程顺序错了就是黑屏CEF4Delphi 的初始化顺序非常讲究顺序错了轻则黑屏重则直接崩溃。标准流程是这样的// 第一步在项目文件.dpr中初始化全局设置 program Project1; uses Vcl.Forms, uCEFApplication, // 必须引用 Unit1 in Unit1.pas {Form1}; {$R *.res} begin // 全局CEF设置必须在Application.Initialize之前 GlobalCEFApp : TCefApplication.Create; GlobalCEFApp.Locale : zh-CN; // 界面语言 GlobalCEFApp.Cache : cache; // 缓存目录 GlobalCEFApp.LogFile : debug.log; // 调试日志 GlobalCEFApp.LogSeverity : LOGSEVERITY_INFO; if GlobalCEFApp.StartMainProcess then begin Application.Initialize; Application.MainFormOnTaskbar : True; Application.CreateForm(TForm1, Form1); Application.Run; end; GlobalCEFApp.Free; end.这段代码的关键点在于StartMainProcess的返回值判断。Chromium 的多进程架构意味着你的 exe 会被多次启动——主进程启动一次渲染进程、GPU 进程也会用同一个 exe 启动。StartMainProcess返回 True 表示当前是主进程返回 False 表示当前是子进程子进程不需要创建 Form直接退出即可。如果你忽略了这个判断子进程也会去创建 Form结果就是一堆莫名其妙的窗口和崩溃。2.4 在 Form 中创建浏览器实例全局初始化完成后在 Form 里创建浏览器procedure TForm1.FormCreate(Sender: TObject); begin // 先创建窗口容器再创建浏览器 Chromium1.CreateBrowser(CEFWindowParent1); end; procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin // 关闭前必须先销毁浏览器否则会内存泄漏 CanClose : False; Chromium1.CloseBrowser(True); end; procedure TForm1.Chromium1BeforeClose(Sender: TObject); begin // 浏览器销毁完成后才能关闭Form DestroyWindow(CEFWindowParent1.Handle); Application.Terminate; end;这里有个血泪经验CloseBrowser是异步的调用之后浏览器不会立刻销毁而是会在后台走完销毁流程后触发BeforeClose事件。如果你在FormCloseQuery里直接CanClose : TrueForm 先销毁了但 Chromium 的销毁流程还在跑访问已经释放的窗口句柄就会报 Access Violation。正确的做法是在BeforeClose事件里再调Application.Terminate。3. 从零跑通第一个 CEF4Delphi 窗口环境配置与参数调优3.1 获取源码与目录结构CEF4Delphi 的源码包拿到手之后先别急着打开工程。目录结构大致是这样的根目录下有source文件夹存放所有 Pascal 单元demos文件夹里有几十个示例工程bin文件夹存放 CEF 的二进制文件libcef.dll、chrome_elf.dll 等。很多人翻车就翻在 bin 目录上——CEF4Delphi 的 Pascal 代码是开源的但 CEF 的二进制文件需要单独下载版本必须和源码匹配。我一般会先看source目录下的uCEFConstants.pas里面有一个CEF_VERSION常量记下这个版本号然后去 CEF 的官方构建页面找对应版本的二进制包。版本不匹配的典型症状是程序启动时直接弹出一个错误框说 The CEF binary is not compatible或者更隐蔽的——程序能启动但网页渲染花屏。3.2 编译前的关键设置Delphi 7 和较新版本 Delphi 的编译设置差异很大这里分开说。Delphi 7 用户需要注意CEF4Delphi 的源码里用了不少 Delphi 7 不支持的语法特性但作者通过条件编译做了兼容。你需要确保在 Project Options 里打开了{$H}长字符串和{$J}可写常量。另外 Delphi 7 的 Unicode 支持是硬伤CEF4Delphi 在 Delphi 7 下会用 WideString 做转换性能会打折扣但功能是完整的。Delphi 10.4 及以上版本的用户确保在 Project Options 里把Runtime Library的Link with runtime packages关掉CEF4Delphi 需要静态链接。另外Output Directory要设置到 bin 目录下或者把 bin 目录下的所有 DLL 复制到你的 exe 输出目录。# 编译后检查输出目录是否包含这些关键文件 # 缺少任何一个都会导致启动失败 libcef.dll # CEF核心库必须和源码版本匹配 chrome_elf.dll # Chromium的ELF加载器 icudtl.dat # 国际化数据 cef.pak # CEF资源包 devtools_resources.pak # 开发者工具资源 locales/ # 语言包目录3.3 加载第一个页面并验证渲染编译通过之后先别急着加载复杂的线上页面。我一般会先用一个最简单的本地 HTML 做验证procedure TForm1.FormShow(Sender: TObject); begin // 加载本地HTML文件验证基本渲染能力 Chromium1.LoadURL(file:///C:/test/hello.html); end; procedure TForm1.Chromium1LoadEnd(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; httpStatusCode: Integer); begin // 页面加载完成后在标题栏显示状态码 Caption : CEF4Delphi - LoadEnd: IntToStr(httpStatusCode); end;LoadEnd事件里的httpStatusCode是个很有用的调试信息。如果是本地文件状态码通常是 0如果是 HTTP 请求200 表示成功404 表示路径不对。如果LoadEnd一直不触发大概率是 CEF 的二进制文件没放对位置或者GlobalCEFApp的初始化参数有问题。3.4 常用参数调优CEF4Delphi 的GlobalCEFApp有一堆参数可以调下面这几个是我在实际项目里改得最多的参数默认值建议值作用Cache空cache设置缓存目录空则禁用缓存LogFile空debug.log调试日志路径排查问题时必开LogSeverityLOGSEVERITY_DISABLELOGSEVERITY_INFO日志级别调试时用INFOLocaleen-USzh-CN界面语言影响右键菜单MultiThreadedMessageLoopFalseTrueWindows多线程消息循环提升响应性WindowlessRenderingEnabledFalse按需离屏渲染做截图或特殊效果时开MultiThreadedMessageLoop这个参数值得单独说。默认情况下 CEF 的消息循环和 Delphi 的主消息循环是合并的设置成 True 之后 CEF 会用自己的线程跑消息循环。在 Windows 上开启这个选项通常能提升页面滚动的流畅度但代价是你不能再直接从 Delphi 主线程调用某些 CEF 接口必须通过TCEFThread或者QueueAsyncCall来调度。4. Delphi 与 JavaScript 双向通信从简单调用到复杂数据交换4.1 扩展方式的选择V8 扩展还是 JS 注入CEF4Delphi 提供了两种 Delphi 和 JavaScript 通信的方式选错了后面会很难受。第一种是 V8 扩展RegisterExtension在 CEF 初始化的时候注册一段 JavaScript 代码这段代码可以调用 Delphi 端注册的原生函数。优点是性能好因为 V8 引擎直接识别这些函数缺点是注册时机早必须在浏览器创建之前完成而且调试不方便。第二种是 JS 注入ExecuteJavaScript在页面加载完成后通过frame.ExecuteJavaScript往页面里注入代码。优点是灵活想什么时候注入就什么时候注入缺点是每次注入都有开销而且如果页面自己也有同名函数容易冲突。我一般会这样选如果是框架级的、全局都要用的接口比如获取当前登录用户信息用 V8 扩展如果是页面级的、跟具体业务相关的交互用 JS 注入。4.2 V8 扩展的注册与调用先看 V8 扩展的注册方式// 在GlobalCEFApp初始化之后、StartMainProcess之前注册 procedure RegisterV8Extensions; begin GlobalCEFApp.RegisterExtension( v8/DelphiBridge, // 扩展名称 // 扩展的JavaScript代码 var DelphiBridge { getAppVersion: function() { native function GetAppVersion(); return GetAppVersion(); }, showMessage: function(msg) { native function ShowMessage(); return ShowMessage(msg); } };, // 原生函数实现 procedure(const extension: ICefV8Context; const registrar: ICefV8Handler) begin registrar.AddFunction(GetAppVersion, procedure(const name: ustring; const obj: ICefV8Value; const arguments: TCefV8ValueArray; var retval: ICefV8Value; var exception: ustring) begin retval : TCefV8ValueRef.NewString(1.0.0); end); registrar.AddFunction(ShowMessage, procedure(const name: ustring; const obj: ICefV8Value; const arguments: TCefV8ValueArray; var retval: ICefV8Value; var exception: ustring) begin if Length(arguments) 0 then ShowMessage(arguments[0].GetStringValue); retval : TCefV8ValueRef.NewNull; end); end); end;这段代码注册了一个叫DelphiBridge的全局对象JavaScript 端可以直接调用DelphiBridge.getAppVersion()和DelphiBridge.showMessage(hello)。注意native function的声明方式——这是 CEF 特有的语法告诉 V8 引擎这个函数是原生实现的不是 JavaScript 函数。4.3 JS 注入与回调处理JS 注入的方式更直接procedure TForm1.Button1Click(Sender: TObject); begin // 向当前页面注入JavaScript代码 if Chromium1.Browser nil then Chromium1.Browser.MainFrame.ExecuteJavaScript( document.getElementById(result).innerText 来自Delphi的消息;, about:blank, // 脚本来源URL 0 // 起始行号 ); end; // 如果需要获取JavaScript的返回值用回调方式 procedure TForm1.Button2Click(Sender: TObject); begin if Chromium1.Browser nil then Chromium1.Browser.MainFrame.ExecuteJavaScript( JSON.stringify({name: test, value: 123}), about:blank, 0 ); end; // 在Chromium1ConsoleMessage事件中接收JS的console输出 procedure TForm1.Chromium1ConsoleMessage(Sender: TObject; const browser: ICefBrowser; level: TCefLogSeverity; const message, source: ustring; line: Integer); begin // 把JS的console.log输出转发到Delphi的Memo里 Memo1.Lines.Add(Format([JS-%d] %s, [level, message])); end;ExecuteJavaScript本身不返回结果如果你需要拿到 JavaScript 的执行结果常见做法是让 JS 把结果写到console.log然后在ConsoleMessage事件里捕获。或者更规范一点用 V8 扩展注册一个回调函数JS 算完之后调用这个回调把结果传回来。4.4 大数据量传输的坑当 Delphi 和 JavaScript 之间需要传大量数据时比如一个几千行的表格数据直接拼 JSON 字符串会非常慢。我踩过这个坑一个 5000 行的数据集序列化成 JSON 字符串大概 2MB通过ExecuteJavaScript注入页面直接卡死 3 秒以上。后来改成了分批传输每次传 500 行传完之后 JS 端发一个确认信号Delphi 端收到确认再传下一批。虽然总时间差不多但页面不会卡死用户体验好很多。另一个方案是把数据写到本地文件JS 端通过fetch或者XMLHttpRequest读取本地文件这样绕过了 IPC 传输速度更快。但要注意 CEF 默认不允许跨域访问本地文件需要在GlobalCEFApp里设置--allow-file-access-from-files参数。5. 避坑指南CEF4Delphi 实际项目中容易翻车的五个场景5.1 程序启动就崩溃没有任何错误提示现象双击 exe窗口闪一下就没了或者直接弹出一个 Windows 错误报告对话框。原因最常见的原因是 CEF 二进制文件缺失或版本不匹配。CEF4Delphi 的 Pascal 代码和 CEF 的二进制文件是分开维护的如果libcef.dll的版本和源码里CEF_VERSION常量不一致启动时就会崩溃。另一个可能的原因是icudtl.dat文件没有放到 exe 同目录下。解决先检查 exe 输出目录下是否有libcef.dll、chrome_elf.dll、icudtl.dat、cef.pak这几个文件。然后打开uCEFConstants.pas确认CEF_VERSION的值去 CEF 官方构建页面下载对应版本的二进制包。如果还是崩溃在GlobalCEFApp里设置LogFile和LogSeverity : LOGSEVERITY_INFO然后看生成的日志文件里有没有更详细的错误信息。5.2 网页能加载但中文显示乱码现象页面能正常渲染但所有中文都变成了方块或者问号。原因CEF 默认的 locale 是en-US在某些系统上会导致中文字体加载失败。另外 Delphi 7 的字符串处理是 ANSI 的传给 CEF 的字符串如果没有正确转换成 UTF-8也会乱码。解决在GlobalCEFApp里设置Locale : zh-CN。如果是 Delphi 7确保所有传给 CEF 的字符串都经过UTF8Encode转换。另外检查locales目录下是否有zh-CN.pak文件没有的话从 CEF 二进制包里复制一份。5.3 关闭窗口时报 Access Violation现象点击窗口关闭按钮程序崩溃报 Access violation at address ... in module ...。原因前面提到过CloseBrowser是异步的。如果在浏览器还没销毁完成的时候就关闭了 FormChromium 的回调函数会访问已经释放的窗口句柄。解决严格按照前面给的关闭流程来——FormCloseQuery里设CanClose : False调CloseBrowser(True)在BeforeClose事件里再调Application.Terminate。另外确保TCEFWindowParent的销毁也在BeforeClose之后。5.4 页面滚动卡顿CPU 占用高现象页面能正常显示但滚动的时候明显掉帧任务管理器里看到 CPU 占用飙升到 50% 以上。原因CEF 默认的渲染模式在某些显卡驱动上会走软件渲染路径导致性能很差。另外如果MultiThreadedMessageLoop没有开启CEF 的消息循环和 Delphi 的主消息循环互相抢占也会导致卡顿。解决在GlobalCEFApp里设置MultiThreadedMessageLoop : True。如果还是卡尝试在GlobalCEFApp的CommandLineArgs里加上--disable-gpu强制走软件渲染看看是不是显卡驱动的问题。反过来如果软件渲染更卡加上--enable-gpu-rasterization强制开启 GPU 加速。5.5 在 DLL 或 ActiveX 里使用 CEF4Delphi 失败现象把 CEF4Delphi 封装到一个 DLL 里由主程序调用结果浏览器窗口创建失败或者直接崩溃。原因CEF 的多进程架构要求主进程和子进程使用同一个 exe 文件。如果你的 CEF4Delphi 代码在 DLL 里子进程启动的时候会去加载 DLL 所在的宿主 exe而不是你的 DLL导致进程模型混乱。解决CEF 官方不建议在 DLL 里使用 CEF。如果非要用必须确保宿主 exe 和 DLL 在同一个目录下并且宿主 exe 的StartMainProcess逻辑要能正确处理子进程。更稳妥的方案是把 CEF4Delphi 相关的逻辑放到一个独立的 exe 里通过进程间通信比如命名管道或者 WM_COPYDATA和主程序交互。6. 进阶技巧用离屏渲染做网页截图和自定义交互6.1 离屏渲染的适用场景CEF4Delphi 支持离屏渲染Off-Screen RenderingOSR意思是 Chromium 不直接创建窗口而是把渲染结果输出到一个内存缓冲区里由你的 Delphi 代码决定怎么显示。这个模式适合几种场景一是需要把网页内容渲染成图片保存二是需要在网页上叠加自定义的 Delphi 绘制内容三是需要做无窗口的网页自动化处理。开启离屏渲染的方式是在GlobalCEFApp里设置WindowlessRenderingEnabled : True然后在创建浏览器的时候用TChromium.CreateBrowser的一个重载版本不传TCEFWindowParent参数。6.2 离屏渲染下的截图实现// 离屏渲染模式下在OnPaint事件里接收渲染结果 procedure TForm1.Chromium1Paint(Sender: TObject; const browser: ICefBrowser; kind: TCefPaintElementType; dirtyRectsCount: NativeUInt; const dirtyRects: PCefRectArray; const buffer: Pointer; width, height: Integer); var Bitmap: TBitmap; SrcPtr, DstPtr: PByte; i, j: Integer; begin // 只在视图渲染时处理不处理弹出窗口 if kind PET_VIEW then Exit; // 创建位图并复制渲染缓冲区 Bitmap : TBitmap.Create; try Bitmap.PixelFormat : pf32bit; Bitmap.Width : width; Bitmap.Height : height; // CEF输出的是BGRA格式需要转换成Delphi的BGRA SrcPtr : PByte(buffer); for i : 0 to height - 1 do begin DstPtr : PByte(Bitmap.ScanLine[i]); Move(SrcPtr^, DstPtr^, width * 4); Inc(SrcPtr, width * 4); end; // 保存截图 Bitmap.SaveToFile(screenshot_ FormatDateTime(yyyymmddhhnnss, Now) .bmp); finally Bitmap.Free; end; end;这段代码的关键在于理解 CEF 输出的像素格式。CEF 的离屏渲染缓冲区是 BGRA 格式蓝-绿-红-透明度每像素 4 字节和 Delphi 的pf32bit位图格式一致所以可以直接用Move做内存拷贝。但要注意dirtyRects参数——CEF 只会重绘发生变化的区域如果你每次都全量拷贝性能会受影响。实际项目中应该根据dirtyRects只更新变化的部分。6.3 离屏渲染的性能边界离屏渲染虽然灵活但性能比窗口模式差不少。因为每一帧渲染结果都要从 Chromium 的渲染进程通过 IPC 传到你的主进程数据量大的时候 IPC 会成为瓶颈。我实测过一个 1920x1080 的页面离屏渲染模式下滚动帧率大概只有窗口模式的 60% 左右。所以我的建议是如果只是偶尔截个图用窗口模式 Chromium1.Browser.Host.TakeSnapshot就够了不需要开离屏渲染。只有当你需要持续获取渲染结果做实时处理比如视频叠加、自定义滤镜的时候才值得上离屏渲染。另外离屏渲染模式下鼠标和键盘事件需要你手动转发给 CEFTChromium提供了SendMouseClickEvent、SendKeyEvent等方法但坐标转换和事件映射需要自己处理工作量不小。从那以后我每次在项目里引入 CEF4Delphi都会先跑一遍最小验证流程初始化、加载本地 HTML、检查 LoadEnd 状态码、测试关闭流程。这四步走完没问题再往上叠业务逻辑。希望帮到你。本文还有配套的精品资源点击获取