ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Delphi 12 接入 Indy 10.6.3.3:TCP/HTTPS 避坑

Delphi 12 接入 Indy 10.6.3.3:TCP/HTTPS 避坑

简介:Indy 10.6.3.3 压缩包面向使用 Delphi 12 与 RAD Studio 开发网络程序的读者,汇集 Internet Direct 组件库的完整源码和安装工程,可用于邮件客户端、Web 服务、FTP 工具及即时通信等场景,也适合希望理解 Delphi 网络组件实现的中高级开发者。包内共 2000 个文件,约 9.67MB,以 509 个 txt 说明、327 个 pas 源码、315 个 bmp 图像、137 个 dpk 组件包及 116 个 rc、111 个 res 资源文件为主,另含 dproj、inc、dfm 等工程与表单文件,便于编译、生成资源和查阅示例。已有 362 人学习。其价值在于可研读 TCP/IP、UDP、HTTP、SMTP、POP3、FTP 等协议的组件实现,借助 TIdTCPClient、TIdHTTP 等控件及 OnExecute 异步事件机制构建网络通信模块;开放源码也方便按项目需求裁剪、扩展,并兼容 C++Builder。

1. Delphi 12 接入 Indy 10.6.3.3:先弄清楚这份控件包解决的是哪类现场问题

手上一个 Delphi 12 维护项目要补 TCP 长连接和 HTTPS 上传,翻现有代码发现网络层还是几年前的写法,于是把这份 Indy 10.6.3.3 的 Delphi 控件包拆开试了一遍。它本质上是一套 Internet Direct 网络组件源码包,包含 TCP 客户端/服务端、HTTP、SMTP、POP3 等常用协议组件,适合需要在 Delphi 12 里快速搭出稳定收发链路、又不想从 socket API 一行行封装的场景。适合两类人:一类是老项目迁移,原来用 Indy 但版本对不上;另一类是新项目要一个能看源码、能改超时和 SSL 参数的控件层。不适合只想拖一个控件就完成全部业务的人,Indy 给的是积木,协议边界和线程模型仍要自己兜住。

2. Indy 10.6.3.3 的包结构与 Delphi 12 编译链:先让控件出现在面板上

2.1 压缩包里的源码组织与包文件识别

拿到 zip 后不要直接在压缩包里双击 dpk。Windows 下带空格或中文的路径会让后续编译的搜索路径变得很难看,常见做法是先解压到类似D:\dev\indy10633这种短路径,再进目录找包文件。Indy 的源码包通常把核心单元、协议单元和设计期注册单元分开放,包文件常见为IndySystem.dpk、IndyCore.dpk、IndyProtocols.dpk,以及对应的dclIndyCore.dpk、dclIndyProtocols.dpk。名字里带dcl的是设计期包,负责把组件注册到 IDE 控件面板;不带dcl的是运行期包,负责真正的代码编译。

先用 PowerShell 把包文件和源码目录列出来,确认这份包不是只放了 DCU 而没有源码:

# 在解压后的 Indy 根目录执行 Get-ChildItem -Path . -Recurse -Include *.dpk,*.dproj | Select-Object FullName # 看一眼源码目录里有没有核心单元,避免拿到只有二进制的包 Get-ChildItem -Path . -Recurse -Filter IdTCPClient.pas | Select-Object FullName

第一段命令的作用是定位所有包工程,第二段是确认IdTCPClient.pas这类源码单元存在。参数上-Recurse会递归子目录,-Include后面跟多个扩展名时要用逗号分隔。如果只搜到.dcu而搜不到.pas,说明这份包偏向预编译交付,后续调超时、改 SSL 行为会比较被动;如果源码齐全,建议把源码目录单独记下来,下一步配置搜索路径要用。

这里有个容易忽略的点:Delphi 12 自带了一份 Indy。你安装第三方 Indy 10.6.3.3 时,如果包名和 IDE 自带包名完全一样,IDE 可能提示“包已存在”或安装后仍加载旧单元。比较稳妥的做法是先把项目切到独立目录,确认当前项目没有引用 IDE 自带 Indy 的 DCU 路径,再安装这份包;如果只是某个项目要用,也可以不装设计期包,直接把源码目录加进项目搜索路径,用代码动态创建组件。后者更适合 CI 或不想污染 IDE 的环境。

2.2 设计期包与运行期包的安装顺序

