ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Delphi 5开发指南光盘复现:VCL与数据库示例编译归档

Delphi 5开发指南光盘复现:VCL与数据库示例编译归档

简介:面向Delphi5初学者及具备Object Pascal基础的开发者,这份光盘资料集围绕教程、示例代码与配套工具展开,帮助读者理解VCL组件库、RAD快速开发、数据库访问和调试流程,可用于自学补强、课程练习或查阅旧项目源码。压缩包为rar格式,共1414个文件,约2.96MB,其中pas、dpr、dfm分别对应源码、项目与窗体设计,res、dcr、bpl、ocx等提供资源及组件支持,txt、htm和少量多媒体文件可用于说明与演示。已有234人学习,说明这一经典版本仍有人查阅。内容覆盖组件拖放、属性设置、事件响应、窗体布局、ADO/BDE数据库连接、断点调试和IDE自定义等主题,示例工程及第三方组件线索较丰富,便于按模块检索、复现界面并积累可运行代码。对需要理解旧版桌面应用结构、整理历史工程或进行二次开发的读者也有一定参考价值。

1. 一张 Delphi 5 开发人员指南光盘,今天翻出来还能干什么

多数人翻出这张光盘,第一反应是怀旧;真正动手的人会发现它是套完整的离线技术底稿:语言参考、VCL 组件文档、数据库与多层结构的示例工程,外加一批.dpr、.pas、.dfm齐全、能直接编译的源码。它解决三件事——资料散、示例缺、老系统看不懂:控件怎么写、数据集怎么串、多层结构怎么搭,示例都给了可运行骨架。适合维护老系统的开发、想补 Object Pascal 的人,以及需要不依赖网络本地参考库的团队。难点不在读,而在于把 1999 年的工程在今天机器上重新编译起来。

2. 光盘镜像拆开看:三层内容与一份能 grep 的索引

拿到镜像先别急着装环境。大多数翻车都发生在这一步:有人直接双击setup.exe,一路下一步,最后 IDE 打开就报错,连光盘里到底有什么都没弄清。这套资料的目录结构本身就有信息量,先把它摊平、统计、建索引,后面每一步都能省半小时。

2.1 从 ISO 到一棵能翻的目录树

不管拿到的是.iso还是被重新打包过的压缩包,第一步都是解出来而不是挂载运行。挂载后只读、路径带盘符、文件名大小写敏感问题都会在后续编译时咬人,解到本地磁盘最省事。跨平台一致性最好的是 7z,Windows、Linux、macOS 行为一样。

mkdir -p ~/d5cd && cd ~/d5cd # 解包,-o 指定输出目录,-y 表示全部覆盖不再逐条询问 7z x "/path/to/delphi5_devguide.iso" -o./extract -y # 先看一级、二级目录,判断这套资料的组织方式 find ./extract -maxdepth 2 -type d | sort # 再统计文件类型分布,扩展名统一转小写,避免 .PAS/.pas 被算成两类 find ./extract -type f \ | sed 's/.*\.//' \ | tr 'A-Z' 'a-z' \ | sort | uniq -c | sort -rn | head -20

-o后不要带空格,路径里有空格时整个参数加引号。sed 's/.*\.//'取最后一个点之后的扩展名,对没有扩展名的文件会输出整行,所以后面接head观察即可,不做强断言。tr那一步是关键:老光盘里.PAS和.pas混用很常见,不统一大小写,统计结果会误导你以为缺了一半源文件。

跑完通常能看到三类东西:文档类(.hlp、.pdf、.txt)、工程类(.dpr、.pas、.dfm、.dof、.dpk)、运行支撑类(.dll、.bpl、.dcu,以及一些安装包)。这三类的处理方式完全不同,混在一起看只会浪费时间,所以值得手动分到docs/、demos/、runtime/三个目录下。

2.2 帮助文件的离线打开方案

光盘里的文档主体是老式 WinHelp(.hlp)和少量 PDF。PDF 没悬念,麻烦的是.hlp:新版 Windows 已经不再自带 WinHelp 运行时,双击只会弹一个"此帮助文件需要旧版组件"的提示。这不是文件坏了,是系统把渲染器摘掉了。

文件类型典型内容打开方式注意点
.hlp语言参考、VCL 类库说明装 WinHelp 查看运行时,或转成 RTF/文本转换后目录层级会丢,靠标题行重建
.pdf开发人员指南正文、章节式教程任意 PDF 阅读器部分老 PDF 无文字层,需 OCR
.txt/.rtf发行说明、勘误、安装须知直接打开编码多为 ANSI,按 GBK 打开
.chm合并后的帮助集系统自带查看器从网络路径打开会被锁,先拷到本地

