Delphi uniGUI示例包编译部署与VCL迁移避坑指南

发布时间:2026/10/23 5:23:59

Delphi uniGUI示例包编译部署与VCL迁移避坑指南 简介DeWeb 是一套把既有 Delphi 工程直接搬上浏览器运行的工具包面向熟悉 Object Pascal、希望绕开 HTML、JavaScript、PHP、ASP 与 C# 等语言、以最少改造成本交付 Web 应用的开发者。压缩包 24.59MB共 2669 个文件以 296 个 pas 源码、247 个 dcu 编译单元、165 个 dfm 窗体、126 个 dproj 工程为核心配合 222 个 js、91 个 css 与 304 个 res 资源支撑页面呈现并含 icsV858 控件相关文件覆盖 Delphi 10.1、10.2、10.3 多个版本。包内示例可通过 demos.dw、mobile.dw、form4.dw 查看桌面与移动端效果也演示了新建窗体后以 RegisterClass、UnRegisterClass 注册页面、重编译即生效的最小闭环。已有 1073 人学习适合想验证 Delphi 转 Web 可行性的团队。1. 一个 2020 年的 Delphi Web 示例包为什么现在还有人翻出来手上有一套跑了十几年的桌面程序业务方突然要求「在浏览器里能打开」这时候再回头翻 Delphi Web 方案的团队不少uniGUI 基本是绕不开的一条路。这个deweb2020-05-23 beta2 new2.rar就是那个时间点留下来的示例工程合集一堆能直接编译的窗体样例、ServerModule 与 MainModule 的骨架、以及把 VCL 界面往 Web 上搬的写法参考。它的价值不在于代码多漂亮而在于省掉你从零搭脚手架、逐个控件试错的那两三天。适合两类人正在评估 uniGUI 能不能承接存量桌面项目的人以及已经决定用、但缺一份可编译参照物的开发者。包名带 beta意味着它是过程产物你得带着怀疑去读。2. 从 rar 到第一个窗体把包里的工程真正编译起来2.1 三条硬门槛先判断这套示例能不能在你机器上用拿到一个来源年份较早的 Delphi 工程包最省时间的做法是先做「能不能编」的判定而不是急着读源码。这类包通常和三样东西强绑定IDE 大版本、uniGUI 运行时版本、目标平台位数。年份在 2020 年附近的工程用 10.x 系列打开的成功率最高用更新的 IDE 打开时.dproj会被自动升级升级本身不可逆所以务必先复制一份再打开这条是血泪经验。门槛检查方法不满足时的典型表现IDE 大版本看.dproj里的ProjectVersion与DCC_Namespace打开时提示转换转换后编译报找不到单元uniGUI 运行时看工程引用的包名里带不带版本后缀报Never-build package unigui... must be recompiled目标平台.dproj里Platform是否为 Win32编译通过但部署到 Web 服务器加载失败位数这条要单独强调多数人第一次部署会把 ISAPI 模块丢进 64 位应用池然后得到一个 500 或者干脆空白页。示例包里若没有明确的 64 位工程配置就按 32 位走并在服务器上把「启用 32 位应用程序」打开。2.2 目录怎么读先找工程文件不按文件夹名字猜beta 包的目录结构往往和正式发布版对不上靠名字猜会浪费时间。正确的顺序是先全量列出工程文件再反推哪些是主工程、哪些是被引用的库工程最后才看窗体。下面这套流程我在很多老包里都用过稳。# 1) 解包到不含中文、不含空格的路径。西文短路径能省掉后面一半的怪问题 7z x deweb2020-05-23.beta2.new2.rar -o./deweb # 2) 列出所有工程文件按路径深度排序主工程一般藏在最外层或 Demo 层 find ./deweb -maxdepth 4 -name *.dproj | sort # 3) 看某个工程引用了哪些包这一步直接决定你会不会卡在编译 grep -o DCC_UsePackage[^]* ./deweb/Demo/WebApp.dproj | sort -u第 2 步用-maxdepth是为了避免某些包里塞了第三方组件源码深挖会翻出一堆无关的.dproj。第 3 步的DCC_UsePackage是关键它告诉你这个工程编译时依赖哪些设计期包把它和你 IDE 里已安装的包列表对一遍缺哪个补哪个比盲目编译再逐个看报错快得多。读完这三步你会得到一张自己的目录用途表大致长这样内容类型常见位置阅读优先级主工程与部署配置外层Demo/App目录最高先编它服务器与主数据模块Server/Module一类目录高逻辑入口在这窗体与 Frame 示例Forms/Frames一类目录中按需对照控件静态资源与主题Res/ext一类目录高路径配错会白屏2.3 用命令行编一次比在 IDE 里点十次更能暴露问题先把编译跑通再谈改造。命令行构建的好处是参数全部显式出错信息干净不会被 IDE 的缓存干扰。# 用 IDE 同款编译器命令行构建Release Win32 是最贴近部署的组合 msbuild ./deweb/Demo/WebApp.dproj \ /t:Build \ /p:ConfigRelease \ /p:PlatformWin32 \ /v:minimal # 构建成功后先别急着配服务器用 standalone 模式跑起来看首屏 ./deweb/Bin/WebApp.exe参数逐个说清/t:Build表示增量构建第一次跑建议换成/t:Rebuild避免残留的.dcu干扰/p:ConfigRelease会切到发布配置调试信息被裁掉能提前暴露那些只在 Debug 下「碰巧能跑」的代码/p:PlatformWin32前面提过别省。/v:minimal只是压日志噪音第一次失败时改成/v:detailed看完整调用链。进程起来后浏览器访问http://127.0.0.1:端口端口在 ServerModule 里配默认值各版本不一样看不出来就直接在服务端代码里写死一个高位端口测。看到第一个窗体渲染出来说明编译器、运行时、静态资源三者的路径是对齐的这一步不通后面所有改造都是空谈。3. uniGUI 的运行模型桌面思维直接搬上浏览器必翻车3.1 ServerModule、MainModule、Form 三层生命周期uniGUI 把一次 Web 请求拆成了三层对象搞不清这三层的存活范围会写出「A 用户的操作改了 B 用户数据」这种极难排查的问题。ServerModule 是进程级单例整个应用只有一份适合放全局配置MainModule 是会话级每个浏览器会话一份适合放连接、数据集、权限缓存Form 是页面级一个会话里可以有多个但必须自己创建自己释放。层实例数量生命周期该放什么不该放什么ServerModule进程内 1 份随进程启动到进程退出端口、主题、字符集、日志开关数据库连接、用户状态MainModule每会话 1 份会话建立到超时销毁连接、Query、当前用户、权限跨用户共享的缓存Form每会话 N 份手动 Create 到手动 Free界面控件、临时状态长事务、全局句柄把上面这张表当纪律用能省掉后面 80% 的排查时间。最典型的错误是把TFDConnection拖到 ServerModule 上本地单人测试一切正常一上并发就开始出现事务互相污染、提交了别人的数据。procedure TUniMainModule.UniGUIMainModuleCreate(Sender: TObject); begin // 每个浏览器会话执行一次连接、数据集、用户上下文都建在这里 FConn : TFDConnection.Create(Self); FConn.Params.Database : demo; FConn.Params.UserName : app; FConn.Params.Password : app; FConn.LoginPrompt : False; FConn.Connected : True; FQuery : TFDQuery.Create(Self); FQuery.Connection : FConn; FQuery.FetchOptions.Mode : fmAll; // Web 端不留活游标一次拉完最稳 end; procedure TUniMainModule.UniGUIMainModuleDestroy(Sender: TObject); begin // 会话超时或主动登出都会走到这里漏了它连接会被慢慢吃干 FreeAndNil(FQuery); FreeAndNil(FConn); end;逻辑说明Create事件对应会话建立Destroy对应会话结束或超时成对出现是硬要求。FetchOptions.Mode : fmAll这行值得单独讲——桌面程序习惯留活游标边翻边取但 Web 请求是短连接游标跨请求存活会拖住连接、放大锁范围改成一次拉完用内存换稳定在这类场景下几乎总是划算的。事件名在不同小版本里可能显示为OnCreate或带框架前缀的写法绑定的是同一个时机别被名字绕晕。3.2 无状态请求怎么拖住有状态窗体HTTP 本身没有会话概念uniGUI 靠会话标识 服务端对象表把「有状态窗体」桥接起来。你写的ShowModal在浏览器里其实是一次异步调用加一次前端遮罩代码看起来是同步的底下全是异步。理解这一点才能解释为什么「在窗体构造函数里等一个慢查询」会把整个会话卡住。procedure TUniMainModule.OpenOrderForm(const AOrderNo: string); var F: TUniFormOrders; begin // 每个会话独立实例绝不能用单元级全局变量缓存窗体 F : TUniFormOrders.Create(UniApplication); try F.OrderNo : AOrderNo; F.ShowModal; finally F.Free; end; end;参数与逻辑Create(UniApplication)把窗体挂到当前会话的所有者上会话销毁时会兜底回收但finally里的Free仍然要写因为模态窗体关闭后不释放会一直占内存用户开开关关十几次就能看到内存曲线往上爬。ShowModal之后主线程是被让出的别在它后面直接假设用户已经操作完。配套一条经验所有耗时操作导出、批量计算都不要塞在窗体创建链路里改成先渲染骨架、再用定时器或回调触发。这类改法在桌面上是可选项在 Web 上是必选项。3.3 VCL 控件往 TUniForm 上搬的对照关系迁移的主体工作是换控件换错了不是样式问题是行为问题。桌面控件Web 对应注意点TStringGridTUniStringGrid/TUniDBGrid大数据量必须分页前端渲染行数是硬上限TPageControlTUniPageControl懒加载要显式打开否则一次全渲染TTimerTUniTimer走的是浏览器轮询间隔别小于 1 秒ShowMessageTUniMessageDlg或前端脚本Application.MessageBox在 Web 下不可用TOpenDialogTUniFileUpload上传是独立请求不能同步等返回值表格里最后一行最容易翻车桌面代码里「弹出文件框 → 拿到路径 → 立刻读文件」是一条直线Web 上必须拆成「用户选文件 → 上传完成事件 → 再读」中间还夹着一次网络往返。所有依赖TOpenDialog.FileName的逻辑都要重写成事件驱动。4. 参数与配置把示例调成能扛住真实访问的样子4.1 服务端参数端口、会话超时、字符集示例包的默认配置是给本地单人调试用的直接拿去上线第一天就会收到「点着点着就没反应了」的反馈。核心参数集中在 ServerModule 上在初始化事件里显式赋值比在设计器里点属性更可控也便于做环境区分。procedure TUniServerModule.UniGUIServerModuleCreate(Sender: TObject); begin Port : 8077; // standalone 模式监听端口ISAPI 部署下这行无效 Title : Order Console; CharSet : utf-8; // 漏设的后果是中文全变问号且很难一眼看出 Theme : triton; // 决定首屏加载哪套前端主题资源 SessionTimeout : 60; // 单位通常是分钟超时后会话对象整体销毁 ExtRoot : /ext/; // 静态资源前缀改目录必须同步改这里 end;参数建议值作用配错的典型症状Port8000 以上高位端口standalone 监听端口启动即报端口占用或被防火墙拦掉SessionTimeout3060 分钟会话存活上限用户离开一会回来点击无响应CharSetutf-8响应与页面编码中文乱码、提交内容截断Theme与资源目录一致前端主题皮肤首屏白屏或样式全丢ExtRoot与实际目录一致静态资源路径前缀控制台一堆 404SessionTimeout是最需要按业务调的一个。设太短用户填了一半的表单被清空投诉直接上门设太长会话对象长期不释放内存缓慢上涨。折中做法是给到 60 分钟同时在前端加一个到期前的提示让用户知道要重新登录而不是莫名其妙点不动。4.2 静态资源与首屏主题、缓存、压缩三件事uniGUI 的首屏慢九成不是服务端逻辑慢是前端资源加载慢。示例包里带的资源目录如果没配压缩和缓存头每次刷新都会重新拉一遍主题包。先用命令确认现状再决定改不改。# 看首屏响应有没有走压缩、缓存头是否合理 curl -s -I -H Accept-Encoding: gzip http://127.0.0.1:8077/ \ | grep -i -E content-encoding|cache-control|content-length判断标准很直接content-encoding里没有gzip或br说明压缩没开静态资源体积会大出好几倍cache-control是no-cache或者压根没有说明每次访问都在重新下载。这两项在反向代理或应用服务器层面开比在代码里折腾有效得多。还有一个隐蔽的坑ExtRoot指向的目录如果是相对路径在 ISAPI 部署下会相对于应用池的工作目录解析而不是站点根目录结果就是本地跑得好好的一上服务器全部 404。稳妥做法是部署时用绝对路径或者干脆把资源目录放在站点根下用相对 URL。4.3 数据访问连接放哪一层、事务边界画在哪前面说过连接必须放会话层这里补事务。Web 场景下事务要短、要闭合、要跟会话绑定任何一个跨请求的隐式事务都是灾难。function TUniMainModule.SaveOrder(const AOrderNo: string; AQty: Integer): Boolean; begin Result : False; FConn.StartTransaction; // 事务开在会话自己的连接上 try FQuery.SQL.Text : update orders set qty :q where order_no :no; FQuery.ParamByName(q).AsInteger : AQty; FQuery.ParamByName(no).AsString : AOrderNo; FQuery.ExecSQL; FConn.Commit; Result : True; except FConn.Rollback; // 任何异常都必须回滚别指望上层兜 raise; end; end;逻辑说明StartTransaction与Commit/Rollback必须成对except里先回滚再raise把异常继续往上抛给界面层做提示而不是在这里吞掉。参数化查询用ParamByName而不是拼字符串一方面是注入防护另一方面是让数据库能复用执行计划——后者在并发上来之后对响应时间的影响比想象中大。边界原则一句话一次用户点击对应一个事务事务里不做网络调用、不做文件 IO、不弹窗。违反任何一条锁持有时间就会从毫秒级涨到秒级然后你在监控上看到一堆超时却找不到是哪行代码干的。5. 避坑与排查这份 beta 包里最容易翻车的五处5.1 编译通过、浏览器白屏控制台一堆 404现象进程正常启动访问首页返回 200但页面全白开发者工具里能看到主题 JS 和 CSS 全是 404。原因ExtRoot或资源目录的实际位置与配置不一致或者部署时只拷了 exe 没拷资源目录。beta 包的资源目录名很可能和正式版不同直接沿用配置就会踩。解决先把ExtRoot改成绝对路径验证一次能通就说明是路径解析问题确认资源目录被完整拷贝到输出目录ISAPI 部署下要特别检查应用池的工作目录它和你想象的往往不是同一个。5.2 两个人同时用A 看到的是 B 的数据现象单人测试一切正常两个人同时登录后列表数据串了偶尔还出现「提交了别人的单据」。原因把数据集、当前用户、权限缓存这类会话级状态放到了 ServerModule 或者单元级全局变量里。ServerModule 进程内只有一份谁最后写谁生效。解决把所有跟「当前用户」相关的对象挪进 MainModule全局变量一律改成 MainModule 的字段。排查手法是在赋值处打一行日志把会话标识一起打出来串数据的时候一眼就能看到两个会话写的是同一个对象。5.3 窗体关掉再打开就报错或者内存一路涨现象第一次打开正常关掉再开报访问违规或者开合十几次后进程内存明显上涨。原因两种可能一是设计期把窗体设成了自动创建代码里又Create了一次二是Create了但关闭后没Free。解决在工程选项里把所有业务窗体从自动创建列表移除统一走Create(UniApplication)加finally Free。检查方法很简单在窗体的Destroy里打日志开合几次看日志条数对不对得上。5.4 中文乱码存进库里是问号现象页面上的中文正常但提交上来的内容进库变问号或者反过来库里读出来是乱码。原因链路有三段编码——页面响应、请求体、数据库连接任何一段不是 UTF-8 都会断。示例包默认配置多半只覆盖了其中一段。解决CharSet设utf-8数据库连接参数里显式指定字符集如果中间有反向代理确认它没有改写Content-Type。排查时不要猜直接在保存函数入口把原始字符串的字节长度打出来长度异常就说明在更早的环节已经坏了。5.5 空闲一段时间后点击没反应刷新才好现象用户填表单填了十分钟点保存没有任何反馈刷新页面后要求重新登录。原因SessionTimeout到期会话对象被销毁此时前端发出的请求打到了一个已经不存在的会话上服务端通常不会给出友好提示。解决把SessionTimeout按业务节奏调到合理值前端加一个到期前的提醒对关键表单做本地暂存会话失效后能恢复。这类问题不是纯技术问题配置值要跟业务方对齐我一般会把这个值写进部署文档而不是代码注释里改的时候有人盯着。6. 二次开发的最小骨架与一次冒烟验证摸清一个来源年份较早的示例包之后别急着在它上面改业务先抽一个最小骨架出来用最少的三件套验证整条链路ServerModule 只留配置、MainModule 只留会话资源、一个TUniForm当壳。骨架跑通再把业务窗体一个个搬进来出问题时你能确定是新代码还是环境。骨架里我会固定加一段冒烟脚本每次改完配置跑一遍比开浏览器点半天可靠# 冒烟三连首页可达、会话建立、静态资源可取 curl -s -o /dev/null -w home:%{http_code} t:%{time_starttransfer}\n http://127.0.0.1:8077/ curl -s -c /tmp/uni.cookie -o /dev/null -w session:%{http_code}\n http://127.0.0.1:8077/ curl -s -b /tmp/uni.cookie -o /dev/null -w ext:%{http_code}\n http://127.0.0.1:8077/ext/三个数字对应三件事第一个看服务是否活着、首字节时间是否正常第二个看会话 cookie 有没有下发这是后面所有交互的前提第三个看静态资源前缀配得对不对。time_starttransfer这个指标特别有用它在资源变多、主题变重的时候会先恶化比等用户投诉早得多。再往下走二次开发有两个提效点。一是把窗体基类统一所有业务窗体继承自同一个带权限校验、带异常兜底、带加载遮罩的基类避免每个窗体各写一套。二是把「会话级资源」的初始化收敛到一个方法里MainModule 的Create只调这一个方法将来加连接池、加缓存、加埋点都只改一处。下面这张表是我判断骨架是否合格的清单验证项做法合格标准会话隔离两个浏览器各登录一次交替操作数据互不影响日志里会话标识不同资源释放同一窗体开合 20 次进程内存回到基线附近超时行为把超时改成 1 分钟等 2 分钟后操作有明确提示不是静默失败并发写入两个会话同时保存同一单号一方成功一方明确报错无脏数据最后说个习惯。早年我在一个内部系统上偷懒把连接对象放在了全局单元里单人测试一周都没事上线第一个小时就出了数据串号排查花了两天因为日志里根本看不出会话边界。从那以后我每次拿到这类示例包第一件事不是读业务代码而是把 ServerModule、MainModule、Form 三层的对象归属列成一张表逐个确认每个对象到底活在哪一层确认完再动手。这个动作现在看来只花二十分钟但省下来的都是半夜被叫起来的时间。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/23 5:23:59