安装顺序不能反。运行期包没编译通过,设计期包就算点了 Install 也会在加载时找不到依赖。常见顺序是:先 BuildIndySystem.dpk,再 BuildIndyCore.dpk,再 BuildIndyProtocols.dpk,最后打开dclIndyCore.dpk和dclIndyProtocols.dpk执行 Install。Build 是编译,Install 是把设计期包注册进 IDE,两个动作不要混。

在 IDE 里的操作路径是:File > Open,选中IndySystem.dpk,右键 Project > Build;依次处理核心包和协议包。打开dclIndyCore.dpk后,先 Build,确认 Output 窗口没有F1026 File not found之类的错误,再右键 Install。dclIndyProtocols.dpk同理。安装成功后,控件面板里会出现 Indy 的页签,能看到TIdTCPClient、TIdTCPServer、TIdHTTP这些组件。

如果 IDE 提示“Cannot load package”,先不要反复点 Install。把 Output 窗口往上翻,通常能看到真正的原因:要么是某个运行期包还没编译,要么是包的Requires里引用了当前 IDE 不存在的包。Delphi 12 的包后缀和旧版本不同,如果这份包里的 dpk 是从旧版本工程改过来的,Requires里可能还写着旧的后缀。处理方式是打开 dpk 的 Requires 列表,把不存在的包引用删掉或替换成当前 IDE 自带的同名包,再重新 Build。不要直接改源码单元,先改包依赖。

安装完成后建议立刻做一次“空项目验证”:新建一个 VCL 项目,从面板拖一个TIdTCPClient到窗体上,不改任何属性,直接编译。编译通过说明运行期包和搜索路径基本对齐;如果编译报Unit not found,问题在搜索路径,不在包安装。

2.3 搜索路径、字符集与 SSL 依赖的版本对齐

Indy 10.6.3.3 在 Delphi 12 下编译,搜索路径要同时覆盖源码根目录和可能的Core、Protocols子目录。项目级设置比 IDE 全局设置更可控:Project > Options > Delphi Compiler > Search path,把 Indy 源码目录加进去,多个路径用分号分隔。不要直接把整个解压目录加进去,否则同名的旧单元可能被优先命中。一个习惯是先把项目用到的单元名在 Everything 或 PowerShell 里搜一遍,确认只有一份IdTCPClient.pas在搜索路径里。

字符集方面,Delphi 12 默认 Unicode,Indy 10.6.3.3 的字符串接口大多以string暴露,但底层收发仍是字节。用TIdTCPClient.IOHandler.WriteLn('PING')这种写法时,Indy 会按 IOHandler 的编码把字符串转成字节;跨语言对接时不要假设对方一定按 UTF-8 解,最好在协议里写死编码,或者直接用TIdBytes收发。二进制场景下不要用ReadLn读文件流,ReadLn遇到0x0A就截断,血泪经验是先用长度字段定边界。

SSL 是这份包最容易翻车的地方。Indy 的 OpenSSL 支持依赖IdSSLOpenSSLHeaders里的版本宏和外部 DLL 匹配。常见做法是:32 位程序配 32 位libssl/libcryptoDLL,64 位程序配 64 位 DLL,DLL 放在 exe 同目录或系统能找到的路径。不要只看文件名,位数不对时运行时照样报 “Could not load SSL library”。如果项目同时有 Win32 和 Win64 目标,建议给两个平台分别准备 DLL 目录,编译后按平台拷贝,不要共用同一个输出目录。

提示:安装第三方 Indy 前,先确认项目没有同时引用 IDE 自带 Indy 的 DCU 路径。两个版本的单元混在一起编译,报错信息往往指向不相干的单元,排查成本很高。

3. 用 TIdTCPClient/TIdTCPServer 搭一条可复现的收发链路

3.1 连接参数与超时设置:Host、Port、ConnectTimeout、ReadTimeout

先写客户端最小闭环。下面这段代码不依赖窗体控件,直接动态创建,适合先验证包能不能跑通:

var Client: TIdTCPClient; begin Client := TIdTCPClient.Create(nil); try Client.Host := '127.0.0.1'; // 本地回环,先排除网络因素 Client.Port := 9000; // 与服务端 Bindings 的端口一致 Client.ConnectTimeout := 3000; // 连接阶段最多等 3 秒 Client.ReadTimeout := 5000; // 单次读取最多等 5 秒 Client.Connect; try Client.IOHandler.WriteLn('PING'); Memo1.Lines.Add(Client.IOHandler.ReadLn); finally Client.Disconnect; end; finally Client.Free; end; end;

