祝愿大家身体健康!

 找回密码
 站点注册
查看: 25|回复: 1

[PBIDEA] PBIDEA:用 uo_tempfile 管理临时文件全解(唯一命名、托管自动清理与 Remove/Clear 的删除语义,PB10 基准 · PB12.5 实测)

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
PBIDEA:用 uo_tempfile 管理临时文件全解(唯一命名、托管自动清理与 Remove/Clear 的删除语义,PB10 基准 · PB12.5 实测)

阅读说明
- 适用版本:PB10 基准(PB12.5 实测,两版行为一致);对象声明在 PBIDEA 的 websuite.pbl 中,运行需 PbIdea.dll 运行库。
- 支持数据库:本文不涉及数据库,纯本地文件操作。
- 操作系统与环境:Windows 7+;临时目录跟随系统的 TMP/TEMP 环境变量;无需联网。
- 难度系数:★☆☆☆☆
- 前置知识:PowerScript 基本文件读写(FileOpen / FileWrite / FileClose)。建议先读本系列 uo_file 一篇,对 PBIDEA 文件类对象有个整体印象。

一、临时文件的老大难:PB 原生连个临时文件函数都没有

写导出功能的人几乎都绕不开临时文件:把查询结果落成 csv 给用户看一眼、给第三方组件喂一个中间文件、解压一个包到临时目录再处理。这事在别的语言里一行就解决,在 PowerBuilder 里得自己造轮子。

造轮子之前,我们按惯例先做了一轮负向探测,把「原生有没有现成函数」这件事在两版环境里各编译一次验证了一遍:


  • GetTempFile():PB12.5 与 PB10 双轨编译,一律报 C0051(Unknown function name);
  • GetTempDirectory():同样双轨 C0051;
  • 翻遍本机 PB12.5 帮助知识库(2138 个条目),没有任何临时文件相关的系统函数。


结论很干脆:PowerBuilder 原生没有临时文件 API。FileDelete 是有的(实测可用),FileOpen / FileWrite 也都在,但「给我一个不重名的临时文件名」「用完自动清掉」这两件事,原生帮不上忙。

于是手工方案通常长这样:拿系统时间加随机数拼一个名字、写到 GetCurrentDirectory() 或拼一个自以为是的临时目录、用完记得 FileDelete。三个痛点也就跟着来了:


  • 重名。两个实例并发跑,拼出来的名字撞了,数据互相覆盖;
  • 清理遗漏。正常分支记得删,异常分支忘了删,跑上几个月,应用目录里全是 xxx_tmp.csv;
  • 无处安放。临时文件写哪儿都有讲究,写程序目录会越积越多,写系统盘根目录权限又不够。


PBIDEA 的 uo_tempfile 就是冲着这三件事来的。这一篇把它 8 个方法的真实行为全部实机跑一遍,重点讲清一个最容易踩的坑:Remove 和 Clear 不是「摘除登记」,是立即删文件。

二、uo_tempfile 全貌:一个管理器,八个方法

uo_tempfile 是非可视对象(NVO),声明在 websuite.pbl,运行依赖 PbIdea.dll。它没有公开属性,所有能力集中在八个方法上:

方法签名返回实测行为
GetFile无参string(全路径)进程hex_32位大写hex,只给名不建文件
GetFile(fileNameOrExt)string以点开头当扩展名,否则当完整文件名
GetFile(fileNameOrExt, addManager)string第二参传 false 则不托管
Add(fileName)无(subroutine)立即登记托管;重复 Add 无害
Remove(fileName)无(subroutine)立即删除文件并摘除登记
Exist(fileName)boolean查托管名单,与磁盘无关
Clear无参无(subroutine)立即删除全部托管文件
GetPath无参string系统临时目录,跟随 TMP/TEMP 环境变量


构造函数和析构函数内部已自动挂钩 Init / UnInit:create 时初始化管理器,destroy 时把仍在托管名单里的文件全部删掉。你不需要调用任何「初始化」动作。

名字长什么样

实测(同一进程里连调三次无参 GetFile):


  • 三次返回各不相同,前缀是 8 位十六进制的进程标识,本进程内固定(例如 007580d8),后面跟 32 位大写十六进制随机串;
  • 完整形态是全路径:临时目录 + 进程前缀 + 随机串,可以直接扔给 FileOpen;
  • 换一个进程再跑,前缀就变了。也就是说名字在进程内唯一、跨进程必不同,并发安全这一点是白送的。


