ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

金蝶云星空集成Delphi DLL与FastReport实现企业级自定义打印

金蝶云星空集成Delphi DLL与FastReport实现企业级自定义打印 1. 方案选型为什么是金蝶云星空加Delphi DLL1.1 一个老牌ERP遇上的新打印难题金蝶云星空企业版在制造业、流通业里用得很广单据流转、存货核算、BOM管理都靠它。但真到了实际业务现场总会碰到一类让人挠头的问题标准打印模板不够用。比如客户要的送货单是三联不等宽复写纸每个栏位的位置、字体、条码密度都有硬性要求又比如质检报告要套印在自家设计的固定版式上一个像素都不能偏。金蝶自带的打印方案做到一定程度就会撞上瓶颈报表设计器的自由度、数据源二次加工的灵活度都差那么一点意思。这时候自然想到两条路一是用C#插件直接改金蝶的打印逻辑二是在外部做一个独立的打印服务。前者的坑在于金蝶的打印引擎本身也是封装的你想在它的事件里做太多自定义操作调试起来非常痛苦。后者则要考虑怎么跟金蝶的数据对接、怎么触发一堆网络和部署问题。我最终的选择是让金蝶云星空通过DLL的方式直接调用Delphi写的打印组件核心报表引擎用FastReport。这个组合的妙处在于金蝶只负责传参数真正干活的是一个独立的DLL。每个字段映射成DLL的参数DLL内部用FastReport的报表模板完成复杂的排版和渲染再把结果送进打印机。这套架构既绕开了金蝶打印引擎的限制又保住了打印环节的绝对可控。1.2 为什么选Delphi加FastReport而不是C#或Java先聊聊Delphi这个看着“上了年纪”的家伙。Delphi的编译器生成的是原生代码不像C#那样跑在.NET虚拟机上也不像Java那样跑在JVM上。它编译出来的DLL体积小、依赖少、启动快就算目标机器上没有装任何运行时环境只要把DLL丢过去就能用。对于金蝶云星空这种企业级系统服务器环境往往受严格管控能少装一个框架就少装一个省心不是一星半点。FastReport则是Delphi生态里报表控件的老牌选手。它的设计器是可视化的拖拖拽拽就能把报表模板画出来支持文本、图片、条形码、二维码、RichText这些常见元素。模板以.fr3文件形式独立于代码存在业务方要调整打印格式只要改模板不用重新编译DLL这让后期维护成本降了一大截。再加上它支持导出PDF、Excel、图片打印预览窗口也做得相当顺手做企业级套打完全Hold得住。提示如果你们的金蝶云星空服务器有强制的安全策略要求DLL必须签名或者白名单Delphi的DLL同样可以走代码签名这个后面再说。2. 前置准备金蝶云星空和Delphi开发环境的搭建2.1 记账清单需要准备的组件与工具动手之前先把环境理清楚。缺少任何一环后面调试会浪费大量时间。金蝶云星空企业版版本建议8.1及以上二开插件机制比较完善。如果是老版本部分接口名称有差异需要在开发时查阅对应版本的BOS平台接口文档。Delphi版本推荐Delphi 10.4或11/12这几个版本对Win64的编译支持稳定FastReport的适配也较为成熟。Delphi 7虽然老当益壮但64位支持太弱不建议用于新项目。FastReport需要安装与Delphi版本匹配的FastReport源码版或编译版版本建议6.x以上性能更好高DPI支持也完善。金蝶BOS二开插件环境金蝶云星空的二开用的是Visual StudioC#虽然你的DLL是Delphi写的但需要一个C#的插件项目来承担“二开插件调用Delphi DLL”的桥接工作。PDF阅读器、条码字体部分场景喜欢把FastReport模板的条形码直接交给打印机这时需要安装对应的条码字体如Code128、EAN-13字体。2.2 FastReport设计器的基本操作与模板规划FastReport的报表模板设计是整套方案里最耗精力也是最值得花时间的部分。报表模板决定了最终打印品的“脸面”业务方对格式的敏感度往往比功能本身还高。在设计模板前先跟业务方确认几个关键点纸张类型是A4、A5还是自定义宽度三联纸打印方向横向还是纵向套打还是非套打如果需要套打预印单据必须拿到预印纸张的扫描图1:1放在FastReport设计器中做底图参照。条码类型Code128、Code39、EAN-13不同条码在不同场景下适用标准不同。FastReport设计器打开后新建一个报表先设置页面尺寸与边距。如果要做三联纸页面宽度设置为实际纸张宽度左右边距清零然后通过Detail Data Band的高度来控制每一联的高度。条码控件在FastReport里属于BarcodeObject加入后直接设置条码类型和字段值。文本控件则可以设置字体的粗细、大小以及最重要的——对齐方式。很多人忽略的一点是FastReport里的对象位置、大小、边框全部以“像素”为单位而打印机的实际输出用的是“毫米”或“1/96英寸”所以模板尺寸一定要在设计器里先换算清楚。FastReport设计器底部状态栏会显示当前选中对象的坐标和大小可以对照着手动微调确保最终打印结果与模板完全一致。FastReport的设计器“文件-保存”会生成.fr3文件这就是我们要跟着DLL一起部署的模板文件。模板文件的命名建议直接跟业务单据挂钩比如SO_Print.fr3代表销售订单打印、QC_Report.fr3代表质检报告这样后期查找和维护都一目了然。3. 核心实现金蝶云星空调用Delphi DLL的完整链路3.1 金蝶侧的调用机制与二开插件设计金蝶云星空的业务单据销售订单、采购订单、入库单、出库单等都提供了BOS插件机制。我们可以在单据的“打印”事件里挂一个C#插件通过这个插件去加载Delphi DLL并把当前单据的字段值传过去。C#插件工程创建后需要引用金蝶的SDK常见的包有Kingdee.BOS.dll、Kingdee.BOS.Core.dll等。插件类继承AbstractBillPlugIn或AbstractOperationServicePlugIn。如果是要在打印按钮触发时介入建议重写OnPreparePagePrint或OnGetCustomPagePrintMethod这类和打印相关的虚方法不过最直接的方式是重写BeforeDoOperation捕获操作类型为Print的操作然后在这时调用DLL。伪代码思路如下C#插件侧public override void BeforeDoOperation(BeforeDoOperationEventArgs e) { base.BeforeDoOperation(e); if (e.Operation.OperationId 256) // 256通常是打印操作 { // 从当前单据上下文取出关键字段 string orderNo this.View.Model.GetValue(BillNo)?.ToString(); string customerName this.View.Model.GetValue(CustomerName)?.ToString(); // 调用Delphi DLL的导出函数 string jsonParams BuildJsonParams(orderNo, customerName); int result PrintDllInvoker.Execute(PrintOrder, jsonParams); if (result 0) { // 告诉金蝶打印操作已经被外部处理避免重复走默认打印 e.Cancel true; } } }这里有个关键设计C#插件不直接处理打印业务只负责把当前单据的字段提取出来拼成一段JSON或XML字符串传给Delphi DLL。这种通过中间层传递参数的方式让金蝶和Delphi之间的耦合降到最低后续哪怕换了打印模板或改动了金蝶字段名都只改插件里的映射逻辑不影响DLL本身。3.2 Delphi DLL侧的函数封装与数据接收Delphi DLL的编写很直接。新建一个Dynamic-Link Library工程在工程文件里导出几个关键函数初始化、打印、释放。Delphi的DLL导出语法如下library PrintCore; uses System.SysUtils, System.Classes, FastReport, frxClass, frxExportPDF; // 打印主函数jsonParams为JSON格式的打印参数 function PrintDocument(AParams: PAnsiChar): Integer; stdcall; var LJson: string; LReport: TfrxReport; begin Result : -1; try LJson : string(AParams); // 解析JSON填充fr3模板的数据源、参数、变量 LReport : TfrxReport.Create(nil); try LReport.LoadFromFile(D:\ReportTpl\SoPrint.fr3); // 给模板中的变量赋值 LReport.Variables[BillNo] : QuotedStr(GetJsonValue(LJson, BillNo)); LReport.Variables[CustomerName] : QuotedStr(GetJsonValue(LJson, CustomerName)); // 准备报表并打印 LReport.PrepareReport(True); LReport.Print; Result : 0; finally LReport.Free; end; except on E: Exception do begin // 日志记录方便排查 LogToFile(E.Message); end; end; end; // 对外导出函数 exports PrintDocument; begin end.这段代码里GetJsonValue需要自己封装一个简单的JSON解析函数或者引入Delphi的System.JSON单元。FastReport的变量赋值用QuotedStr包一层是为了保证字符串变量里有空格或特殊符号时FastReport引擎能正确解析。DLL工程编译时要注意FastReport的源码版在编译时需要设置好搜索路径确保frxClass、frxExportPDF这些单元能被找到。编译Win32版本时FastReport用Win32的包编译Win64版本时要确保FastReport也编译了Win64版本细节错误很容易在这一步埋下隐患。3.3 桥接层配置金蝶插件如何找到并调用DLLC#调用Delphi DLL最常见的方式是P/Invoke也就是在C#里声明DLL的导出函数然后用DllImport特性引入。C#侧声明的写法public class PrintDllInvoker { [DllImport(D:\PrintCore\PrintCore.dll, EntryPoint PrintDocument, CallingConvention CallingConvention.StdCall)] private static extern int PrintDocument(string jsonParams); public static int Execute(string jsonParams) { return PrintDocument(jsonParams); } }这里有个容易被坑的地方DllImport的第一个参数是DLL的路径。如果直接用绝对路径DLL位置固定测试方便但部署不灵活。如果只写DLL文件名系统会按PATH环境变量、程序所在目录、Windows系统目录依次查找。金蝶云星空的插件运行在w3wp.exeIIS应用池或Kingdee.K3Cloud.WebSite目录下系统搜索目录未必包含DLL所在路径最稳妥的方式是使用绝对路径或者把DLL放到金蝶站点的bin目录下。另一个隐蔽的坑是字符编码。Delphi导出函数的参数类型如果定义为PChar,在Win32下是AnsiChar在Win64下是UCS2编码。但C#里的string默认是Unicode常见做法是Delphi统一用PAnsiCharC#用StringBuilder或string并配合CharSet.Ansi 的声明。不然中文单据号、客户名称很容易乱码。注意Delphi的stdcall调用约定是Windows API标准约定而老的C语言默认是cdecl。两边一不一致调用时直接崩没有任何商量余地。4. 难点攻坚64位DLL兼容性与云星空环境适配4.1 32位与64位的世纪难题金蝶云星空企业版的Web服务进程K3Cloud在64位操作系统上默认跑64位。你的Delphi DLL如果编译成32位集成到64位进程里必然加载失败甚至让整个应用池直接崩溃。这是整个方案里最容易踩、也最要命的一个坑。解决思路有两个方向。方向一把Delphi DLL编译成64位版本。Delphi从XE2开始支持Win64编译FastReport也要对应使用64位版本编译。全部编译成64位后放在金蝶站点bin目录下由64位的K3Cloud进程直接加载性能和稳定性都最好。这也是我推荐的方案。方向二如果你保留了部分32位Delphi代码库无法轻易升级到64位就需要独立部署一个32位的打印进程。金蝶C#插件先把打印参数写进某个队列或中间表再由一个独立的Delphi编写的32位程序读取并执行打印。这个架构多了一个进程但把金蝶和DLL彻底解耦稳定性反而更高。不过需要额外处理进程间的通信和状态同步工程量大一些。64位编译具体操作是在Delphi IDE中Project Manager里右键你的DLL工程勾选Target Platform为64-bit Windows然后重新编译。FastReport如果是通过GetIt安装的会自动包含64位的DCU如果是手动安装的源码包需要重新编译64位版本的运行时包。编译完成后用Dependency Walker或dumpbin工具检查DLL是否还有32位依赖。如果某个第三方单元库只有32位版本优先替换实在没有替代的只能放弃该功能模块或改用32位进程方案。4.2 DLL加载失败与“目标DLL已取消”的排查方法金蝶环境里最常见的报错是系统提示“文件加载失败”“无法加载DLL或依赖项”或者网上俗称的“error: flash download failed - target dll has been cancelled”这句话最初来自单片机烧录场景但在很多DLL加载失败场景中用户用这个热词来检索同类问题。实际原因大多数不是DLL本身损坏而是依赖缺失或位数不匹配。排查顺序建议先单独写一个简单的Delphi调用程序或C#控制台程序加载这个DLL并调用导出函数确认DLL本身在独立环境中可用。再用Process Explorer或ProcMon监控K3Cloud进程加载DLL的路径看它是否真的能搜到你放的目录。检查目标机器是否安装了VS C运行库部分Delphi DLL依赖第三方C库会用到。虽然Delphi不依赖VC运行库但DLL内部如果调用了其他C库就说不准了。用Event Viewer的应用程序日志查看详细的错误模块信息。如果DLL本身可用只是金蝶进程加载时报错大概率是位数不匹配或DLL搜索路径问题。先把DLL放到金蝶K3Cloud的bin目录WebSite\bin下或者放到应用池的工作目录排除路径问题再确认编译版本和进程位数是否一致。这一步说不难但确实是很多新手折腾一整天的地方。5. FastReport模板设计实战参数传递、套打与条码5.1 金蝶字段到FastReport变量的映射关系FR3模板和DLL之间通过“变量”传递数据。FastReport的Variables在TfrxReport.Variables中DLL加载模板后可以直接给Variables赋值。变量名的格式通常是“全局变量名”赋值时要用字符串表达式所以数字和字符串要区分处理。例如在模板设计器中在菜单“报表-变量”里新增变量BillNo类型为字符串。那么Delphi里就可以写LReport.Variables[BillNo] : QuotedStr(SO202501160001);如果变量类型是整数LReport.Variables[TotalAmount] : IntToStr(Amount); // 这里也要用字符串表达式FastReport的变量机制相当于模板里的占位符系统模板里任何文本控件、条码控件的Text属性都可以写成[BillNo]报表准备好后FastReport会自动替换成实际值。5.2 套打底图、条码和自动换页的设计细节套打是出货单打印里最常见的高频需求。FastReport的设计方式是把预印好的纸张扫描成图片建议分辨率300dpi。打开FASTREPORT设计器在Page对象里添加一个PictureObject把扫描图设置进去透明度调到100%。在底图上精确摆放文本控件或条码控件每个控件的位置对着底图的栏位框。最后在代码里调用LReport.PrepareReport(False)通过PrepareReport的第二个参数控制是否显示预览。打印时如果底图不想被打印出来可以在报表的BeforePrint事件里动态设置这个PictureObject的Visible为False。条码部分FastReport自带BarcodeObject选中后类型选择Code128Auto、Code39、EAN-13等。条码内容可以关联变量也可以直接写字符串。注意条码下方的可读文本可以根据需求隐藏或显示一般套打单据只需要条码不需要下面的数字这时把ShowText设为False即可。自动换页的快照需求几乎人人都会遇到。比如一张单据有四联每联高度固定但明细行数不确定。处理方式是在明细数据带DataBand上设置Height为单行行高FastReport会在DetailDataBand铺满页面后自动换页并重复打印页头页脚同时可以在PageFooter里放“第 [Page#] 页 / 共 [TotalPages#] 页”。5.3 参数化报表日期区间、客户过滤、币别等条件打印往往不是简单地把当前单据打出来还要支持批量打印或条件过滤。比如月初要打印上个月所有销售订单或者只打印某个客户的送货单。这时DLL的入参除了单号还需要时间段、客户标识、币别等过滤条件。在DLL中先根据这些条件去查询金蝶数据库或调用金蝶提供的查询接口取得数据集后再动态生成FastReport的数据源。FastReport支持两种主流方式设计器中通过“数据-添加数据源”连接数据库或DataSet。完全在DLL中动态创建TfrxUserDataSet把查询结果逐行塞进去。用TfrxUserDataSet的好处是不需要在fr3模板里绑定固定的数据库连接后期换库、换连接字符串都不影响模板。Delphi中动态注册数据集的简化代码var LDataSet: TfrxUserDataSet; begin LDataSet : TfrxUserDataSet.Create(nil); LDataSet.Name : MyData; // 定义字段 LDataSet.Fields.Add(OrderNo); LDataSet.Fields.Add(CustomerName); // 每行数据的赋值逻辑在OnGetValue事件里处理 end;模板中的明细带DataBand设置为“MyData”数据源在Band的Data属性选择MyData列字段直接引用[MyData.OrderNo]。这样动态数据源和模板就关联起来了。这种方式对“金蝶推送多个单号批量打印”的场景特别有用一次调用DLL就能打印一批单据。6. 远程打印与打印服务化当金蝶和打印机不在同一台机器6.1 打印路由金蝶服务器如何把任务交给远程打印机金蝶服务器不一定连着打印机尤其在企业里服务器在机房打印机在收发货现场。DLL跑在金蝶服务器上直接弹打印对话框让用户选打印机是可行的但需要目标打印机共享到金蝶服务器上并且对打印机名称要做统一规划。常规做法在服务器上安装共享打印机或在打印逻辑里使用UNC路径直接指定打印机。FastReport的PrintOptions.Printer属性可以指定打印机名。这个名称必须和服务器上枚举到的打印机名完全一致。提前在服务器上用EnumPrintersAPI或调用GetPrinter枚举确认打印机名称。常见坑网络打印机的名字在服务器上可能是“\printserver\HP_LaserJet”这样的UNC格式而不是本地“HP LaserJet P2015”这种友好名。FastReport选择打印机的代码LReport.PrintOptions.Printer : \\printserver\HP_LaserJet; LReport.PrepareReport(False); LReport.Print;如果金蝶服务器不方便安装打印机驱动还有一条路DLL不直接打印而是把FastReport导出成PDF文件再调用一个常驻的远程打印客户端或Windows打印服务去处理这个PDF。这种做法把打印动作从金蝶服务器上剥离出来打印任务丢给打印服务打印服务去枚举打印机、管理队列、重试失败后期的打印状态追踪也好做很多。6.2 打印队列冲突与重试机制生产环境打印最烦人的是排队和冲突。FastReport没内置打印队列管理需要DLL自己实现一套轻量级打印状态管理用一张数据库表或本地配置文件记录每次打印任务的任务ID、单据号、状态待打印/成功/失败、失败原因。打印前先检查该打印机是否有未完成的任务如果有要么排队等待要么提示用户确认。我实际项目里还遇到过一种情况同一台打印机同时被多个K3Cloud应用池进程调用并行任务多时打印内容会出现丢失或错页。排查下来是打印作业在传输层被中断。后来在DLL里加了一个同步锁同一时间只允许一个打印线程操作同一台打印机问题基本就消失了。不过要注意加了锁后打印并发能力会下降如果量大需要把打印任务放到消息队列里来削峰填谷。7. 数据回写与状态联动打印完成后如何反写单据7.1 打印完成反写方案比轮询刷新更优雅的做法金蝶云星空本身有审计机制但用户往往希望知道自己“实际打印了几次、最后一次打印是什么时间、是谁打的”甚至在打印完成后自动把单据状态改成“已打印”。DLL本身只做打印不太适合直接改金蝶数据库因为金蝶的表结构变了你的DLL逻辑就崩了。推荐做法是让BOS插件负责反写。C#插件调用DLL打印前先记录时间、操作人、单据号DLL打印返回成功后插件通过金蝶的合法API去更新单据的扩展字段或状态// 反写打印信息到扩展字段 this.View.Model.SetValue(F_PRTZ_PrintTime, DateTime.Now); this.View.Model.SetValue(F_PRTZ_PrintCount, (int)this.View.Model.GetValue(F_PRTZ_PrintCount) 1); this.View.Model.UpdateValue(F_PRTZ_PrintTime); this.View.Model.UpdateValue(F_PRTZ_PrintCount);如果DLL是独立进程打印完成后DLL可能需要回调金蝶的WebAPI来更新状态。这种场景下建议在金蝶里提前建好WebAPI二开接口DLL通过HTTP POST触发。注意鉴权方式金蝶的WebAPI通常支持两种令牌AppSecret模式或用户名密码登录模式。生产环境用令牌模式更合适安全性和稳定性都有保障。7.2 扩展字段与打印日志的埋点设计无论走哪种反写方式建议在数据库里保留一份独立的打印日志表表结构大致这样字段类型说明FID主键日志唯一IDFBillNovarchar(50)对应金蝶单据号FPrinterNamevarchar(100)实际使用的打印机名FPrintTimedatetime打印时间FPrintUservarchar(50)操作者FTemplateNamevarchar(100)使用的fr3模板名FResultint0成功 1失败FErrorMsgvarchar(500)失败原因这张表由C#插件或Delphi DLL直接写库都行取决于你DLL有没有连接数据库的权限。如果DLL没有数据库权限插件拿到DLL返回结果后负责写日志。这样后续做打印追溯、失败统计甚至对接条码扫描校验都有数据支撑。8. 部署、权限与常见报错速查8.1 服务器端的部署清单与环境配置把整套方案搬上生产环境最怕漏步骤。以下是我每次部署都会逐项检查的清单Delphi DLL及其依赖的fr3模板文件复制到金蝶站点bin目录或者单独建一个PrintService目录。如果DLL动态加载fr3模板模板路径建议不要用绝对路径用DLL所在目录拼接否则换一台服务器就得改代码。Delphi里获取DLL所在路径的方法是GetModuleFileName(HInstance, Buffer, SizeOf(Buffer)); // 取目录部分确认应用池的“启用32位应用程序”设置如果DLL编译的是32位而应用池是64位有可能需要在IIS应用池高级设置中把“启用32位应用程序”改为True但这会拖累整个站点性能实在不推荐。注册FastReport所需字体如果使用了特殊字体。如果DLL需要写日志或导出PDF确保应用池账户对目标目录有写权限。金蝶的应用池账户可能是NetworkService或自定义域账户默认对D盘新建目录没有权限经常导致文件写入失败。8.2 高频报错的定位方法现象可能原因处理思路金蝶插件调用DLL无反应位数不匹配 / 路径不对用C#控制台单独测试DLL检查位数与路径报错System.DllNotFoundExceptionDLL不在搜索路径强制用绝对路径加载或放bin目录固定位置报错AccessViolationException参数类型不匹配或DLL内部内存访问异常检查stdcall与cdecl、检查字符串编码方式报错Unable to load DLL依赖缺失用dumpbin或Dependency Walker查依赖项FastReport打印无输出打印机名不对 / 打印机未共享枚举服务器上所有打印机名称核对是否含UNC前缀打印中文变问号字符串编码不对统一Delphi侧参数改为PAnsiCharC#侧CharSet.Ansifr3模板加载失败模板路径错误或模板文件版本不符确认模板文件与FastReport版本匹配模板文件版本不匹配是那种你根本想不到的烂坑。比如你用FastReport 6.x设计器新建的.fr3模板拿到FastReport 5.x的DLL环境里去加载可能无声无息地打不开或只解析出一部分。建议部署时把FastReport版本号写进日志头遇到问题时第一时间定位版本差异。8.3 权限设置应用池账户与文件写入DLL如果要在运行时写日志或者导出PDF金蝶站点的应用程序池账户必须对相关目录有写权限。默认情况下IIS应用池使用ApplicationPoolIdentity账户这种账户的权限很有限。我在生产环境遇到过一个问题DLL导出PDF成功但文件写不到网盘目录。排查后确认是应用池账户没有访问网盘映射盘的权限。解决方案是给应用池更换为域账户并授予对应目录的读写权限。但要注意域账户密码变更时需要同步更新IIS应用池设置否则第二天早上打印服务就静默罢工了。另一个容易被忽略的点金蝶的打印插件回写扩展字段时登录金蝶的用户通常有权限限制。插件在服务端执行时用的上下文中操作用户可能就是当前登录用户但也有系统管理员上下文的情况。如果反写字段失败优先检查当前操作员是否有该单据的修改权限。9. 性能优化与稳定性保障9.1 FREMModel与FastReport的启动速度优化FastReport的DLL加载和模板PrepareReport在绝大多数场景下耗时可以控制在几百毫秒内。如果发现打印操作有明显的卡顿感通常问题出在以下几个方面模板文件放在网络共享盘上每次加载都要走网络耗时会成倍增加。解决办法是模板放到本地磁盘通过文件同步机制比如robocopy定期同步保持更新。数据源查询太慢。如果DLL每次打印前都去数据库拉全量数据再填充报表慢是必然的。解决办法是细化参数过滤只拉会打印的数据。频繁创建和释放TfrxReport对象。TfrxReport虽然不重但在高频打印场景下反复创建、加载模板、释放开销仍然可观。可以在DLL初始化时创建报表对象池打印时从池里取一个用完了还回去大幅减少对象创建和销毁的损耗。9.2 与金蝶插件生命周期的一致性设计金蝶BOS插件的实例生命周期由金蝶框架管理一个页面实例对应一个插件对象。插件中调用DLL建议做成静态方法避免插件实例化时反复加载DLL。如果用DllImportCLR会缓存DLL的加载句柄一般不用特别处理。但如果你在插件代码里用LoadLibrary动态加载记得在页面关闭时用FreeLibrary释放不然DLL文件会被锁住下次更新DLL时替换不了文件。关于日志记录DLL内部建议用文本文件或者系统事件日志记录关键步骤。生产环境出问题时没有日志等于两眼一抹黑。我的习惯是DLL每个导出函数入口先写一行“进入打印函数参数xxx”出口再写一行“打印完成结果xxx”。打印失败的异常堆栈也要完整记录包括Excel选项。这样用户报问题时我只要看日志文件就能初步定位不用远程连服务器反复复现。9.3 高可用多台金蝶服务器部署时如何保证DLL一致大型企业的金蝶云星空常常部署在多台前端服务器上通过负载均衡对外提供服务。DLL如果只更新了其中一台打印行为就会出现“时好时坏”的诡异现象。建议把DLL和fr3模板作为独立的发布包纳入统一的构建部署流程通过发布工具分发到所有前端服务器。版本管理建议用下面这个组合DLL文件版本号在Delphi工程中设置编译后从文件属性可见。模板文件在FastReport设计器的“报表属性”里记录模板版本号。DLL启动时读取模板版本号如果发现模板版本和DLL预期版本不一致记录警告日志甚至拒绝打印避免用旧模板打出错误格式。10. 扩展思路这套方案的更多应用场景10.1 不局限在打印DLL还能做数据导入导出、格式转换Delphi DLL这套桥梁机制不只能做打印。金蝶云星空的二开插件完善但有些业务需要更贴近底层、更高性能的处理比如大批量数据导入、Excel复杂报表生成、图片处理、PDF合并等。只要把DLL的导出函数设计成“接收JSON参数返回JSON结果”金蝶和DLL之间的交互就统一了后续加功能就是往DLL里加函数的事。举个例子金蝶要导出跨年度的复杂统计报表如果用C#报表引擎做可能需要引用一堆库性能也不好。如果把这个任务交给Delphi DLL用FastReport把结果渲染成PDF或Excel速度飞快且内存占用低效果很理想。10.2 对接WebAPI云机构与本地业务深度联动金蝶云星空如果是私有化部署DLL直接在服务器上跑没问题。但如果你用的是公有云版本K3Cloud并不在你本地网络这时候DLL就得跑在本地业务服务器上由金蝶云端通过WebAPI发送打印请求到本地打印服务本地打印服务再调用Delphi封装好的打印DLL。这种做法是把打印独立成微服务同时支持多端调用手机审批通过后触发打印、API接口触发批量打印等适用面更宽。10.3 后续演进从DLL到独立打印服务坦率地说DLL调用方案在金蝶二开里已经非常成熟但它依然受限于“谁调用谁执行”的模型。如果打印需求继续膨胀比如要支持Web端用户选择打印机、要支持排队重试、要支持打印预览确认那下一步就该把DLL封装成独立的Windows服务或控制台程序提供HTTP接口给金蝶调用。这等于把打印能力从“函数级复用”升级为“服务级复用”架构上更清晰扩展性也更强。我在一个项目中就把这套打印服务封装成了Windows服务金蝶插件只负责拼参数然后POST到本地服务端口服务内部再加载Delphi DLL执行打印。这样金蝶和打印模块彻底分离后续换报表引擎、增加打印规则库都互不影响而且可以轻松扩展多台打印服务器负载均衡。个人实操小结整个方案落地后我最深的体会是把金蝶的“表单世界”和Delphi的“打印世界”通过DLL这座桥连起来边界一定要划清楚。金蝶只负责“我有哪些数据、我需要打印什么”DLL只负责“我拿到数据后如何排版、如何输出”两者之间的交接协议越简单越好一份JSON、一个返回值不要夹带任何业务逻辑。另一个踩过的坑也顺便提醒一句FastReport在准备报表时PrepareReport(True)和PrepareReport(False)的区别是一个弹出预览、一个直接后台打印。很多人直接把参数写成True结果服务器上弹了个预览窗口没人点确定打印任务卡一整天。生产环境一定要用False调试时才用True。至此整套金蝶云星空调用Delphi DLL加FastReport实现打印的方案已经搭建完毕。你可以先从一张最简单的单据试起比如销售订单做好一张模板跑通全链路再慢慢扩展到其他单据。模板的后期维护建议交给业务侧熟悉FastReport设计器的人来跟进开发这边只要维持好DLL接口稳定就能在很长一段时间里舒舒服服地运行。
返回列表