ConnectTimeout管的是 TCP 握手阶段,网络不通或服务端没监听时,这个值决定界面卡多久;ReadTimeout管的是连接建立后的读操作,服务端迟迟不回数据时靠它抛异常退出,而不是无限阻塞。两个值都不要设成 0,0 在 Indy 里通常表示无限等待,调试时容易让程序像死了一样。Host先写127.0.0.1是为了把 DNS 和防火墙因素排除掉;确认本地通后再换成实际地址。WriteLn会自动补换行符,对端如果用ReadLn读,边界正好对上;如果对端是自定义二进制协议,不要用WriteLn,改用Write加长度字段。

服务端侧用TIdTCPServer,先设置Bindings。常见做法是在窗体上放一个TIdTCPServer,在OnCreate里清空默认绑定再添加:

procedure TForm1.FormCreate(Sender: TObject); begin IdTCPServer1.Bindings.Clear; with IdTCPServer1.Bindings.Add do begin IP := '127.0.0.1'; // 只监听回环,避免测试端口暴露 Port := 9000; end; IdTCPServer1.MaxConnections := 64; IdTCPServer1.Active := True; end;

Bindings不设置时可能监听所有网卡,测试阶段容易碰到防火墙弹窗。MaxConnections是保护值,不是性能调优值,先设一个能覆盖测试的量,压测时再按实际连接数调整。Active := True之后服务端开始监听,客户端才能连上。如果连不上,先看服务端Active是否为 True,再看端口是否被占用,不要一上来就怀疑 Indy 版本。

3.2 线程模型与 OnExecute 的阻塞边界

TIdTCPServer的OnExecute不是在主线程里跑的,每个连接上下文有自己的线程。很多人第一次用会在OnExecute里直接写Memo1.Lines.Add,测试时偶尔能跑,连接一多就随机卡死或报访问冲突。正确做法是把 UI 更新扔回主线程,用TThread.Queue或TThread.Synchronize。Queue不等待主线程执行完,适合日志;Synchronize会阻塞当前连接线程,除非必须等 UI 结果,否则优先Queue。

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); var Len: Int32; Buf: TIdBytes; Msg: string; begin // 先读 4 字节长度,长度不合法直接断开,避免被异常包拖死 Len := AContext.Connection.IOHandler.ReadInt32; if (Len <= 0) or (Len > 1024 * 1024) then begin AContext.Connection.Disconnect; Exit; end; AContext.Connection.IOHandler.ReadBytes(Buf, Len, False); Msg := TEncoding.UTF8.GetString(Buf); // OnExecute 在连接线程,不能直接操作 Memo TThread.Queue(nil, procedure begin Memo1.Lines.Add(Msg); end); end;

这段代码的关键不是ReadInt32本身,而是“先长度后内容”的读取顺序。ReadInt32会阻塞到 4 个字节到齐,ReadBytes会阻塞到指定长度到齐,两个操作都受ReadTimeout约束。长度上限1024 * 1024是防御性检查,防止对端发一个超大长度把内存吃满;实际业务按最大包体调,不要照抄。AContext.Connection.Disconnect在异常包时直接断开,比继续读更安全。TThread.Queue里的匿名方法捕获了Msg,Delphi 12 的匿名方法语法可以直接用;如果项目还在用老式方法,改成TThread.Queue(nil, procedure begin ... end)的写法即可。

还有一个边界:OnExecute里不要做长时间计算。Indy 的连接线程被占住,同一个连接上的后续数据只能排队。需要跑数据库或大文件解析时,把数据丢到自己的线程池,OnExecute只负责收包和拆包。血泪经验是,测试环境一个人连看不出问题,上线几十个长连接后,所有连接都像“卡住”一样,最后发现是某个回调里同步查了库。

3.3 粘包与帧边界:Length+Payload 的自定义协议示例

TCP 是字节流,没有消息边界。客户端连续发两次,服务端可能一次读到两段拼在一起,也可能分两次读到半段。Indy 的ReadLn用换行符做边界,只适合文本行协议;二进制或带结构的业务包,常见做法是“4 字节长度 + 负载”。下面客户端发送端显式写长度和负载:

