马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PBIDEA:在 PowerBuilder 里调用 Python / Lua / JS 扩展能力
阅读说明
1. 适用版本:PowerBuilder 12.5 及以上,并安装 PBIDEA 运行库(PbIdea.dll)。本文对象 uo_python / uo_lua / uo_js 均来自 PBIDEA 的 websuite.pbl,API 名称已逐一对照片本源码(export_20260811)核实。
2. 支持数据库:本文不涉及数据库操作(三个互操作对象为进程内脚本引擎,无需数据库),故不罗列 DBMS。
3. 操作系统与环境要求:Windows 7+;除 PBIDEA 运行库(PbIdea.dll 等)外,还需在目标机上安装对应的脚本解释器——Python 需装 CPython(如 C:\Python39)、Lua 需装 Lua 运行库、JS(ES)引擎由 PbIdea.dll 内置。部署到客户机时这些运行库与解释器都要一并带上。
4. 难度系数:★★★★★(跨语言调用、外部运行时依赖、部署与编码坑较多,新手慎入)。
5. 其它阅读说明:本文所有调用 pbidea.dll + 外部解释器的代码段,均因依赖用户机上的 Python/Lua/JS 运行环境,无法在本机 headless 测试环境实机运行;以下示例已对照源码核实 API 名称、并经 PB12.5 ORCA 编译校验(0 错误),但运行时结果未实测、仅静态核对,请在你本机装好解释器后再跑。
一、为什么要在 PB 里跑脚本语言
PowerBuilder 擅长做企业桌面应用和数据库前端,但有些活它干起来别扭:
- 要用 numpy / pandas 做数据分析,或调用一个现成的 Python 算法库;
- 要嵌入一段 Lua 规则脚本,让业务人员不改 PB 程序就能改逻辑;
- 要复用一段成熟的 JavaScript 函数做字符串/日期处理或加解密。
把这些"胶水层"放进 PB 里直接调,比另起一个进程、用命令行互传文本要顺手得多。PBIDEA 提供了三个非可视对象,把三种脚本引擎包成了 PB 能直接 create 后调用的类:
| 对象 | 引擎 | 主要用途 | | uo_python | CPython | 调用 Python 模块/函数、直接跑一段 Python 拿 sys.stdout | | uo_lua | Lua | 跑一段 Lua 脚本,取回输出字符串 | | uo_js | 内置 ES 引擎 | 跑一段 JavaScript,可重定向输出到窗口/日志文件 |
它们的共同点是:都靠 PbIdea.dll 里的外部函数(system library "pbidea.dll")落地,调用前必须保证该运行库随程序一起发布。
二、三个对象的 API 全貌(对照源码)
下面每个方法名、参数都来自 websuite.pbl 的真实导出源码,不是凭记忆写的。
2.1 uo_python
源码 uo_python.sru 的公开原型(已逐条核对):
- // 初始化 python,指定安装路径
- function boolean InitLib(readonly string path) // → pythonInitLib
- function boolean InitLib(readonly string path, uo_json jsInfo) // → pythonInitLib(带版本信息)
- subroutine UnInitLib() // → pythonUnInitLib
- function boolean AddPath(readonly string path) // → pythonAddPath(追加库搜索路径)
- function boolean Invoke(ref uo_json returnValues, readonly string module_name, readonly string func_name, ...) // → pythonInvoke
- function boolean InvokeScript(ref uo_json returnValues, readonly string script) // → pythonInvokeScript
- function blob InvokePython(readonly string command) // → pythonInvokePython(返回 sys.stdout,默认 UTF-8)
复制代码
要点:
- 这三个对象都是 nonvisualobject,且源码里声明了同名的全局变量 global uo_python uo_python——但它只是全局变量声明,不是自动实例化,使用前必须 create。直接写 uo_python.InitLib(...) 运行必报 Null object reference。
- Invoke 的最后是 ...,表示后面跟的是传给 Python 函数的实参(按位置传),返回值通过第一个 ref uo_json returnValues 带回来。
- InvokePython 直接返回 blob,内容是 Python 的 sys.stdout,默认 UTF-8 编码;想用 GBK 可在 command 里写 python -X gbk ...。
2.2 uo_lua
源码 uo_lua.sru:
- subroutine luaCreate() // 构造时自动调用
- subroutine luaDestroy() // 析构时自动调用
- function string run(readonly string code) // → luaRun,返回脚本输出
复制代码
要点:uo_lua 的 constructor 事件里已经调了 luaCreate(),destructor 里调 luaDestroy(),所以你 create uo_lua 之后直接 run 即可,run 的返回值是脚本打印到 stdout 的内容。
2.3 uo_js
源码 uo_js.sru:
- subroutine jsCreate() // 构造时自动调用
- subroutine jsDestroy() // 析构时自动调用
- subroutine SetConsole(readonly string ConsoleType) // 输出去向:""=控制台 / "window"=编辑框窗口 / "文件名"=日志文件
- subroutine Reset()
- function boolean eval(readonly string script) // 跑字符串脚本
- function boolean eval(readonly string script, readonly string user_define)
- function boolean eval(readonly blob script) // 跑 blob(UTF-8 代码)
- function boolean eval(readonly blob script, readonly string user_define)
- function boolean evalFile(readonly string filename) // 跑一个 .js 文件
复制代码
要点:eval 返回 boolean 表示成败,脚本里 console.log 的输出默认打到控制台;想拿到结果,用 SetConsole('window') 弹出一个编辑框窗口看输出,或 SetConsole('log.txt') 把输出写进日志文件再用 PB 读回来。
三、uo_python 实战
前置:目标机已安装 CPython(示例路径 C:\Python39),且 PbIdea.dll 随程序发布。
步骤:1) 在按钮或 NVO 里 create uo_python;2) 先 InitLib 指定 Python 目录;3) 用 InvokePython 直接拿输出,或用 InvokeScript/Invoke 调函数;4) 用完 UnInitLib() 并 destroy。 - // 示例输入:本机 Python 安装目录
- string ls_pyhome
- ls_pyhome = 'C:\Python39'
- // 示例输入:要交给 Python 执行的语句(打印 1+2)
- string ls_code
- ls_code = 'print(1 + 2)'
- // 创建 python 桥接对象(uo_python 非自动实例化,必须 create)
- uo_python lpy
- lpy = create uo_python
- // 初始化:告诉 pbidea.dll 去哪找 python
- boolean lb_ok
- lb_ok = lpy.InitLib(ls_pyhome)
- // 直接执行一段脚本,返回 sys.stdout(blob,默认 UTF-8)
- blob lbl_out
- lbl_out = lpy.InvokePython(ls_code)
- // 把 UTF-8 的 blob 转成可读字符串(fromUtf8 是 PBIDEA 全局函数,不是对象方法)
- string ls_result
- ls_result = fromUtf8(lbl_out)
- // 收尾:卸载 python 运行库,销毁对象
- lpy.UnInitLib()
- destroy lpy
- MessageBox('Python 输出', ls_result)
复制代码
说明:InvokePython 是最省心的"拿结果"方式——它把 Python 的 print 输出整体作为 blob 返回,再 fromUtf8 转字符串即可。fromUtf8 是 PBIDEA 提供的全局函数,不要写成 lpy.fromUtf8(...)。
3.1 调用一个 Python 函数并取回结构化返回值
前置同上。下面演示用 InvokeScript 先把函数定义进去,再用 Invoke 调它;返回值由 pbidea 收集进 ref uo_json returnValues。 - // 示例输入:Python 安装目录
- string ls_home
- ls_home = 'C:\Python39'
- // 示例输入:要定义的 Python 函数源码
- string ls_def
- ls_def = 'def add(a, b):~n return a + b'
- // 创建并初始化 python 桥
- uo_python lpy
- lpy = create uo_python
- boolean lb_init
- lb_init = lpy.InitLib(ls_home)
- // 返回值容器(uo_json 也是 PBIDEA 对象,需 create)
- uo_json lret
- lret = create uo_json
- // 先把函数定义进 python 命名空间
- boolean lb_def
- lb_def = lpy.InvokeScript(lret, ls_def)
- // 调用 add(3, 4),3 和 4 作为位置实参传进去;结果回到 lret
- boolean lb_call
- lb_call = lpy.Invoke(lret, 'builtins', 'add', 3, 4)
- // 从返回 json 里取结果(键名遵循 PBIDEA python 桥约定,这里用 'result' 示意)
- string ls_val
- ls_val = lret.getstring('result')
- lpy.UnInitLib()
- destroy lret
- destroy lpy
- MessageBox('add(3,4) =', ls_val)
复制代码
坑提醒:Invoke 第一个参必须是 ref uo_json,且要在调用前 create,否则取不到返回值。返回 json 的具体键名以你用的 PBIDEA 版本为准,建议先用 InvokePython 打印 returnValues 看结构。
四、uo_lua 实战
前置:目标机装有 Lua 运行库(PbIdea.dll 内置 Lua 支持时可直接用);uo_lua 构造即 luaCreate,析构即 luaDestroy。 - // 示例输入:要执行的 Lua 代码(打印两数之和)
- string ls_lua
- ls_lua = 'print(1 + 2)'
- // 创建 lua 桥(constructor 已自动 luaCreate)
- uo_lua llua
- llua = create uo_lua
- // 执行脚本,返回值是脚本打到 stdout 的内容
- string ls_lua_out
- ls_lua_out = llua.run(ls_lua)
- // 用完销毁(destructor 自动 luaDestroy)
- destroy llua
- MessageBox('Lua 输出', ls_lua_out)
复制代码
uo_lua 比 uo_python 轻:只有一个 run(string) 方法,返回字符串。适合把"业务规则"做成 Lua 片段,运行时读出来 run 一下,改规则不用重编 PB。
五、uo_js 实战
前置:PbIdea.dll 自带 ES 引擎,无需额外安装;输出默认打到控制台,想留存用 SetConsole。 - // 示例输入:要执行的 JavaScript 代码
- string ls_js
- ls_js = 'var s = "hello"; console.log(s + " from js");'
- // 示例输入:输出重定向到的日志文件名
- string ls_log
- ls_log = 'js_run.log'
- // 创建 js 桥(constructor 已自动 jsCreate)
- uo_js ljs
- ljs = create uo_js
- // 把脚本输出重定向到日志文件,方便事后用 PB 读回
- ljs.SetConsole(ls_log)
- // 执行脚本(返回 boolean 表示执行是否成功)
- boolean lb_eval
- lb_eval = ljs.eval(ls_js)
- // 也可以直接跑一个 .js 文件
- // lb_eval = ljs.evalFile('d:\scripts\calc.js')
- // 用完销毁(destructor 自动 jsDestroy)
- destroy ljs
- MessageBox('JS 执行', '成功=' + string(lb_eval) + ',输出见 ' + ls_log)
复制代码
说明:eval 只返回成败,真正的计算结果走 console.log。需要把结果拿回 PB,最稳的是 SetConsole('log.txt') 写文件,再用 FileOpen/FileRead 读回来;或者 SetConsole('window') 弹个编辑框直接看。
六、跨语言数据怎么传
三个对象里,只有 uo_python 的 Invoke/InvokeScript 通过 ref uo_json 带回结构化数据,uo_lua.run 和 uo_js.eval 都只回字符串/blob。所以实战里推荐的统一做法:
- 脚本侧把结果序列化成 JSON 字符串(Python 用 json.dumps、JS 用 JSON.stringify、Lua 可用第三方库);
- PB 侧用 uo_json 的 parse/getstring/getnumber/getarray 解析;
- 反向传参:简单类型(数字、字符串)直接当 Invoke 的实参传;复杂结构先 uo_json 序列化、在脚本里 json.loads 还原。
这样 PB 与脚本之间只交换"字符串化的 JSON",互不关心对方内存布局,最稳。
七、边界情况与坑
- 必须 create:global uo_python uo_python 这类是全局变量声明,不是自动实例化。类名直调 uo_python.InitLib(...) 会 Null object reference。正确是 uo_python lpy; lpy = create uo_python。
- PbIdea.dll 要一起发布:三个对象最终都调 pbidea.dll 的外部函数,部署时该 dll(及它依赖的 Lua/Python 桥接组件)必须在程序目录下,否则一调用就崩。
- Python 路径要对:InitLib 传错路径,或目标机没装对应版本的 CPython,Invoke 会返回 false。建议在 InitLib 后判断返回值。
- 编码:InvokePython 返回 UTF-8 的 blob,用 fromUtf8 转;若 Python 侧是 GBK,command 里加 python -X gbk,或自己用 GBK 解码。
- 解释器位数匹配:PBIDEA 多为 32 位,装 32 位 CPython 更稳,避免 64 位 Python 与 32 位 pbidea.dll 桥接失败。
- UnInitLib / destroy 别漏:尤其 uo_python,跑完调 UnInitLib() 再 destroy,否则 Python 解释器资源不释放。
- 线程:这些桥接默认在主线程跑,重计算会卡 UI;要异步,把调用包进 uo_thread(见 Day10)的后台函数里。
- 返回值键名:Invoke 回写的 uo_json 键名随 PBIDEA 版本,先 InvokePython('print(returnValues)') 之类探明结构再用。
八、与替代方案对比
| 方案 | 优点 | 缺点 | 适用 | | uo_python/uo_lua/uo_js | 进程内调用、API 简单、不启子进程 | 依赖 pbidea.dll + 解释器、部署体积大 | 频繁调用、追求简单 | | OLE / COM | 标准 Windows 机制 | Python 需注册 com 服务器、配置繁琐 | 已 COM 化的组件 | | 命令行 Run + 文本互传 | 零依赖、最通用 | 启子进程慢、要解析 stdout 文件 | 偶发调用、重计算 | | 纯 PB 重写 | 无外部依赖 | 有些算法 PB 写起来费劲 | 逻辑简单、可控 |
一般规律:调用频繁、又只想"写几行脚本",用 PBIDEA 这三个对象最省事;偶尔跑一次重活,命令行 Run 更轻。
九、扩展点
- 把 Lua 规则脚本存进数据库或配置文件,运行时读出来 uo_lua.run,实现"不改 PB 也能改业务规则"。
- 用 uo_python 调 numpy/pandas 做报表计算,结果 uo_json 回传,再交给 uo_fin_chart(见 Day8)画图。
- 把 JS 片段做成前端校验/格式化函数库,PB 侧 uo_js.eval 复用,避免前后端两套逻辑。
- 配合 uo_thread(Day10)做后台脚本执行,长任务不卡界面。
到这里,30 天 PB / PBIDEA 技术连载告一段落。从 DataWindow 缓冲区、PowerScript 语言、嵌入式 SQL,到 PBIDEA 的 JSON/HTTP/加密/线程/Excel/PDF/图表/WebView2,再到今天的跨语言互操作——把"一件事讲透"的写法希望能帮你真正用起来。后续文章会继续按这个标准更新,欢迎在论坛回帖交流你踩到的坑。 |