我一般不在 IDE 里翻帮助,因为老 IDE 的目录树不能全文检索。正确做法是先把.hlp批量转成纯文本,再用rg搜。转出来的文本没有格式,但检索体验是降维打击——你搜TClientDataSet,能一次看到它在语言参考、示例说明、勘误三个地方的全部出现位置。

# 假设转换工具把每个 .hlp 输出成一个同名 .txt,统一收进 txt_out/ mkdir -p txt_out # 全文检索,-n 显示行号,--no-heading 不打印文件名分组,-i 忽略大小写 rg -n --no-heading -i "TClientDataSet" ./txt_out | head -30 # 想同时看上下文,加 -C 3 rg -n -C 3 -i "MIDAS" ./txt_out | head -60

-C 3会带出前后三行,看参数说明时特别有用。这里有个经验:老文档里的类名经常带前缀写法(TDBGrid写作TDBGrid,但叙述里可能写成DBGrid),所以搜的时候把T去掉再搜一遍,命中率会高不少。

2.3 示例工程清单:完整度先于可编译性

光盘里的示例数量不少,但不是每个都完整。判断标准只有一条:.dpr在,同名.pas和.dfm也在,才算完整。缺.dcu完全无所谓,那是编译产物;缺.pas就没救,因为 VCL 的窗体定义在.dfm里、逻辑在.pas里,少一个都还原不出来。

# 扫出所有工程文件,逐个检查同名 .pas / .dfm 是否齐全 find ./extract -iname "*.dpr" -print0 | while IFS= read -r -d '' p; do base="${p%.dpr}" miss="" [ -f "$base.pas" ] || miss="$miss pas" [ -f "$base.dfm" ] || miss="$miss dfm" [ -f "$base.dof" ] || miss="$miss dof" printf "%-70s %s\n" "$p" "${miss:-OK}" done

-print0配read -r -d ''是为了扛住路径里的空格和特殊字符——光盘里的示例目录名带空格是常态,用普通的for f in $(find ...)一定会在空格处断开。${p%.dpr}是去掉后缀取基名,${miss:-OK}是"变量为空则输出 OK"的写法,比写 if-else 干净。

输出里dof缺失的工程要留意,这个文件存的是工程选项,包括单元搜索路径和输出目录。少了它,打开工程后第一次编译大概率报"找不到单元",得手动补搜索路径。所以我的习惯是:先按完整度排序,优先啃OK的那批,缺dof的放第二批,缺pas的直接跳过。

3. 把示例工程跑起来:从 .dpr 到第一个能编译的窗口

示例跑不起来,八成不是代码问题,是环境问题。这套工具链是 32 位的,编译器、调试器、组件包全是那个年代的产物,直接装在今天的主力系统上,IDE 能启动但设计器经常画不出窗体,调试器断点也时灵时不灵。血泪经验是:别跟环境较劲,隔离出来。

3.1 虚拟机里的编译环境怎么配

常见做法是在虚拟机里装一个 32 位的老系统,专机专用。这么做不是为了怀旧,是为了三件事可控:路径短、编码单一、注册表干净。老 IDE 往注册表里写的东西非常多,组件包注册、BDE 别名、最近打开列表全在里面,跟现代开发环境混在一台机器上,出问题很难定位。

环境项建议值原因
系统位数32 位工具链只有 32 位,64 位系统下部分设计期包加载失败
安装路径C:\D5路径带空格会让某些命令行工具截断参数
工作目录C:\work纯英文、短路径、无空格,编译输出稳定
源码编码ANSI(GBK)示例源文件是本地编码,按 UTF-8 存会毁掉中文注释
网络不挂共享目录老 IDE 扫描网络路径会卡死几十秒

装的时候有个顺序不能乱:主程序 → 帮助文件 → 运行库 → 第三方组件包。组件包一定要最后装,因为它依赖前面装好的编译器和运行库;顺序反了,包能装上但设计期一拖控件就报"类未注册"。装完先别急着开示例,新建一个空工程编译一次,能出 exe 再往下走,这一步能把 80% 的环境问题挡在门外。