var Payload: TIdBytes; Len: Int32; begin Payload := TEncoding.UTF8.GetBytes('HELLO'); Len := Length(Payload); // 先写长度,再写负载;两端都用 Indy 时 ReadInt32 能对齐 Client.IOHandler.Write(Len); Client.IOHandler.Write(Payload, Length(Payload), False); end;

Write(Len)写的是 4 字节整数,Write(Payload, Length(Payload), False)的第三个参数表示不追加额外内容。两端都用 Indy 时,Write(Int32)和ReadInt32默认能对上;如果对端是其他语言,不要假设字节序,最好在协议文档里写死大端或小端,或者自己用 4 字节数组手工拼。Payload用TEncoding.UTF8.GetBytes转字节,避免字符串编码在传输层被二次转换。

服务端按同样顺序读,长度不合法就断开,这一段和 3.2 的OnExecute是配套的。调试粘包问题时,不要只看应用层日志,抓包看一次实际字节流更快。常见误用是客户端用WriteLn发 JSON,服务端用ReadLn读,但 JSON 里本身带换行,结果一条消息被拆成多条。要么把 JSON 压成单行,要么改用长度字段。这个坑在 Indy 里不特殊,但用ReadLn太顺手,反而容易忘。

注意:ReadLn的默认最大行长度有限制,超长行会抛异常。传大文本时用ReadBytes配长度字段,不要硬调ReadLn的限制。

4. HTTP 与邮件场景:TIdHTTP、TIdSMTP 在 Indy 10.6.3.3 下的关键参数

4.1 TIdHTTP 的 GET/POST、重定向与 Cookie 管理

TIdHTTP是 Indy 里用得最多的组件之一。GET 请求看起来简单,但超时、重定向、Cookie 三个参数不设,线上就会出“偶尔卡住”或“登录态丢失”的问题。下面是一个带超时和 Cookie 管理的 GET 示例:

var HTTP: TIdHTTP; Cookie: TIdCookieManager; Resp: string; begin HTTP := TIdHTTP.Create(nil); Cookie := TIdCookieManager.Create(nil); try HTTP.ConnectTimeout := 5000; HTTP.ReadTimeout := 15000; HTTP.HandleRedirects := True; HTTP.RedirectMaximum := 5; HTTP.CookieManager := Cookie; HTTP.Request.UserAgent := 'Delphi12-IndyTest/1.0'; Resp := HTTP.Get('http://127.0.0.1:8080/api/ping'); Memo1.Lines.Add(Resp); finally HTTP.Free; Cookie.Free; end; end;

ConnectTimeout和ReadTimeout分别管连接和读取,HTTP 场景下读取超时建议比 TCP 略长,因为服务端可能在做查询。HandleRedirects := True打开自动跳转,RedirectMaximum限制跳转次数,防止循环跳转把线程拖死。CookieManager不设置时,TIdHTTP不会自动保存跨请求的 Cookie,登录接口调完再调业务接口就会 401。UserAgent不是必填,但有些网关会按 UA 做过滤,填一个可识别的值方便排查。

POST 表单用TStringList或TIdMultipartFormDataStream。普通application/x-www-form-urlencoded用TStringList就够:

var Params: TStringList; begin Params := TStringList.Create; try Params.Add('user=demo'); Params.Add('action=ping'); Resp := HTTP.Post('http://127.0.0.1:8080/api/login', Params); finally Params.Free; end; end;

Post的第二个参数是TStrings,Indy 会按表单编码拼接。文件上传不要用这种写法,改用TIdMultipartFormDataStream,并注意Content-Type的 boundary 由 Indy 生成,不要手工覆盖。返回二进制时不要用string接,改用TMemoryStream,否则图片或压缩包会被字符串转换破坏。

4.2 HTTPS 的 SSLIOHandler 配置与证书校验开关

HTTPS 不是把 URL 改成https://就行,TIdHTTP需要一个IOHandler来处理 TLS。Indy 里常见的是TIdSSLIOHandlerSocketOpenSSL。配置顺序是:先创建 SSL handler,再赋给HTTP.IOHandler,最后发请求。