一个必须先说清的边界:GetFile 只给名,不建文件

实测最容易被误判的一件事:调用 GetFile 之后立刻 FileExists,结果是 false。它只负责生成一个保证唯一的名字,文件本身不存在,要你自己去写。所以「拿到名字」和「文件可用」是两步,写代码时别把 FileExists=false 当成出错了。

三、最小可用示例

下面这段代码可以直接放进窗口的 open 事件(或任何函数)里跑。输入值只有文件内容,前段定义好了;结尾用 MessageBox 输出全过程。
  1. // 托管用法:拿名 → 写文件 → 用 → destroy 自动清理
  2. uo_tempfile lt
  3. string ls_file, ls_report
  4. long ll_f
  5. lt = create uo_tempfile              // 创建管理器,构造时已自动初始化
  6. ls_file = lt.GetFile('.csv')         // 拿一个随机名、.csv 扩展名的临时文件全路径(此时文件尚不存在)
  7. ll_f = FileOpen(ls_file, LineMode!, Write!, LockWrite!, Replace!)
  8. FileWrite(ll_f, 'name,qty')          // 写表头
  9. FileWrite(ll_f, 'apple,3')           // 写数据行
  10. FileClose(ll_f)
  11. ls_report = '文件:' + ls_file
  12. ls_report = ls_report + '~r~n写完之后磁盘上:' + string(FileExists(ls_file))
  13. ls_report = ls_report + '~r~n管理器登记中:' + string(lt.Exist(ls_file))
  14. destroy lt                           // 析构即删除全部托管文件,这里 ls_file 被删掉
  15. ls_report = ls_report + '~r~ndestroy 之后:' + string(FileExists(ls_file))
  16. MessageBox('uo_tempfile 演示', ls_report)
复制代码

实际运行结果(本机实测):写完之后磁盘上 True、管理器登记中 True、destroy 之后 False。中间那个 csv 文件从生到死一行清理代码都没写,这就是托管的意义。

四、三种取名方式:无参、扩展名、完整文件名

GetFile 的第二形态参数名叫 fileNameOrExt,实测它确实是「二合一」语义:

调用写法实测返回(截取文件名部分)语义
GetFile()007580d8_93B328A6…B6C随机主体,无扩展名
GetFile('.xlsx')00652df0_571AFFAC….xlsx随机主体,扩展名生效
GetFile('mydata.dat')00652df0_mydata.dat完整文件名保留
GetFile('rep97.xlsx')00652df0_rep97.xlsx同上,名字随意


规律一句话:参数以点开头 → 当扩展名用,主体仍然随机;否则 → 当完整文件名用。两种情况下,返回串前面都会拼上进程前缀(那个 8 位十六进制),所以就算你指定了 mydata.dat,实际落盘名字也是 00652df0_mydata.dat,不会真叫 mydata.dat。这一点在按名字找文件时要注意——别拿你传进去的名字去拼路径,要拿返回值。

第三形态再加一个 addManager 参数,传 false 表示「名字照给,但我不托管」:


  • 不托管文件的 Exist 恒为 false;
  • 对象 destroy 之后,托管文件被删,不托管文件原样保留(实测:托管侧 post=false,不托管侧 post=true)。


什么时候用不托管?这个临时文件你打算给别人、要挪走、或者要活过本对象的生命周期时,就别让管理器替你删。

五、Exist 查的是名单,不是磁盘

Exist(fileName) 的语义实测是这个路径是否登记在托管名单里,跟文件在不在磁盘上没有关系。两个证据:


  • 一个自己手工创建、真实存在的文件,Add 之前 Exist=false(磁盘上有,名单上没有);
  • GetFile 之后文件根本没建,Exist=true(名单上有,磁盘上没有)。


所以 Exist 不能当「文件存在判断」用——那是 FileExists 的活。它真正的用途是回答「这个文件还归不归我管」。

六、Remove 与 Clear:都是立即删除

这是全文最反直觉、也最容易出事故的一处。看名字容易以为 Remove 是「从名单里摘掉」、Clear 是「清空名单」,实测两者都是先把文件从磁盘上删掉,再摘登记:


  • Remove(路径):立即删除该文件。实测:Remove 之后再查 FileExists,结果 false——注意这一步查在 destroy 之前,文件确实当场就没了;
  • Clear():立即删除名单里全部托管文件。实测:Clear 之后两个托管文件的磁盘存在性都是 false;
  • Remove 一个没登记过的路径、Add 一个不存在的路径,都不报错(subroutine 无返回值,实测无异常抛出),只是名单上没它的位置,destroy 时自然也不会去删一个不存在的东西。