还有一点容易被忽略:虚拟机的共享剪贴板和共享目录。共享目录在老 IDE 的文件对话框里会触发网络枚举,表现是点"打开"之后界面卡住十几秒。带示例代码进去用 ISO 挂载或者直接拷进虚拟磁盘,别图省事挂共享盘。

3.2 打开 .dpr 之后先改三处设置

.dpr是工程入口,本质是一个 Pascal 程序,只是主体被 IDE 接管了。打开它之前先看清楚uses里列了哪些单元,那些in 'xxx.pas'的写法就是相对路径声明,一旦你把工程挪了位置,这些路径全要跟着改。

program DemoQuery; uses Forms, UnitMain in 'UnitMain.pas' {FormMain}; {$R *.RES} begin Application.Initialize; Application.CreateForm(TFormMain, FormMain); Application.Run; end.

{$R *.RES}是编译指令,把同名资源文件编进 exe,图标和版本信息都在里面,删了程序照样跑但没图标。Application.CreateForm创建的是主窗体,第二个参数是变量名,顺序不能反。这段代码本身没什么可改的,要看的是它引用了几何个单元——单元越多,缺依赖的概率越大。

打开工程后立刻做三件事:第一,进工程选项,把单元输出目录和编译输出目录改成相对路径(比如.\dcu、.\bin),IDE 默认会把.dcu散落在源码目录里,几十个示例一起编译之后根本分不清谁是谁;第二,检查单元搜索路径,把示例自己的子目录、公共单元目录都加进去,路径之间用分号隔开;第三,确认dof里的设置和当前一致,不一致就以当前为准重新保存一次。

3.3 最小改动验证:给窗体挂一个查询

环境通了之后,别急着跑完整示例,先做一次最小改动。挑一个带TDBGrid的窗体,把它的数据源换成一个自己能控制的查询,能出数据就说明整条链路(连接、数据集、数据源、控件)都活了。用 ADO 组件比用 BDE 省事,因为它不依赖别名那套注册表配置。

procedure TFormMain.btnLoadClick(Sender: TObject); begin // 先关再改 SQL,开着的时候改会抛异常 qry.Close; qry.SQL.Clear; qry.SQL.Add('select * from demo_orders where amount > :amt'); // 参数化,别用字符串拼接,拼出来的 SQL 在带引号的数据上必翻车 qry.Parameters.ParamByName('amt').Value := 100; qry.Open; end;

Close那行不是可有可无的礼节:数据集处于打开状态时改SQL属性,运行期会直接抛"数据集已打开"的异常,而这个异常在设计期不会出现,所以很多人是在客户现场才发现的。Parameters.ParamByName的名字必须和 SQL 里的:amt完全一致,大小写不敏感但拼错就是运行期报"参数未找到"。Open和ExecSQL的区别要记牢:返回结果集用Open,纯更新用ExecSQL,用反了前者报错、后者静默不生效。

数据出来了,再回头跑原示例,这时候如果报错,错误信息就有指向性了——因为最小链路是通的,问题一定在示例自己的依赖上,而不是环境。

4. VCL 与数据库两条主线:示例里最值钱的部分

光盘里真正有长期价值的是两块:VCL 的组件设计思路,和数据库访问的分层结构。前者解释了很多今天看来"为什么是这样"的问题,后者是一套完整的主从表、多层结构的参考实现。这两块内容对照源码看,比读任何二手教程都直接。

4.1 数据集组件怎么选:四种取值的边界

老资料里同时存在四套数据访问路径,很多人分不清什么时候用哪个,看到示例里用哪个就抄哪个,结果在错误的场景上堆了一堆代码。按"数据在哪、要不要缓存、有没有中间层"三个问题过一遍,选型其实很清楚。

组件适用场景代价
本地表组件单机、数据量小、直接开本地表文件独占锁多,多机共享要靠目录权限,容易留锁文件
查询组件需要 SQL、需要服务端过滤依赖别名的注册表配置,换机器就丢
ADO 系列连标准数据源、要连接串可配置连接串里带密码,注意别硬编码进源码
客户端数据集需要离线缓存、主从表、多层依赖运行库 DLL,部署时容易漏

选型的核心判断是"数据要不要离开服务端"。要,就走客户端数据集那条线;不要,查询组件足够了。示例里大量使用本地表组件,那是当年的部署条件决定的,不是因为它更好——今天照抄,会在多用户场景上撞锁。

4.2 多层结构示例的复现要点