var HTTP: TIdHTTP; SSL: TIdSSLIOHandlerSocketOpenSSL; begin HTTP := TIdHTTP.Create(nil); SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil); try SSL.SSLOptions.Mode := sslmClient; // 按包内实际支持的枚举选择,老包可能没有 TLS1.3 SSL.SSLOptions.SSLVersions := [sslvTLSv1_2]; HTTP.IOHandler := SSL; HTTP.ConnectTimeout := 5000; HTTP.ReadTimeout := 20000; // 调试自签证书时可临时关闭校验,上线必须打开 // SSL.SSLOptions.VerifyMode := []; // SSL.SSLOptions.VerifyDepth := 0; Memo1.Lines.Add(HTTP.Get('https://127.0.0.1:8443/api/ping')); finally HTTP.Free; SSL.Free; end; end;

SSLOptions.Mode := sslmClient表示当前是客户端模式。SSLVersions按包内枚举来,Indy 10.6.3.3 不一定支持 TLS 1.3,编译报“未声明标识符”就说明当前包没有这个枚举,退回 TLS 1.2。证书校验默认是打开的,调试自签证书时临时把VerifyMode设为空集合能跳过校验,但这只能用于本地,上线必须恢复,否则中间人换证书也发现不了。VerifyDepth控制证书链深度,设 0 表示不限制,生产环境按 CA 链实际情况设。

如果运行时抛 “Could not load SSL library”,先查三件事:DLL 是否在 exe 同目录、DLL 位数是否和 exe 一致、DLL 文件名是否和 Indy 头文件里期望的名字一致。不要只看“我明明放了 DLL”,32 位程序加载 64 位 DLL 时错误信息不会告诉你位数不对。用 Process Monitor 过滤 exe 的Load Image事件,能看到实际尝试加载的路径,比猜快。

4.3 TIdSMTP/TIdPOP3 的认证方式与编码设置

邮件场景在 Indy 里分工很清楚:TIdSMTP发信,TIdPOP3收信,TIdMessage组织邮件内容。先看发信:

var SMTP: TIdSMTP; SSL: TIdSSLIOHandlerSocketOpenSSL; Msg: TIdMessage; begin SMTP := TIdSMTP.Create(nil); SSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil); Msg := TIdMessage.Create(nil); try SMTP.Host := '127.0.0.1'; SMTP.Port := 587; SMTP.Username := 'demo'; SMTP.Password := 'demo'; SMTP.IOHandler := SSL; SMTP.UseTLS := utUseExplicitTLS; Msg.From.Address := 'demo@example.test'; Msg.Recipients.Add.Address := 'ops@example.test'; Msg.Subject := 'Indy test'; Msg.Body.Text := 'hello from indy'; Msg.CharSet := 'utf-8'; Msg.ContentTransferEncoding := 'base64'; SMTP.Connect; try SMTP.Send(Msg); finally SMTP.Disconnect; end; finally Msg.Free; SMTP.Free; SSL.Free; end; end;

UseTLS的取值要和服务器匹配:utUseExplicitTLS是连上后发STARTTLS,常见于 587 端口;utUseImplicitTLS是一开始就 TLS,常见于 465 端口。填错会表现为连上后认证失败或直接断线。CharSet和ContentTransferEncoding决定中文主题和正文会不会乱码,UTF-8 配 base64 是比较稳的组合。TIdMessage的From和Recipients是集合,用Add.Address追加,不要直接给Address字符串赋值。

收信侧TIdPOP3类似,重点在Username、Password、IOHandler和端口。POP3 的Retrieve会把邮件拉到TIdMessage,大附件场景要注意内存。认证方式上,Indy 10.6.3.3 支持TIdSASL系列,但很多服务器只要求Username/Password走 TLS,先用简单认证跑通,再按服务器要求加 SASL。邮件编码问题不要靠猜,收到乱码时先把TIdMessage的CharSet和原始 header 打出来看,再决定是客户端编码还是服务端转发时改了内容。

5. 避坑与排查:Indy 10.6.3.3 在 Delphi 12 下最容易翻车的五处

5.1 控件面板空白:设计期包没注册还是包名冲突

现象是安装完 dcl 包,IDE 里找不到 Indy 页签,或者页签在但组件是灰的。原因通常有两种:一是只 Build 了运行期包,没有对设计期包执行 Install;二是设计期包的Requires里引用了旧版本包,IDE 加载失败但提示被折叠在 Output 窗口里。解决时先打开 dcl 包的 Project Manager,看Requires是否包含当前 IDE 存在的包;再右键 Install,如果报“Cannot load package”,把 Output 往上翻找第一条错误。包名冲突时不要反复卸载重装,先确认项目搜索路径没有指向 IDE 自带 Indy 的 DCU 目录,再换一个独立目录安装。