把这三条合起来,清理策略就很清晰了。下面这段代码把三档清理各演示一遍,可直接放进窗口事件或函数里运行:
  1. // 三档清理:Remove 单删、Clear 全删、destroy 兜底
  2. uo_tempfile lt
  3. string ls_a, ls_b, ls_report
  4. long ll_f
  5. lt = create uo_tempfile
  6. ls_a = lt.GetFile('a.log', false)    // 拿两个不托管的名字(先自己建出来)
  7. ls_b = lt.GetFile('b.log', false)
  8. ll_f = FileOpen(ls_a, LineMode!, Write!, LockWrite!, Replace!)
  9. FileWrite(ll_f, 'a')                 // 建第一个文件
  10. FileClose(ll_f)
  11. ll_f = FileOpen(ls_b, LineMode!, Write!, LockWrite!, Replace!)
  12. FileWrite(ll_f, 'b')                 // 建第二个文件
  13. FileClose(ll_f)
  14. lt.Add(ls_a)                         // 登记进托管名单
  15. lt.Add(ls_b)
  16. ls_report = '登记后 A=' + string(lt.Exist(ls_a))
  17. lt.Remove(ls_a)                      // 第一档:当场删 A
  18. ls_report = ls_report + '~r~nRemove 后 A:' + string(FileExists(ls_a))
  19. ls_report = ls_report + '~r~nRemove 后 B 还在:' + string(FileExists(ls_b))
  20. lt.Clear()                           // 第二档:全删,B 也当场消失
  21. ls_report = ls_report + '~r~nClear 后 B:' + string(FileExists(ls_b))
  22. destroy lt                           // 第三档:兜底(此时名单已空,无可删)
  23. MessageBox('三档清理演示', ls_report)
复制代码

实测输出:登记后 A=True、Remove 后 A:False(当场删)、Remove 后 B 还在:True、Clear 后 B:False(也当场删)。

七、生命周期:destroy 即清理,进程退出有兜底

托管文件的删除时机实测有两档:


  • destroy 对象:析构钩子 UnInit 立即清名单,实测 destroy 之后托管文件全部消失;
  • 进程退出:如果对象一直活着没 destroy,理论上来不及走析构,运行库在退出时也会做一轮清理。这一条在实机测试环境里无法直接观察(进程一退所有证据都没了),本段标注:未实测,仅静态核对源码注释(源码注释原文:退出时会自动删除)。


实践建议别赌第二条:临时文件的生命周期要短,用完 destroy 或显式 Clear,别让「退出兜底」变成常态。

顺带说一个边角:destroy 之后再访问对象的任何成员(比如 GetPath),直接报 Null object reference 运行时错误,双轨实测一致,TRY-CATCH 能捕到。这属于 PB 通用行为,列在这里只是提醒:管理器用完再 destroy,别销毁了还想接着用。

八、GetPath 与临时目录的选择

GetPath() 返回的就是它给文件安排的目录。实测返回值是 C:\Users\…\AppData\Local\Temp\…\ 这样的路径——跟着进程的 TMP/TEMP 环境变量走,不是硬编码某个目录。本机测试时进程的 TEMP 被指到了一个自定义目录,GetPath 就老老实实返回了那个目录。

这带来两个实用推论:


  • 部署时如果需要把临时文件集中到某个盘(比如 D 盘空间大),可以在这个进程内设置 TEMP 环境变量(配套的 uo_sysenv 能做),GetPath 和 GetFile 就都跟着过去了;
  • 拼自己的文件名时用 GetPath() + '你的名字' 而不要硬编码 C:\Windows\Temp,权限问题和盘符问题都省了。


九、与手工方案对比

维度手工拼名 + FileDeleteuo_tempfile
唯一性自己拼(时间+随机数,仍有撞名概率)进程前缀+随机 hex,进程内唯一
建文件自己 FileOpen 建只给名,建文件仍归你(这是差异点)
清理每个分支手写 FileDeleteRemove / Clear / destroy 三档自动
异常路径try 外忘删就残留destroy 兜底,托管名单内的必被清
临时目录自己猜(硬编码风险)GetPath 跟随 TMP/TEMP


手工方案唯一「赢」的地方是自由:想删哪个删哪个、想什么时候删什么时候删。但 Remove / Clear / destroy 三档已经覆盖了手工的全部场景,而且不会忘。结论:除非临时文件要跨进程交接(那用不托管 + 自己管理),否则没有理由再手工拼。