多层结构(中间层 + 客户端)是这套资料里最有嚼头的部分。它的模型很朴素:中间层放数据集和提供者,客户端放客户端数据集,两边靠数据包通信。复现的关键不在代码量,在于三个对象的连接关系不能错。

// 中间层:数据集 → 提供者 → 导出接口 procedure TServerModule.DataModuleCreate(Sender: TObject); begin qry.LinkDataSource := nil; // 提供者由数据集驱动,别再挂数据源 prv.DataSet := qry; // 提供者指向数据集,这一行错了客户端拿到空包 qry.Open; end; // 客户端:远端提供者 → 客户端数据集,取数用 Data 或 Fetch procedure TClientForm.btnFetchClick(Sender: TObject); begin cds.Close; cds.ProviderName := 'prv'; // 必须和中间层注册的提供者名一致 cds.Open; // Open 会走一次完整取数 // 只做增量更新时,改完记录调 ApplyUpdates,参数是允许出错的条数 cds.ApplyUpdates(0); end;

prv.DataSet := qry这行是最容易漏的:提供者没绑数据集,客户端Open不报错,只是拿到一个空结果集,然后你会花两小时怀疑网络。ProviderName是字符串匹配,中间层注册时用什么名,客户端就写什么名,改了中间层忘了改客户端是经典事故。ApplyUpdates(0)的参数是"允许失败的最大条数",传 0 表示任何一条失败就整体回滚;要部分成功就传正整数,然后逐条检查错误包。

部署时还有一个必须做的动作:注册运行库 DLL。多层结构在客户端依赖一个通信运行库,没注册的表现是程序启动就报"无法加载 DLL",而不是运行到某一步才报。所以部署脚本里应该无条件带上这一步,别指望开发机上能跑就等于客户机上能跑。

4.3 自定义组件与包的编译顺序

示例里有一部分是自定义控件,带.dpk包工程。包的编译顺序是有讲究的:先编运行期包,再编设计期包,设计期包requires里必须列出运行期包和 IDE 的设计期单元。顺序错了,报错信息会指向一个你根本没写过的单元。

package DemoPack; requires rtl, // 运行库 vcl50, // 可视组件库 designide; // 设计期支持,只有设计期包才需要 contains MyGauge in 'MyGauge.pas'; end.

requires里列的是包,不是单元,写单元名会报"未找到包"。contains里列的是本包自己的单元。设计期包和运行期包要分成两个.dpk,混在一个包里,编译能过,但装进 IDE 之后一拖控件就可能把 IDE 带崩——因为设计期代码和运行期代码加载到了同一个地址空间。

组件注册靠单元末尾的Register过程,这个过程只在设计期被调用,运行期完全不执行:

procedure Register; begin // 第一个参数是 IDE 组件面板上的页签名称,第二个是组件类 RegisterComponents('Demo', [TMyGauge]); end;

页签名写错不会报错,只是组件跑到了另一个页签下,找不到的时候先翻一遍面板再怀疑代码。组件写完之后一定要在空工程里拖一次、编一次、跑一次,三步都过才算能用——只编译不拖拽,属性编辑器里的问题一个都暴露不出来。

5. 排查:五个真会卡住人的坑

老工具链的问题有个共同特征:报错信息指向的位置和真实原因隔了很远。下面五条是我在实际复现过程中反复遇到的,按"现象 → 原因 → 解决"记下来,能省掉大量瞎试的时间。

5.1 安装与编译期

现象:打开示例窗体,弹窗报"Class Txxx not found",窗体显示成一个空白框。原因:窗体.dfm里引用了某个第三方控件,但对应的组件包没装进 IDE。设计期加载.dfm时找不到类定义,整个窗体就退化成一个空容器。 解决:先在.dfm里用文本方式搜类名(.dfm是文本格式,可以直接打开),确认是哪个控件;能找到对应包的,装上再打开;找不到的,把那个组件从.dfm里删掉,同时删掉.pas里对应的字段声明,窗体就能正常打开。别指望"忽略"能过去,忽略之后一保存,.dfm里那个组件的所有属性就永久丢了。

现象:编译报 "File not found: 'xxx.dcu'",但源文件明明就在旁边。原因:单元搜索路径没包含源文件所在目录。IDE 找单元的顺序是"当前目录 → 搜索路径 → 已安装包",源文件在但不在搜索路径里,等于不存在。 解决:工程选项里把源目录加进搜索路径,多个路径用分号分隔,路径末尾不要带反斜杠。如果是包里的单元,先确认包已经编译并且.dcp在库路径里,顺序是"先编包、再编工程",反过来一定找不到。

