本帖最后由 pbai 于 2026-9-20 17:27 编辑
【补充篇】PBIDEA 全局函数 choice:从多个值里挑一个(13 条边界实测 + 两个避坑)
本篇是上一楼《PowerBuilder 控制结构全解:IF / CHOOSE CASE / FOR / DO LOOP》的补充篇 —— 主题独立,讲的是 PBIDEA 新增的全局函数 choice。
版本声明
- 本文以 PBIDEA 当前最新版本为准,版本源目录:D:\PBAI\UPDATE\10\(即以该目录下的 PbIdea.dll 与源码导出为准)
- 核验日期:2026-09-20;当次核验的 PbIdea.dll:18,019,840 字节 / 2545 个导出,md5 dfc61467c288…(mtime 2026-09-20 16:18)
- 源码依据:export_20260920(575 个文件)
- PBIDEA 版本会持续更新,本文只对上述核验时点的最新版本负责;后续若行为有变,以新版为准
前置标注
- 适用版本:以顶部【版本声明】为准;本文写法 PB 10 及以上通用(依据见第五节)
- 难度系数:★★☆☆☆(2 / 5)
- 数据库:不依赖数据库
- 运行环境:运行目录内必须有 PbIdea.dll(本文实测用 2026-09-20 最新版 PbIdea.dll,放进 run 目录后 5 个重载全部可调用)
- 操作系统:Windows
一、它是怎么声明的:5 个重载指向同一个 DLL 导出
- global function any choice(boolean bCheck, readonly any a, readonly any b) SYSTEM library "PbIdea.dll" alias for "fnChoice"
- global function any choice(int index, ...) SYSTEM library "PbIdea.dll" alias for "fnChoice"
- global function any choice(readonly any arr[]) SYSTEM library "PbIdea.dll" alias for "fnChoice"
- global function string choice(readonly string str) SYSTEM library "PbIdea.dll" alias for "fnChoice"
- global function long choice(long maxValue) SYSTEM library "PbIdea.dll" alias for "fnChoice"
复制代码
以上 5 行取自 PBIDEA 源码 choice.srf,原文照抄未做改动。注意第 2 行用了 ... 变参:自定义 PowerScript 函数不支持变参,但外部函数声明可以 —— 所以 choice 才能按序号取"不定个数"的候选。
二、五个重载分别做什么(实机结果)
1. 二选一 choice(boolean bCheck, any a, any b):bCheck 为 true 返回 a,否则返回 b。实测 b_true=a、b_false=b。
2. 按序号取 choice(int index, ...):返回第 index 个后续实参,index 是 1 基。实测 i_1=a、i_2=b、i_3=c;只有一个候选时 choice(1, a) = a。
3. 数组随机 choice(any arr[]):从数组里随机取一个元素。实测 3 元素数组跑 20 次,x / y / z 都出现过。
4. 字符随机 choice(string str):从字符串里随机取 1 个字符。实测 20 次 choice('abcdef') 得到 20 个字符,样本 afcdcebebeedadcabfbc。
5. 整数随机 choice(long maxValue):返回 [0, maxValue) 内的整数。实测 200 次 choice(10),最小 0、最大 9。
返回值类型:候选是字符串时 ClassName(any) 得到 string —— any 里装的就是候选本身,没有做类型转换。
三、第一个坑:choice 从不抛异常,非法输入一律"静默返回 NULL"
13 条边界输入逐条隔离实测,全部 BRANCH=OK —— 一条异常都没抛:
○ choice(0, a, b, c) 索引 0,低于下界 → NULL
○ choice(5, a, b, c) 索引越过候选个数 → NULL
○ choice(2, a) 只有 1 个候选却要第 2 个 → NULL
○ choice(-1, a, b, c) 负索引 → NULL
○ choice(0) maxValue = 0 → NULL
○ choice(-5) maxValue 为负 → NULL
○ choice('') 空串 → NULL
○ choice(true, NULL串, 'a') 候选本身是 NULL → NULL(原样把那个 NULL 返回)
○ 对照组全部正常:choice(2, a, b, c)=b、choice(1)=0、choice('abcdef')=随机字符、choice(false, a, b)=b
结论:别指望 try ... catch (runtimeerror) 兜住 choice 的错误用法 —— 它不报错,直接给你 NULL。必须自己 IsNull() 判空。
四、第二个坑:一个 NULL 会污染整条拼接串
这个坑我在测试里连踩两次,值得单独说。原写法:
- ls_ret = 'E1=' + String(la_r) + ';'
复制代码
当 la_r 是 NULL(choice 越界正是这种),String(la_r) 也是 NULL,而 PB 的 + 拼接遇到 NULL 就得 NULL —— 于是 ls_ret 整条变成 NULL,后面所有追加全部失效,最后 FileWrite 出去是一个 0 字节的空文件。
排查信号很有辨识度:函数明明正常返回、测试也报 PASS,但诊断文件是 0 字节,十有八九就是 NULL 传染。本次就是靠"9 条用例的 pre / after 文件全都生成了、after 却是 0 字节"这一现象倒推出根因的。
正确写法 —— 每个参与拼接的值先判空:
- string ls_ret, ls_v, ls_a, ls_b
- any la_r
- ls_a = 'a'
- ls_b = 'b'
- la_r = choice(5, ls_a, ls_b) // 索引越界,返回 NULL
- if IsNull(la_r) then
- ls_v = '<NULL>'
- else
- ls_v = String(la_r)
- end if
- ls_ret = 'r=' + ls_v
复制代码
五、版本定位:PBIDEA 是“一份 PB10 二进制,PB10 以上通用”
解析 PE 导出表(只读)实测,2026-09-20 最新版各份 PbIdea.dll:
● D:\PBAI\UPDATE\10\PbIdea.dll —— 18,019,840 字节 / 2545 个导出 / 有 fnChoice / md5 dfc61467…
● D:\PBAI\UPDATE\125\PbIdea.dll —— 18,019,840 字节 / 2545 个导出 / 有 fnChoice / md5 dfc61467…
● D:\PBAI\PbIdea.dll —— 17,893,888 字节 / 2520 个导出 / 没有
● pblit_test\PbIdea.dll —— 17,892,864 字节 / 2520 个导出 / 没有
● UPDATE\125\runtest\PbIdea.dll —— 15,122,944 字节 / 2201 个导出 / 没有
两个结论:
1、UPDATE\10 与 UPDATE\125 下的 PbIdea.dll 现在是同一份文件,md5 完全相同 —— PBIDEA 的二进制只有一份,10 和 125 两个目录只是配套的 PBL 容器格式不同(websuite.pbl 一份是 PB10 格式、一份是 PB12.5 格式,源码同源)。所以只要用到本文这些函数,PB 10 及以上行为一致。
2、版本只能靠导出表认,不能靠文件大小、也不能靠目录名:2520 导出的那份比 2545 的小,可 2201 导出最小的那份反而在“最新”目录的 runtest 子目录里。判断某个函数在不在,唯一可靠办法就是解析导出表。
顺带把“旧快照比新版少什么”也列清楚 —— 2545 相对 2520 多出的 25 个导出:
- fnChoice choice 全局函数(本文主角)
- color2string 颜色转字符串(uo_pbidea_report / uo_utils)
- reportCreatePageObj 报表:创建页对象(对象式写法入口)
- reportSetName 报表:命名
- reportGetJsonDataTemplate 报表:取 JSON 数据模板
- _reportpageCreate@8 页对象:创建
- reportpageAddText / AddLine / AddRect / AddCircle / AddChart / AddImage / AddCode
- reportpageSetName / GetPageId / Destroy
- reportpageBeginRepeat / EndRepeat
- units_mm2cm / mm2inch / mm2pt / mm2px_x / mm2px_y / mm2pb_x / mm2pb_y 毫米换算(uo_units)
复制代码
注意 uo_pbidea_report / uo_pbidea_report_page 两个对象本身源码没变,变的是 DLL 把它们的导出补齐了 —— 之前拿 2520 那份 DLL 跑对象式建页(CreatePageObj)必定失败,就是缺这 25 项。
六、典型用法
- // 1) 二选一,替代 if / else 赋值
- ls_mode = choice(ib_debug, '调试模式', '生产模式')
- // 2) 按用户选择取第 n 个候选(n 从 1 开始)
- ls_col = choice(li_col, 'id', 'name', 'dept')
- // 3) 随机抽样
- ls_pick = choice(ls_names) // 数组里随机取一个
- ls_ch = choice('abcdef0123456789') // 随机取 1 个字符
- li_roll = choice(6) + 1 // 掷骰子:1 ~ 6
复制代码
七、边界与注意事项汇总
● index 是 1 基;0 / 负数 / 越界全部 → NULL,且不报错
● maxValue ≤ 0 → NULL(choice(0)、choice(-5) 实测都是 NULL)
● maxValue = 1 → 恒为 0
● 空串 → NULL,不是空串!所以判空只能用 IsNull(),用 Len(s) = 0 判断会漏
● 候选值本身是 NULL → 原样返回 NULL
● 本文只验证了"抽得到",没有做均匀性统计检验,别把它当均匀分布使用
● 属外部函数,运行目录必须有 PbIdea.dll
● 不要用在安全场景(令牌 / 密码 / 验证码)—— 随机源性质未公开
八、验证方式
- 测试工程:choice.srf(PBIDEA 原源码)+ nvo_choice_demo(正常路径)+ nvo_choice_edge(13 条边界逐条隔离)
- 实机四步全绿:import 0 错 + rebuild --type full 0 错 + PBVM 真机运行 PASS + 源码自检 0 错 0 提醒 / 编码检查 bad = 0
- 诊断产物:diag_choice_safe.txt(正常路径全量输出)、diag_c3_*.txt(边界逐条 pre / after)
- 最新版 DLL 复测:把 run 目录的 PbIdea.dll 换成上面那份 18,019,840(md5 dfc61467…)后重跑 —— 13 条边界 + 正常路径全部 PASS,逐用例输出与换之前逐字符一致(只有随机取字符那条的字面结果不同,属预期)
- 未实测 · 仅静态核对:①PB10 运行时下的执行结果 —— 本机 pypower 工具链只带 PBORC125,--pb-version 100 走不通、--no-build-exe 报 license invalid,故 PB10 侧只做到“PB10 目标编译验证 + 同一份 DLL 字节全等的推断”,没有 PB10 虚拟机实跑;②抽样的均匀性、多会话下的随机源行为
九、附:最新版 PBIDEA(2026-09-20)顺带发现的另外几处变化
学习新版导出时对照出来的,一并记下:
1、代码编辑器的语言名改成了小写短名。官方 demo 里原来的 "JavaScript" / "javascript" 统一改成 "json","XML Document" / "xml document" 改成 "xml","C/C++ Source Code" 改成 "json","python script" 改成 "python"。自己写 SetLanguage / setlanguage 时建议直接用 json / xml / python 这些小写短名。
2、PDF 页转图片不再需要翻转。uo_tabpage_tilewidget 里原来的 dib.flip() 两处都注释掉了 —— 现在 pdf.loadPage(...) 拿到的图交给 uo_dib.open() 后直接编码即可,多调一次 flip() 反而会把图上下颠。
3、官方报表 demo 新增了“超市小票”场景:80mm 热敏纸、用 job.CreatePageObj() 对象式建页(就是上面那 25 项新导出里的那个)、每页 22 行商品、商品超一页自动分页、首页完整页头 / 续页简化页头、末页打印合计与收款找零。要跑这段必须用新版 DLL(旧快照没有 reportCreatePageObj)。
4、另外对比了两版导出目录:websuite.pbl 下 4 个 _proto_*.txt / _ucurl.txt 草稿文件被清掉了,其余 33 个文件的改动都是上面第 1 条的同义替换加空白调整;报表与 choice 的源码一个字没动。 |