十、坑清单(全部实测背书)


  • GetFile 只给名不建文件:拿到路径立即 FileExists=false,是正常的,接着写就行;
  • Exist 是名单语义:查「是否托管」,不查磁盘;判断文件在不在用 FileExists;
  • Remove 立即删文件:不是摘名单,删完就没了,还想用就得重写;
  • Clear 立即删全部:按下 Clear 之前确认名单里没有还要用的文件;
  • destroy 即清理:托管文件在 destroy 那一刻全部消失,之后还想读?没门。要留活口的用 addManager=false;
  • 指定了名字也会被加前缀:GetFile('mydata.dat') 落盘名是 进程hex_mydata.dat,找文件要用返回值,不要用传入值拼路径;
  • 以点开头才是扩展名:GetFile('.csv') 随机主体 + .csv;GetFile('csv') 会得到一个叫 进程hex_csv 的无扩展名文件;
  • GetFile('') 空串:按无扩展名处理,返回随机名,不崩溃、不报错;
  • GetPath 跟随 TMP/TEMP:不是固定目录,程序在不同机器上返回值不同,别拿返回值做业务判断;
  • Add 重复无害:同一文件 Add 两次,Exist 仍 true,destroy 只删一次;
  • PB 原生没有 GetTempFile / GetTempDirectory:双轨编译 C0051 实证,帮助库亦无,不要花时间找原生替代;
  • destroy 后访问必炸:Null object reference,TRY-CATCH 可捕;先 destroy 后使用的写法在代码评审时直接打回。


十一、小结

uo_tempfile 干的事很小,但把「临时文件」这个没人愿意认真处理的问题做成了三句话:GetFile 给你一个保证唯一的名字,Exist 告诉你它归不归管,Remove / Clear / destroy 负责收拾干净。唯一的语义陷阱是 Remove 和 Clear 的删除是当场生效的,记住「名单动作 = 磁盘动作」这条,剩下的按直觉用就行。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
 楼主| 发表于 1 小时前 | 显示全部楼层
本帖最后由 pbai 于 2026-11-14 06:15 编辑

PB10 兼容版附件说明(art97.pbl)

一、这是什么
本文的 PB10 兼容版演示包:把文中两个示例(托管用法、Add/Remove 三档清理)整合成一个可直接跑的对象 nvo_art97,跑一遍即可复现文章全部关键读数。

二、环境要求

  • PowerBuilder 10 基准(本包 PBL 以 PB10 编译 0 错误),PB12.5+ 同一份源码直接用;
  • PBIDEA 运行库 PbIdea.dll(32 位),uo_tempfile 声明在 websuite.pbl,两样都要挂;
  • Windows 7+,无需数据库、无需联网。


三、操作步骤

  • 把包内 pbl/art97.pbl 加入应用库列表;
  • 确认 websuite.pbl 也在库列表;
  • PbIdea.dll 放应用目录或 PATH;
  • 任意窗口事件里调用(见下)。


四、调用片段(可直接抄)
  1. nvo_art97 lo
  2. string ls
  3. lo = create nvo_art97
  4. ls = lo.of_run()
  5. MessageBox('uo_tempfile 演示', ls)
  6. destroy lo
复制代码

五、期望读数(自验证)
mgr1=true、after1=false(destroy 自动清理生效)、mgr2=false(不托管)、added2=true(Add 入名单)、removed2=false(Remove 退名单)、disk2=false(Remove 立即删文件)。六项全命中即包正常。对象名与进程前缀、随机 hex 每次运行不同属正常现象。

六、常见坑

  • Error opening DLL library:PbIdea.dll 位置或位数不对(必须 32 位);
  • C0001 Illegal data type: uo_tempfile:liblist 没挂 websuite.pbl;
  • 拿到名字 FileExists=false:正常,GetFile 只给名不建文件;
  • Remove 后文件没了:正常,Remove 是立即删除不是摘名单。


七、实机验证情况
PB12.5 轨与 PB10 轨:各自独立解包、独立全量重编译均 0 错误,运行断言全 PASS,读数两版一致。未实测项:对象不销毁、进程直接退出时的运行库兜底清理(无法取证),文中已按静态核对标注。

art97_pb10.zip (6.87 KB, 下载次数: 1)
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-11-14 08:01 , Processed in 0.028991 second(s), 9 queries , MemCached On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表