企业大脑落地指南:从知识工程到大模型应用的关键路径

1. 别急着上AI,先看看你的钱都花哪去了这两年我走访了不少企业,有个现象特别有意思:一说“数字化转型”,老板们眼睛发亮,预算拨得也痛快;一说“导入AI”,大家更兴奋,觉得终于要赶上浪…

2026/10/23 5:23:59

AI代理落地实战:从增强型LLM到可停机工作流

简介:本资源是Anthropic官方发布的《构建高效的 Agents 指南》全文中文翻译版,面向AI开发者、LLM应用工程师及Agent系统架构实践者,聚焦解决AI代理落地中“重技术堆砌、轻流程价值”的普遍误区。报告直击核心:强调以实际工作流痛点…

2026/10/23 5:23:59

Dify + SpringBoot + Vue 集成实战:SSE 流式对话链路全拆解

1. 先想清楚整体架构:这套组合到底在解决什么问题1.1 为什么要引入Dify作为AI中间层如果你手头有一个需要对接大模型能力的业务系统,最直接的做法是去调模型本身的接口,把提示词模板硬编码在代码里,再自己维护多轮记忆、知识库检索…

2026/10/23 7:39:11

动态规划与滚动数组:LeetCode 712 最小ASCII删除和详解

1. 题目拆解:什么是最小ASCII删除和1.1 先理解题目的本质LeetCode 712这道题,给定两个字符串s1和s2,要求我们通过删除字符的方式,使得两个字符串变得完全相同。但删除字符是有代价的,每个字符的代价就是它的ASCII码值。…

