马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PBIDEA:用 uo_sysenv 读写环境变量与获取系统信息全解(SetEnvVar 三处不可见的注册表取证与 append 崩溃实录,PB10 基准 · PB12.5 实测)
阅读说明
1. 适用版本:一律以顶部【版本声明】为准 —— PBIDEA 以维护方核验的最新版本为基准,PB 侧写法 PB 8 及以上通用。
2. 支持数据库:本文不涉及数据库。
3. 操作系统与环境要求:PBIDEA 本体支持 Windows XP 及以上;需部署 PBIDEA 运行库(PbIdea.dll,websuite.pbl 内含 uo_sysenv);编译需挂对应大版本的 websuite.pbl。本文的实机验证环境为 Windows 10 + PB 10 与 PB 12.5。
4. 难度系数:★★☆☆☆(读方向的 API 都直白,坑集中在写方向——SetEnvVar 的「假成功」与 append 重载的崩溃,按本文清单规避即可)。
5. 其它阅读说明:本文所有结论都来自实机探测:PB10 与 PB12.5 双轨各 14 组用例全部 PASS,外加一次注册表取证实验与一次崩溃复现实验。凡是没有实测到的能力,文中明确标注「未实测」,不猜。
程序要拿 PATH、要判断自己是跑在 64 位系统上、要拿操作系统版本、要拿 DPI 缩放比例——这些「问系统要答案」的小事,PowerBuilder 原生只有一套零散的 RegistryGet 和外部函数声明。PBIDEA 的 uo_sysenv 把它们收拢成一个非可视对象:15 个方法,覆盖环境变量读写、路径展开、系统信息与 DPI。
实测下来,读方向(GetEnvStrings / GetEnvVar / GetPath / OSVersion / IsWin64 / zoom)全部可用且两版行为一致;写方向(SetEnvVar / RemoveEnvVar)则是本文最重要的发现:返回 true,但环境变量哪里都没变。我们用注册表取证把这个结论钉死了。
【版本声明】
- 本文以 PBIDEA 当前最新版本为准(版本源由维护方统一核验),并对照随版发布的源码整理。
- 核验日期:2026-11-02;当次核验的 PBIDEA 核心运行库:18,162,688 字节 / 2572 个导出,md5 ca2cd120da5b…
- 版本识别口径:逐项解析 PE 导出表(文件体积、目录名都不能作为判据)。
- 适用平台:Windows XP 及以上(XP / 7 / 10 / 11 均可);PowerBuilder 8 及以上(PB 8/9/10/10.5/11.x/12.x 及以上通用)。本文的实机验证环境为 Windows 10 + PB 10 与 PB 12.5。
- PBIDEA 版本会持续更新,本文只对上述核验时点的最新版本负责;后续若行为有变,以新版为准
一、API 全貌
uo_sysenv 从 nonvisualobject 继承(非 autoinstantiate,用前要 create)。全部方法按功能分四组:
| 分组 | 方法 | 说明 | | 枚举与读取 | GetEnvStrings / GetEnvVar(单值) / GetEnvVar(数组) | 枚举进程全部环境变量;读单个值;读并以分号拆成数组 | | 写入与删除 | SetEnvVar(2 参) / SetEnvVar(3 参追加) / RemoveEnvVar | 实测均不生效或崩溃,见第六、七节 | | 路径与工具 | Split / GetPath / GetAppPath / AddAppPathToEnv | 通用分割;实路径转换;应用目录;把应用目录加进 PATH(可变参数,未实测) | | 系统信息 | IsWin64 / OSVersion(数值) / OSVersion(JSON) / zoom / scale | 64 位判断;系统版本两种形态;DPI 缩放 |
二、最小可运行示例:读 PATH 并拆成数组
- // 读 PATH:单值形态与数组形态(需挂 websuite.pbl,运行目录有 PbIdea.dll)
- uo_sysenv e
- string ls_path
- string arr[]
- long ll_cnt, i
- e = create uo_sysenv
- // 形态一:整串读出
- if e.GetEnvVar('PATH', ls_path) then
- MessageBox('PATH 长度', String(Len(ls_path))) // 实测 1943(本机)
- end if
- // 形态二:自动按分号拆成数组
- if e.GetEnvVar('PATH', arr) then
- ll_cnt = UpperBound(arr) // 实测 34 段,无空段
- for i = 1 to ll_cnt
- // 逐段处理,arr[i] 即每个路径目录
- next
- end if
- destroy e
复制代码
实测读数:单值形态 ok=true, len=1943;数组形态 ok=true, cnt=34, empty=0。同一个方法名两个重载,数组版按分号拆分,正好对上 PATH 的格式。
三、GetEnvStrings:一次拿全进程环境
GetEnvStrings 把当前进程的环境块整个枚举出来,每行一条 KEY=VALUE:
- uo_sysenv e
- string arr[]
- long ll_cnt, i, ll_path
- e = create uo_sysenv
- ll_cnt = e.GetEnvStrings(arr)
- // 统计含 PATH 的行数,检查首行格式
- ll_path = 0
- for i = 1 to ll_cnt
- if Pos(Upper(arr[i]), 'PATH=') = 1 then
- ll_path = ll_path + 1
- end if
- next
- MessageBox('环境块', '共 ' + String(ll_cnt) + ' 条, PATH 行 ' + String(ll_path))
- destroy e
复制代码
实测读数:cnt=191, hasEq(首行含等号)=true, pathLines=1。要点两条:
- 条数随进程而变(本机 191 条)。同一个值在不同进程里两次枚举,首条内容都不一样——环境块的排列顺序不保证稳定,代码里不要依赖下标位置,按 KEY 前缀找。
- 它枚举的是进程环境块,不是注册表。这个区别正是第六节写方向结论的根源。
四、GetEnvVar:读不到返回 false,大小写不敏感
三个实测行为:
不存在的键:返回 false,且输出变量被清成空串。实测传入哨兵值 'SENTINEL' 后读不存在的键,返回 false、变量变空——不要指望用它保留原值。
大小写不敏感:GetEnvVar('PATH') 与 GetEnvVar('path') 都返回 true,读到的长度一致(本机两次都是 1943)。Windows 环境变量名本就不分大小写,这个 API 跟着系统语义走。
多值重载:对非分号型变量(如 SystemRoot 这类单值变量)数组版也能用,只是只拆出一段;对 PATH 这类天然分号分隔的变量,就是第二节的 34 段。
五、GetPath:把相对路径和 %变量% 换成实路径
GetPath 做「实路径转换」,实测它同时干两件事:
- uo_sysenv e
- string ls_win, ls_rel
- e = create uo_sysenv
- ls_win = e.GetPath('%SystemRoot%') // 实测 10 字符、含盘符冒号、% 已消失 => Windows 安装目录
- ls_rel = e.GetPath('foo\bar') // 实测 51 字符 => 应用目录 + foo\bar 的绝对路径
- MessageBox('GetPath', String(Len(ls_win)) + ' / ' + String(Len(ls_rel)))
- destroy e
复制代码
- 展开 %环境变量%:%SystemRoot% 进去,Windows 安装目录 出来(10 字符、含冒号、百分号消失);
- 相对路径补全成绝对路径:foo\bar 进去,出来是「当前应用目录 + foo\bar」的完整路径(实测 51 字符);
- 空串进出都是空串(实测 len=0),不报错。
写 INI 路径、日志目录这类配置时,这个函数可以替代手工拼应用目录。
六、SetEnvVar / RemoveEnvVar:返回 true,但哪里都没变(本文核心发现)
写方向四个实测事实,全部两版一致:
事实一:写完立刻读,读不到。 SetEnvVar('PBAI_T85A', 'V1') 返回 true,紧跟着 GetEnvVar('PBAI_T85A') 返回 false、值为空。覆盖写(同键再写 'V2')也一样:set=true、get=false。
事实二:新进程也读不到。 在一个探针进程里写入 PBAI_T85P='P85'(返回 true),随后另一个全新进程里 GetEnvVar 同样返回 false。也就是说不是「写进去了要下个进程才可见」——下个进程也没有。
事实三:注册表里也没有。 我们做了专门的取证实验:探针进程调用 SetEnvVar('PBAI_REGTEST', 'R1') 返回 true 后,用外部工具直接查注册表两处标准落点——
| 检查点 | 写入前 | SetEnvVar 返回 true 之后 | | 当前进程环境块(GetEnvVar) | 不存在 | 读不到 | | 新进程环境块(跨进程 GetEnvVar) | 不存在 | 读不到 | | 注册表 HKCU\Environment | 不存在 | 不存在 | | 注册表系统环境(Session Manager\Environment) | 不存在 | 不存在 |
四处全部不可见。RemoveEnvVar 同样:无返回值、重复删除不崩,但本来就删不到东西。
结论:在当前核验版本上,SetEnvVar 与 RemoveEnvVar 等效于「空操作」——调用不报错、返回 true,但对进程、子进程、注册表三层都没有可观察效果。 需要改环境变量时不要依赖它们:改当前进程用 PB 原生没有但 Win32 有的 SetEnvironmentValue(自己声明外部函数),改持久环境走注册表 + 广播,或者干脆用第六节的替代方案。
事实四(唯一好消息):process 级读方向完全可靠。 上面所有「读不到」都是对刚写入的自定义键;对系统已有的 PATH / SystemRoot 等键,读取一路全绿。
七、append 重载:调用即崩(两版复现)
SetEnvVar 的三参数追加重载(appendToExists=true,注释说以分号追加到现有变量)实测崩溃:
- // 崩溃复现序列(请勿在生产代码中出现)
- e.SetEnvVar('PBAI_AP85', 'A') // 两参版本:返回 true(不生效)
- e.SetEnvVar('PBAI_AP85', 'B', true) // 三参追加版本:进程直接崩掉
复制代码
我们在 PB10 与 PB12.5 两条轨上各复现一次:PB 测试进程在该行直接异常退出,诊断文件未能写出(崩溃退出码 0xE06D7363,C++ 异常)。这是版本级行为缺陷,与调用方式无关——先建后追加、对不存在的键追加,都一样崩。规避方式只有一个:不用三参重载。AddAppPathToEnv(把应用目录加进 PATH 的可变参数方法)同属写方向,按第六节结论推定同样不可用,本文未实测、不推荐。
八、系统信息:IsWin64 / OSVersion 两种形态
- uo_sysenv e
- uo_json j
- ulong lul_os
- e = create uo_sysenv
- j = create uo_json
- lul_os = e.OSVersion() // 实测 2560 = 0x0A00(主版本 10、次版本 0 打包成 ulong)
- e.OSVersion(j) // JSON 明细形态:16 个键一次全给
- MessageBox('OS', String(lul_os) + '~n' + j.ToString())
- destroy j
- destroy e
复制代码
数值形态返回 2560(十六进制 0x0A00),高位是主版本、低位区域是次版本,本机 Windows 10 读到 10/0。JSON 形态实测输出 16 个键(本机读数,长度 287 字符):
| 键 | 本机读数 | 说明 | | dwMajorVersion / dwMinorVersion | 10 / 0 | 系统主次版本 | | version | "0a00" | 版本的十六进制串 | | workstation(注意键名带尾空格) | true | 工作站系统 | | szVersion | ""(空) | 补丁包描述,未装 SP 时为空 | | IsXP / IsVista / IsWin7 / IsWin10 | false | 逐代判断 | | IsVistaOrLater / IsWin8OrLater | true | 「或更高」判断 | | IsWin10OrLater | false | 见下方怪点 | | IsElevated / IsUACEnabled | true / false | 管理员权限与 UAC 状态 | | IsWin64 | true | 与 IsWin64() 一致 |
两个要当心的怪点(本机实读):
- 键名 "workstation " 带一个尾空格。用 uo_json.Get 取这个键时要写全带空格的键名,想当然写 workstation 会取不到。
- IsWin10 与 IsWin10OrLater 都是 false,但 dwMajorVersion 明明是 10。这套逐代开关的判断口径偏旧(疑似老版本判定表没覆盖 Win10/11),系统判断不要信这些布尔键,以 dwMajorVersion 为准。
IsWin64() 单独调用返回 true(本机 64 位系统)。
九、zoom / scale:DPI 缩放比例
zoom() 与 scale() 返回 double(注释都写「返回当前系统环境的缩放比例」)。本机 100% DPI 环境实测 zoom=1, scale=1,两者相等。非 100% DPI 的机器上两者的分工与取值差异未实测(手头没有高 DPI 测试机),此处如实标注。要做高 DPI 适配的读者请自行在目标机器上取值验证后再用。
十、完整主线示例:一条「环境自报家门」的汇总
把读方向的能力串成一个系统信息汇总(写方向按第六节结论只用「设了立刻读不到」来演示行为):
- // 环境自报家门:应用目录长度、环境块规模、系统版本、DPI(实测读数见注释)
- uo_sysenv e
- uo_json j
- string ls_app, ls_v
- string arr[]
- long ll_cnt, ll_len
- boolean lb_set, lb_g, lb_64
- ulong lul_os
- double ldb_z
- e = create uo_sysenv
- j = create uo_json
- ls_app = e.GetAppPath() // 应用目录(本机 102 字符,值随部署位置变化)
- ll_len = Len(ls_app)
- lb_set = e.SetEnvVar('PBAI_ART', 'HELLO85') // 实测 true
- lb_g = e.GetEnvVar('PBAI_ART', ls_v) // 实测 false:写完立刻读不到(第六节)
- e.RemoveEnvVar('PBAI_ART') // 清理,无副作用
- ll_cnt = e.GetEnvStrings(arr) // 实测 191 条
- lul_os = e.OSVersion() // 实测 2560
- e.OSVersion(j) // 实测 JSON 287 字符 16 键
- lb_64 = e.IsWin64() // 实测 true
- ldb_z = e.zoom() // 实测 1.0
- MessageBox('环境自报家门', 'appLen=' + String(ll_len) + ', set=' + String(lb_set) + &
- ', inProcGet=' + String(lb_g) + ', envCnt=' + String(ll_cnt) + &
- ', os=' + String(lul_os) + ', is64=' + String(lb_64) + ', zoom=' + String(ldb_z))
- destroy j
- destroy e
复制代码
实测读数(两版一致):appLen=102, set=true, inProcGet=false, envCnt=191, os=2560, jLen=287, is64=true, zoom=1。
十一、坑清单(发布前逐条实测核对)
- uo_sysenv 非 autoinstantiate,用前必须 create,用完 destroy。
- SetEnvVar 返回 true 但四处不可见(进程 / 子进程 / HKCU / 系统):当前版本等效空操作,不要用它改环境。
- 三参 append 重载调用即崩(0xE06D7363,两版复现):唯一规避是不用。
- RemoveEnvVar 无返回值,删不存在键不崩,但也删不到任何东西。
- GetEnvVar 读不存在的键返回 false,输出变量被清空,不保留原值。
- GetEnvVar 大小写不敏感('PATH' 与 'path' 同读)。
- GetEnvStrings 枚举顺序不稳定,首条内容两次运行都不同,按 KEY 前缀找、别按下标。
- GetEnvStrings 返回条数随进程环境变化(本机 191),是「进程环境块」不是注册表全量。
- 数组重载按分号拆分,PATH 实测 34 段无空段;'A;;B' 这类连续分隔符拆出 2 段、空段被跳过。
- GetPath 同时做 %变量% 展开与相对路径补全;空串进出都是空串。
- OSVersion 的 JSON 键 "workstation " 带尾空格,Get 时要写全。
- JSON 里 IsWin10 / IsWin10OrLater 与 dwMajorVersion=10 矛盾:判断系统版本以 dwMajorVersion 为准,别信逐代布尔键。
- zoom / scale 仅在 100% DPI 实测过(均为 1),非 100% DPI 行为未实测。
- AddAppPathToEnv 是可变参数声明,本文未实测;按写方向结论推定不可用。
- GetAppPath 返回部署目录(不带尾部反斜杠),长度随部署位置变化,属环境读数。
十二、PB12.5 差异
本文全部 14 组用例在 PB10 与 PB12.5 双轨编译 0 错、运行 PASS,所有行为读数逐字一致:枚举与读取、GetPath 展开、SetEnvVar 假成功、append 崩溃、OSVersion 数值与 JSON、IsWin64、zoom,两版零差异。个别读数随进程环境浮动:PATH 长度 1942/1943、GetAppPath 长度 102/103、GetEnvStrings 首条内容不同——这些是进程环境的自然差异,不是行为差异。结论:uo_sysenv 在两版上行为一致。
十三、选型:什么时候用 uo_sysenv
- 读:拿 PATH / 任意环境变量(单值或分号数组)、枚举进程环境、展开 %变量%、拼绝对路径、判断 64 位、取系统版本明细、取 DPI——uo_sysenv 一站式,读方向全部可靠。
- 写:当前版本没有任何可用入口(SetEnvVar 空操作、append 崩溃、AddAppPathToEnv 未实测推定不可用)。改环境请走 Win32 API 声明或注册表方案,别在这三个方法上花时间。
- 判断系统代际:用 dwMajorVersion 数值自己比对,JSON 里的逐代布尔键口径偏旧。
- 与 uo_config(第 9 类)的分工:配置读写用 uo_config(INI),它落盘可靠;uo_sysenv 只负责「问系统」,不负责「改系统」。
示例包内有可直接照抄的完整探针对象:14 组用例的源码、pbtest 与一份使用说明,PB10 / PB12.5 两个大版本均可直接导入运行。
(完) |