现象:路径里带中文或空格,编译到一半报一堆莫名其妙的语法错误。原因:老编译器的命令行解析对空格处理很粗糙,路径被截断之后,后半段被当成了编译参数。 解决:整个工程挪到C:\work\下,路径全英文、无空格、层级不超过三层。这一步没有技术含量,但不做就会持续消耗时间,属于典型的"五分钟能解决、五小时才想到"的问题。

5.2 运行期依赖与数据访问

现象:程序启动直接报"无法加载 DLL",没有任何堆栈。原因:多层结构依赖一个通信运行库,或者某些组件依赖运行期包,这些 DLL 没注册或不在系统路径里。启动阶段加载失败,所以拿不到堆栈。 解决:把运行库 DLL 拷到 exe 同目录,或者用系统自带的注册工具注册一次(注意必须是 32 位版本,64 位系统下默认调起的注册工具是 64 位的,注册 32 位 DLL 会报"模块加载失败")。注册完重启一次程序,别在原地反复点,进程已经缓存了失败的加载结果。

现象:示例能打开表,但换一台机器就报表不存在,或者只能读不能写。原因:数据路径硬编码在源码或别名配置里,换机器路径不存在;只读则通常是目录权限问题,本地表格式在写入时需要在同目录创建锁文件,没权限就退化成只读。 解决:把所有数据路径抽成一个配置项,启动时读配置拼绝对路径,别在窗体上写死。多机共享的场景,锁文件目录要指到一个所有人都有写权限的位置,否则并发一上来就互相踩。

现象:中文全部变成乱码,注释和界面文字都是问号。原因:示例源文件是本地编码(GBK),用 UTF-8 打开再保存,双字节字符就被拆坏了。这个损坏是不可逆的,保存那一刻原文就没了。 解决:编辑器里显式指定按 GBK 打开,确认中文正常显示之后再动手改。已经存坏的,从镜像里重新解一份出来,别试图修复。这条我吃过一次亏,一个几百行的单元文件直接报废。

6. 把这套文档变成能查的知识库:三步归档法

光盘里的内容按"读一遍"的方式用,价值会衰减得很快;按"随时能查"的方式用,它才是一份长期资产。我后来固定了一套三步归档法,每次处理这类老资料都走一遍。

第一步是拆文档层。把.hlp批量转成纯文本,按原目录结构落到docs/下,文件名保留原标题。转换会丢层级,所以在每个文件头部补一行来源路径,将来看到一段描述想找原文,靠这一行就能定位。

第二步是建检索层。用rg直接搜目录树,比任何索引工具都快;需要按类名反查时,加一个从类名到文件的映射表,格式就是两列:类名、文件相对路径。

# 生成"类名 → 文件"映射,只抓行首的类声明 rg -n --no-heading -o "^\s*T[A-Za-z0-9_]+\s*=\s*class" ./docs \ | sed 's/.*:\(T[A-Za-z0-9_]*\).*/\1/' \ | sort -u > class_index.txt

-o只输出匹配部分,sed把类名抠出来,sort -u去重。这个索引不追求完整,能覆盖八成常用类就够了,剩下两成靠全文搜兜底。

第三步是留验证工程。每啃完一个主题,在C:\work\verify\下留一个最小工程,命名就用主题名,比如verify\clientdataset\。工程里只放能证明这个主题成立的最少代码,注释里写清楚"改哪一行会失败"。这一步的价值在半年后:你再次需要确认某个行为时,不用重读文档,打开工程改一行、编一次就有答案。

归档层存放位置更新时机
文档层docs/,保持原目录结构一次性完成,之后只增不改
检索层class_index.txt、全文索引每次新增文档后重建
验证层verify/<主题>/每啃完一个主题补一个

这套方法唯一的成本是纪律:验证工程必须能独立编译,不能依赖别的工程里的单元;文档层不能随手改,看到错的地方在旁边加批注文件,原文保持原样。破坏其中任何一条,三个月后这套东西就退化成又一堆看不懂的旧文件。

从那以后我每次拿到这类老光盘,都强制先跑一遍目录统计和完整度扫描,再动手装环境——因为我已经不止一次在环境上耗掉整个下午,最后发现问题只是路径里有个空格。这套镜像和我整理出来的目录清单、完整度报告放在一起,照着上面的路径对一遍就能直接上手。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表