2026/10/23 7:39:11

OpenCV图像处理翻译实战:代码验证与环境兼容的九十九篇积累

这个系列做到第九十九篇的时候,心里是有些感慨的。最初接手 PyImgSearch 博客中文翻译这个项目时,我以为最费心思的是术语怎么翻、长难句怎么处理,真正做了几十篇之后才发现,工作量最大的根本不是翻译,而是让每一段示例…

2026/10/23 7:39:11

Android仿外卖点餐双列表联动与吸顶头实现避坑

简介:这是一份基于 Android Studio 的仿美团外卖菜单实战项目源码包,面向已掌握安卓基础、希望通过完整案例提升工程能力的开发者。项目围绕菜单列表展示与交互展开,涉及约束布局等界面设计、列表控件与适配器、本地数据库和偏好存储、网络请…

2026/10/23 7:39:11

Minecraft启动器PCL2:免费开源,5分钟跑起来还能自动装Java

Minecraft启动器PCL2:免费开源,5分钟跑起来还能自动装Java PCL2(Plain Craft Launcher 2)是一款免费开源的Minecraft启动器:自动匹配并下载Java、多版本并存、一键导入模组整合包,下载、启动、崩溃排查全包…

2026/10/22 11:29:28

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/23 0:02:26

Verilog实现FPGA密码锁:矩阵键盘与状态机设计实战

说实话,这几年接触了不少想入门FPGA的朋友,如果你问哪个项目能一口气把Verilog的核心基本功串起来,我通常会推荐电子密码锁。这个项目看着不起眼,却把矩阵键盘扫描、按键消抖、状态机设计、动态显示刷新、外设控制这些数字逻辑里的…

2026/10/23 0:02:26

数据服务监控运维实战:从指标体系到告警治理与故障排查

周末凌晨两点,手机震了。第一反应是“数据服务挂了”。打开电脑看了下,果然,某张核心报表的数据产出时间已经超过SLA两小时,下游三个业务方连着发来问询。这种场景,做过大数据运维的人都不会陌生。数据服务的监控与运维…

2026/10/23 0:02:26

FreeRTOS任务通知实战详解:内核原理、API与避坑指南

裸机编程切到RTOS之后,任务之间的通信和同步永远是躲不开的坎。前面聊过队列、信号量、互斥锁、事件组,这次把FreeRTOS任务通知(Task Notification)单独拎出来讲透。原因很简单:任务通知是FreeRTOS里最容易用错、也最容…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