DevExpress 源码包在 Delphi/BCB 中的编译安装与避坑 简介DevExpress ExpressSpreadSheet v1.38 for Delphi/BCB 完整源代码面向需要集成类似Excel电子表格能力的桌面应用开发者。使用该组件可快速实现数据录入、公式计算、图表展示与格式排版适用进销存、报表生成、数据看板等场景也适合中高级Delphi、C Builder程序员深入研究组件内部机制。资源包共382个文件体积仅2.81MB主要包括73个pas、26个dfm等Delphi源码与窗体定义45个dpk及24个bpk包工程38个cpp、10个h等C混合代码还有83个res、8个bmp界面资源和6个xls示例表格目录组织清楚便于按模块定位。目前已有446人学习下载。源码完整覆盖电子表格引擎、公式解析、数据绑定、样式渲染等核心模块并关联ExpressGDI绘图支持、ExpressLibrary通用基础库和XP主题管理器附带的CustomFunction、FeaturesDemo等演示工程可直接观察自定义函数、单元格绘制、图表交互与系统主题适配的具体实现为二次开发或学习DevExpress架构提供了高质量参考无论是直接编译使用还是借鉴其设计模式都有很强的实践价值。1. 这套源码包是什么老项目接盘时为什么它比安装盘更可靠如果你被拉去接手一个放着多年的遗留工程多半见过这种场面IDE 重装后工程里十几个第三方控件全部变红编译报错一屏接一屏其中就有找不到 dxSpreadSheet 单元之类的错误。标题里的 DevExpress.ExpressSpreadSheet.v1.38.for.Delphi.BCB 源代码就是当年项目买下后留在代码库里的一套完整源码里面通常有 pas 源文件、dfm 资源、dpk 与 cbpk 包定义。这篇文章讲的是怎么把这套源码重新编译进 Delphi 和 C Builder让控件回到组件面板、工程恢复编译同时把最常见的几个坑避开。适合手上握着老工程但安装盘早已丢失、或者想读源码改控件默认行为的开发者按这篇文章走完能省下大半天的试错。2. 编译前先摸清环境包依赖、IDE 版本和路径规划2.1 为什么这套源码值得自己编一次而不是找现成的 DCU很多人的第一反应是去网上找一个编译好的 dcu 或 bpl 塞进 Lib 目录这确实能解决“编译报错找不到单元”的问题但副作用很隐蔽。DCU 是和编译器版本强绑定的产物Delphi 7 编出来的 dcu 放到 Delphi XE 里轻则报 “Unit was compiled with a different version”重则直接 Fatal Error。更麻烦的是工程里其他第三方控件也在等某个特定版本的公共单元你换一个 dcu 就可能牵连别的包一起崩。而源码包的价值在于它把所有 pas 和 dfm 都摆在你面前你可以用当前 IDE 重新编出一套完全匹配的 dcu 和 bpl还能顺手改掉源码里的默认行为。我一般拿到这种包的第一件事不是急着编而是先确认工程当年用的 IDE 主版本。v1.38 这个年代的东西大概率是配合 Delphi 5/6/7、BCB 5/6 这一代环境编译模式和新版 IDE 的包机制不完全一样后面有单独的章节讲兼容性坑。另外源码包也给了你排查问题的能力。编译期报错能精确到某个 pas 文件的某一行运行期控件行为不对也能直接跟进去看属性初始化逻辑。相比之下装一个现成的发布版像在用黑匣子出了问题只能靠猜。2.2 编译前置三件事IDE 版本、Library 路径、公共依赖安装这套源码前先做三件准备缺一件后面都会翻车。第一件是确认 IDE 版本。Delphi 7 编出来的 bpl 不能装进 Delphi 2010BCB 6 编出来的包也不能给 BCB 2009 用。原因是每个 IDE 的 RTL/VCL 版本不同包在加载时要和 IDE 自身的运行库做符号匹配版本差太远会直接拒绝加载。所以先打开 IDE 的 About 窗口把主版本号记死后面所有编译参数都跟着这个版本走。第二件是规划 Library 路径。VCL 包编译的基本逻辑是dpk 源文件经过 dcc32 编译产出 bpl 运行期包和 dcu 中间产物IDE 安装包时加载 bpl并把 dcu 路径写进 Library Path 供工程引用。这里最容易犯的错是路径里混着多个版本的源码目录编译器搜路径时先撞见旧 dcu导致你改了源码却不生效。我一般会单独建一个干净的目录放这套包的源码编译输出也固定到一个 Bin 目录避免和系统 Lib 混在一起。第三件是查清公共依赖。ExpressSpreadSheet 这一系控件并不是孤立的它依赖 DevExpress 自己的一套公共基础库常见的是 dxCore、dxCommon 这类单元。如果源码包里带了这些公共单元的 pas 文件那一起编译即可如果包里没有就得先把公共库编译安装好再回来编这张表。判断方法很简单看源码根目录下有没有 dxCore.pas 或类似文件没有就用 IDE 的搜索功能全盘找一遍。cd /d C:\DevExpress\ExpressSpreadSheet.v1.38 dir /s /b *.pas pas_list.txt findstr /i dxCore dxCommon pas_list.txt第一条 dir 命令把源码目录下所有 pas 文件列出输出到文件里。第二条 findstr 在清单里搜公共依赖单元名能快速确认依赖是否齐备。如果你搜到的是空结果就去公共库源码目录里再搜一轮仍然没有就说明这套包还缺前置组件得先补上。注意pas 文件名和单元名不完全一致文件名是 dxCore.pas单元名通常是 dxCorefindstr 搜文件名就够了。2.3 包文件类型速览dpk、bpl、dcu、cbpk 各自干什么在动 dcc32 之前先把源码目录里那几类文件认清楚。很多人一进目录看到几十个文件就头晕其实按扩展名分就四类事。文件后缀作用出现在哪个阶段.dpk / .dpkwDelphi 包的源码工程定义.dpkw 是设计期包编译入口.cbpkC Builder 的包工程定义BCB 编译入口.pas / .dfm源码和窗体资源dfm 是控件属性的设计期描述编译输入.bpl / .dcu / .lib / .obj编译产物bpl 是运行期包dcu 是 Delphi 中间码lib 和 obj 是 BCB 的导入库和中间文件编译输出简单理解dpk 是给 Delphi 看的“编译任务书”cbpk 是给 BCB 看的pas 是本体dfm 描述控件在 IDE 里长什么样编译完成后 bpl 是你要装进 IDE 的东西dcu 给工程链接用lib 给 BCB 链接用。安装这套控件的本质就是把这些文本和资源文件变成 IDE 能加载的二进制包并注册到组件面板上。我习惯在编译前把所有旧 dcu、旧 bpl 先清一遍只保留源码。因为残留的旧物会让编译器优先使用陈旧产物造成“代码改了但行为没变”的假象这种玄学问题最浪费时间。3. 在 Delphi 里编译安装dcc32 命令行与组件面板注册3.1 用 dcc32 命令编运行期包先清理再全量重编Delphi 安装三方控件的标准路径是先编译运行期包得到 bpl再编译设计期包并注册到组件面板。运行期包负责控件运行时的所有逻辑设计期包负责在 IDE 里显示图标和属性编辑器。两步缺一不可只装运行期包的话组件面板上什么都不会出现。命令行编译的好处是可控、可重复而且能看到完整报错。打开 cmd切到源码目录执行下面这段cd /d C:\DevExpress\ExpressSpreadSheet.v1.38 del /s /q *.dcu dcc32 -B -Q -DDELPHI7 -UC:\DevExpress\Source;C:\DevExpress\Bin -RC:\DevExpress\Source -LC:\DevExpress\Bin ExpressSpreadSheet_D7.dpkdel 命令先清掉目录里所有 dcu避免缓存干扰这个动作很关键。dcc32 的是 Delphi 的编译器-B 表示全量重编译忽略已有的中间产物-Q 是安静模式只输出错误和警告不刷屏-DDELPHI7 是一个条件编译开关告诉源码“我现在用的是 Delphi 7”如果你在别的版本下编要把这个宏改成对应版本否则源码里的一些版本分支代码不会走入正确路径。-U 指定单元搜索路径编译器靠它找到 pas 和 dcu第一段路径是源码目录第二段是公共库输出目录-R 是资源搜索路径让编译器能找到 dfm 和 res-L 是输出目录编出来的 bpl 和 dcu 会落到这里。这里特别提醒-U 的顺序有讲究越靠前的路径优先级越高。如果你把公共库目录写在前面而那里恰好有一份旧 dxCore.dcu编译结果可能混入不匹配的版本。我都是把当前源码目录放第一位并且保证目录里没有旧产物。编译成功后在 Bin 目录下会看到生成好的 ExpressSpreadSheet_D7.bpl。如果这一步报错先检查 -U 路径是否包含依赖的所有公共单元目录九成以上的失败都出在这个参数上。3.2 设计期包安装让控件出现在组件面板上运行期包编译通过后接着处理设计期包。很多老版本的源码包里同时带 dpk 和 dpkw 两种包文件后者的“w”代表 design-time也就是设计期专用的包。找到对应的 dpkw 文件用同样的方式编译dcc32 -B -Q -DDELPHI7 -UC:\DevExpress\Source;C:\DevExpress\Bin -RC:\DevExpress\Source -LC:\DevExpress\Bin ExpressSpreadSheet_D7.dpkw编译参数和上一个命令一致唯一的区别是入口文件从 dpk 换成了 dpkw。设计期包编译完成后打开 Delphi IDE进 Component Install Packages 对话框点 Add在 Bin 目录里选中刚才生成的 bpl。安装成功后组件面板上会出现一个新页签名字一般是 ExpressSpreadSheet 或类似叫法拉一个控件到窗体上能正常显示就说明注册成功。如果你的源码包里没有 dpkw 文件常见做法是找以 Reg 或 Design 开头的单元比如 dxSpreadSheetReg.pas它里面包含 RegisterComponents 过程。手动写一个空 dpk 把设计期注册单元编进去也是可以的但要小心别把运行期和管理期逻辑混在一个包里否则安装时会报 “Package contains both runtime and design-time units” 的警告。3.3 编译完成后先跑一个小工程验证包安装成功不代表万事大吉工程里引用这套控件时的编译路径可能还有问题。我习惯立刻建一个最小工程验证只放一个窗体和一个控件编译通过后再打开老工程避免在几千行的老工程里排查环境问题。// MiniSpread.dpr最小验证工程 program MiniSpread; uses Vcl.Forms, dxSpreadSheet in ..\Source\dxSpreadSheet.pas; // 路径替换成你实际的源码位置 var Form: TForm; Sheet: TdxSpreadSheet; // 类名以你源码里的 pas 声明为准 begin Application.Initialize; Application.CreateForm(TForm, Form); Form.Caption : ExpressSpreadSheet 验证窗口; Form.Width : 800; Form.Height : 600; // 创建控件实例并铺满窗体 Sheet : TdxSpreadSheet.Create(Form); Sheet.Parent : Form; Sheet.Align : alClient; // 老版本 API 的单元格写法差异很大注释掉演示代码编译通过即可 // Sheet.Cells[0, 0].AsString : Hello; Application.Run; end.这段代码里 uses 子句直接引用了源码目录里的 dpp 文件这样一来编译器会强制重新编译这个单元不受 Lib 路径里旧 dcu 的影响能更准确判断源码本身有没有编过。TdxSpreadSheet 的类名以你手上的 pas 文件里的声明为准不同小版本的控件类名可能带后缀我见过不少把类名写错导致编译失败的案例。代码能编译、窗体能跑起来就证明 dcu 路径、bpl 注册、资源文件三项都正常这时候再去打开老工程心里就有底了。4. 在 C Builder 里编译BCB 与 Delphi 包的桥接方式4.1 BCB 不能直接编译 pas它靠 hpp 和 lib 桥接C Builder 的工程文件后缀是 cbpk但你打开它编译时BCB 实际干的活是把 C 代码和 Delphi 产物的导入库做链接。BCB 的编译器 bcc32 并不直接吃 pas 文件它需要三个东西配合bpl 运行期包、lib 导入库、hpp 头文件。lib 文件记录了 bpl 里所有可用的导出符号hpp 告诉编译器这些符号的 C 声明长什么样。这套机制对没接触过 BCB 的 Delphi 开发者来说就是个黑匣子很多人直接在 BCB 里尝试添加 pas 文件结果编译器报错完全不认识这个语法。正确思路是要么用源码包里自带的 cbpk 在 BCB 里整个编译一次要么先让 Delphi 编出 bpl再用 BCB 的工具生成配套的 hpp 和 lib。源码包里如果直接带 cbpk 文件优先用这个入口因为它把包依赖和链接顺序都定义好了省去手工配置的麻烦。如果只有 dpk 文件可以先用第 3 章的 Delphi 编译器把包编出来然后打开 BCB 的命令行工具用 implib 命令从 bpl 生成导入库。这个顺序反了不行先有 bpl 才能有 lib。4.2 用 bcc32 编译 cbpk 包参数怎么设拿到 cbpk 文件后在 BCB 的命令行环境里执行编译。BCB 6 的命令行工具路径一般在 RAD Studio 安装目录的 bin 下打开时它会自动配置好环境变量直接敲命令即可。cd /d C:\DevExpress\ExpressSpreadSheet.v1.38 bcc32 -B -IC:\DevExpress\Source;C:\DevExpress\Include -LC:\DevExpress\Lib;C:\DevExpress\Bin ExpressSpreadSheet_BCB6.cbpkbcc32 是 BCB 的 C 编译器-B 表示重建所有依赖项和 dcc32 的 -B 意思一致。-I 指定头文件和源文件的搜索路径注意 BCB 里源文件既包含 .cpp 也包含 .hpp编译器要靠这条参数找到它们-L 指定库文件路径包括 .lib 和 .obj 的搜索位置。cbpk 文件里已经写好了包内含哪些单元编译器会按文件里的清单逐个编译并链接。这条命令跑完会在输出目录里生成 bpl、lib 和 hpp 三样东西。如果编译中途报找不到头文件检查 -I 路径是否包含源码根目录以及公共库的头文件目录。如果报链接错误检查 -L 路径里能不能找到依赖的 lib特别是公共库的导入库。这一步常见的翻车点是只配了 -I 没配 -L结果编译过了、链接挂了。4.3 在 BCB 工程里引用控件include 和链接配置包编译完成后还要让 BCB 的组件面板认识它。操作路径和 Delphi 一样在 IDE 里进 Component Install PackagesAdd 刚才生成的 bpl。注意 BCB 只会加载符合当前 IDE 版本要求的包版本不对会直接拒绝这个在避坑章节详细说。然后在你的工程里引用这控件需要三行#include vcl.h #include dxSpreadSheet.hpp // 头文件名以源码包实际生成为准 #pragma link ExpressSpreadSheet.lib // 导入库告诉链接器去哪个lib找实现 TdxSpreadSheet *Sheet new TdxSpreadSheet(Form); Sheet-Parent Form; Sheet-Align alClient;第一行引入 VCL 基础库第二行引入控件头文件第三行是关键它告诉链接器把 ExpressSpreadSheet.lib 里的符号链接进最终程序。很多人忘了写 #pragma link编译阶段一切正常到链接阶段报一堆 unresolved external就是这个原因。类名后的*是 C 指针语法代表对象在堆上创建new 出来的实例记得在窗体销毁时释放老版本控件有的不支持自动释放。头文件的搜索路径在 BCB 的 Project Options Directories/Conditionals 里配置把源码目录和 Include 目录加进去不然编译器找不到 hpp。做完这些拖一个控件到窗体上能看到设计期预览工程编译链接通过BCB 这条线就算通了。5. 避坑指南v1.38 源码编译安装中的 5 个常见问题5.1 编译期避坑公共依赖缺失与缓存污染第一个坑是找不到公共单元。现象很直接执行 dcc32 或 bcc32 后编译器报 Fatal: File not found: dxCore.dcu。原因是这套控件的源码里 uses 了公共库单元而 -U 或 -I 路径里没有指到它们所在的目录。解决方式是把公共库目录路径加进 -U 参数如果公共库本身也是源码先把它编译一次。这里有个细节公共库编译后的 dcu 目录和当前源码目录最好分开不然两套东西混一起后面升级时又是一笔烂账。第二个坑是缓存污染导致的“改了源码没反应”。现象是你明明在某段实现里加了代码、重新编译了包IDE 里跑起来还是老行为翻车了还不知道为什么。原因分析下来大半是搜索路径里存在旧 dcu编译器的搜索顺序让你新编的 dcu 根本没被用上。解决方式是先扫一遍路径里所有相关旧产物全删掉后再全量重编并且每次编译前都把 del 命令跑一遍养成习惯。这里我用一个笨办法验证在源码里故意敲一个语法错误重新编译如果编译器没报错说明编的根本不是你改的那个文件。5.2 安装与链接避坑bpl 版本不匹配和 unresolved external第三个坑出现在 IDE 加载包的时候。现象是点 Add 选择 bpl 后IDE 弹窗提示 cannot load package 或者 module not found后面还经常跟着一串找不到的单元名。原因是包的编译版本和当前 IDE 不匹配最常见的是用 Delphi 7 编的 bpl 往 Delphi 2010 里装系统底层库符号完全对不上。解决方式是严格用当前 IDE 对应的编译器版本重编别想着跨版本用现成产物。如果确认版本正确仍加载失败还有一个可能包依赖的公共库 bpl 没先装上IDE 加载这个包时发现它的依赖项缺失一并报错。把公共库先装好再回来装这个顺序反了都会出问题。第四个坑是 BCB 特有的链接错误。现象是 BCB 工程编译通过但链接阶段报一堆 unresolved external全是对 TdxSpreadSheet 构造、析构、属性访问的未定义引用。原因是 C 代码里用了控件类但没把导入库链接进工程。#pragma link ExpressSpreadSheet.lib这行没写或者 Project Options 的 Linker 路径里没有指向 lib 所在目录链接器拿到符号名却找不到实现只能报错。解决方式是在工程里加上 #pragma link并把 -L 路径配置正确。这种错对新手特别劝退因为错误信息几百行其实核心就一句话链接器没找到库。5.3 运行期避坑设计期拖放崩溃和新版 IDE 兼容性第五个坑也比较隐蔽。现象是在 IDE 里拖一个 ExpressSpreadSheet 控件到窗体上设计期直接 Access Violation或者控件显示空白运行时反而正常。原因是设计期包和运行期包的资源不匹配dpkw 编出来的设计期包引用的 dfm 资源版本不对IDE 在设计期尝试加载预览界面时崩掉。解决方式是确认设计期包和运行期包用的源码目录、资源路径完全一致重新全量编译两个包并且清掉 dfm 资源目录下的旧缓存文件。再补一个常被追问的兼容性坑有人拿 v1.38 的源码往新版 Delphi比如 Delphi 10.4 以上里编报错 F1026 File not found 或者一堆语法过时警告。原因是老代码里用了旧版条件编译指令、个别已移除的库单元以及 IDE 底包机制的变化。我的建议是别硬编新版v1.38 本身就是跟着老 IDE 走的如果必须用新版先跑一遍源码里的语法转换工具把旧的$R指令和废弃类型别名换成新写法再逐个处理报错。这只是“能做”不代表“值得做”维护老项目时保持和当年一致的 IDE 版本是成本最低的选择。6. 把这套源码用透日志埋点与默认行为修改6.1 在绘制流程里埋日志验证数据到底走没走到 UI 层装好包只是第一步真正体现源码包价值的是你能进去改行为。我接手一个老报表项目时遇到过单元格数据对不上运行时表格里显示的值和底层数据结构不一致查了半天不知道是数据源的问题还是绘制层的问题。后来直接在绘制方法里埋了日志一跑就定位到了。procedure TdxSpreadSheet.DrawCell(ACanvas: TCanvas; ARow, ACol: Integer); begin // 只在调试版输出避免刷屏拖慢滚动速度 {$IFDEF DEBUG} OutputDebugString(PChar(Format(DrawCell Row%d Col%d, [ARow, ACol]))); {$ENDIF} inherited DrawCell(ACanvas, ARow, ACol); end;这段代码在绘制每个单元格时输出它的行号和列号到 IDE 的调试输出窗口。OutputDebugString 是 Win32 的调试输出函数Delphi 封装好可以直接调对运行性能影响很小。实际定位时你在数据层给某个单元格设值看绘制层有没有收到对应的 Row/Col 日志就能判断是上游数据没传进来还是绘制层自己画错了。注意别在绘制循环里做重操作滚动时每帧要画几十上百个单元格日志频率过高会把 IDE 拖死用 DEBUG 条件编译包一层发布版自动去掉。6.2 改默认列宽和字体绕过属性面板直接改初始化代码另一个高频诉求是调整默认样式。属性面板能改单个控件的列宽和字体但每个新窗口都要重新设一遍很烦。真正的做法是在源码的初始化流程里改默认值一劳永逸。我一般会先搜源码里的 DefaultColWidth、DefaultFont 之类的属性名然后在控件构造函数或 Loaded 方法里直接改数值。// 在 TdxSpreadSheet.Create 或 Loaded 方法里 DefaultColWidth : 24; // 把默认列宽从 18 改成 24 DefaultRowHeight : 28; // 默认行高调大适配中文显示 Font.Name : Microsoft YaHei; Font.Size : 9;如果你手上的版本里没有这几个属性名就在源码里搜 ColWidth、RowHeight 的初始化赋值点在那附近改。这样做的效果是工程里所有新建的表格控件都自动带上这套默认值比在设计期每个窗体加一遍代码干净得多。改完记得重新编译运行期包并在 IDE 里重装不然改动不生效。回头说说我自己栽过的一次教训接一个老项目时我改完默认字体后编译、安装一气呵成但打开程序发现字体纹丝不动折腾了一下午才发现是 Library 路径里另一份旧源码的 dcu 抢先被引用我改的根本不是编译进包的那份源码。从那以后我养成了两个习惯一是编译前先 del 清掉目录里全部 dcu二是装包前检查 Library Path 是否严格指向当前源码目录。这两个习惯让我在后续处理各种老控件源码时省下了大量时间。这套 ExpressSpreadSheet v1.38 的源码编译安装本质就是一次环境、缓存和版本的游戏摸清这三个变量的脾气后面就顺了。希望帮到你。本文还有配套的精品资源点击获取