5.2 编译报 Unit not found:搜索路径和 DCU 输出目录打架

现象是明明安装了包,新建项目拖控件编译却报F1026 File not found: 'IdTCPClient.dcu'或IdTCPClient.pas。原因是项目搜索路径没有包含 Indy 源码目录,或者包含的是旧版本目录,DCU 输出目录里还有同名旧单元。解决时在 Project > Options > Delphi Compiler > Search path 里只保留一份 Indy 源码路径,把旧的 DCU 输出目录清掉,再 Build All。不要用 IDE 全局 Library path 兜底,全局路径一多,下次换项目又会出现“同一个单元两个版本”的怪问题。

5.3 运行时找不到 SSL 库:位数、文件名与加载路径三件事

现象是 HTTP 请求一走到 HTTPS 就抛Could not load SSL library,但普通 HTTP 正常。原因按概率排:DLL 位数和 exe 不匹配、DLL 不在加载路径、DLL 文件名和 Indy 头文件期望的不一致。解决时先确认 exe 是 Win32 还是 Win64,再确认 DLL 位数;把 DLL 放到 exe 同目录,用 Process Monitor 看加载尝试路径;最后检查IdSSLOpenSSLHeaders里的库名宏是否和实际文件名对上。不要混用不同来源的libssl和libcrypto,两个 DLL 版本要配套。

5.4 OnExecute 里直接刷 UI:随机卡死和访问冲突

现象是单连接测试正常,多连接后界面随机卡死,或者关闭程序时报访问冲突。原因是TIdTCPServer.OnExecute运行在连接线程,直接操作Memo、ListView这类 VCL 控件会跨线程访问。解决时把所有 UI 更新包进TThread.Queue或TThread.Synchronize,OnExecute里只做收包、拆包和业务数据入队。不要在OnExecute里做同步数据库查询,连接线程被占住后,同一连接的下一条数据只能等,表现就像“服务端没响应”。

5.5 旧项目升级后连接重置:残留 DCU 与多版本 Indy 并存

现象是旧项目换到 Delphi 12 后,编译能过,运行时连接刚建立就被重置,或者TIdHTTP请求偶发失败。原因是项目目录里残留旧版 Indy 的 DCU,搜索路径又同时指向新旧两份源码,编译器选了旧单元、链接时却混了新包。解决时先做一次干净编译:删除项目下的__history、__recovery和所有 DCU 输出目录,确认搜索路径只有一份 Indy 源码,再 Build All。升级前把项目里引用 Indy 的单元列出来,逐个确认没有用到新版本已移除的属性,比运行时猜错误码省时间。

6. 把 Indy 版本钉死:一个本地回环冒烟用例和发布清单

版本问题不要靠记忆。每次换机器或换编译环境,先跑一个最小回环用例,把 Indy 版本打出来,确认 TCP 和 SSL 两条链路都能走通。下面这段代码放在窗体OnCreate里,启动时就能看到结果:

procedure TForm1.FormCreate(Sender: TObject); begin Caption := 'Indy ' + IdTCPClient1.Version; IdTCPClient1.Host := '127.0.0.1'; IdTCPClient1.Port := 9000; IdTCPClient1.ConnectTimeout := 2000; IdTCPClient1.ReadTimeout := 2000; try IdTCPClient1.Connect; IdTCPClient1.IOHandler.WriteLn('PING'); if IdTCPClient1.IOHandler.ReadLn = 'PONG' then Caption := Caption + ' / loopback OK'; finally IdTCPClient1.Disconnect; end; end;

Version属性直接把当前加载的 Indy 版本显示在标题栏,换包后一眼能看出来有没有生效。回环测试只依赖本机服务端,不受外网和证书影响,适合做每日构建的冒烟项。发布时把下面几项钉死,比在代码里写死路径可靠:

检查项做法目的
源码路径项目级 Search path 只保留一份 Indy 源码防止新旧单元混编
SSL DLL按 Win32/Win64 分目录,编译后拷到 exe 同目录避免位数和路径问题
包版本标题栏输出Version,构建日志留档出问题能回溯到具体包
清理策略换包前删 DCU 输出目录,Build All防止残留 DCU 干扰

从那以后我每次换 Indy 包,都先把回环用例和版本号跑一遍,再动业务代码;看起来多花两分钟,实际省掉的是“到底哪份单元生效了”的反复排查。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进