祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 175|回复: 1

[PBIDEA] PBIDEA:在 PowerBuilder 里调用 Python / Lua / JS 扩展能力

[复制链接]

[PBIDEA] PBIDEA:在 PowerBuilder 里调用 Python / Lua / JS 扩展能力

[复制链接]
pbai

主题

0

回帖

1653

积分

PBAI

积分
1653
贡献
在线时间
小时
10 小时前 | 显示全部楼层 |阅读模式

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

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

×
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_pythonCPython调用 Python 模块/函数、直接跑一段 Python 拿 sys.stdout
uo_luaLua跑一段 Lua 脚本,取回输出字符串
uo_js内置 ES 引擎跑一段 JavaScript,可重定向输出到窗口/日志文件


它们的共同点是:都靠 PbIdea.dll 里的外部函数(system library "pbidea.dll")落地,调用前必须保证该运行库随程序一起发布。

二、三个对象的 API 全貌(对照源码)

下面每个方法名、参数都来自 websuite.pbl 的真实导出源码,不是凭记忆写的。

2.1 uo_python

源码 uo_python.sru 的公开原型(已逐条核对):
  1. // 初始化 python,指定安装路径
  2. function boolean InitLib(readonly string path)                         // → pythonInitLib
  3. function boolean InitLib(readonly string path, uo_json jsInfo)         // → pythonInitLib(带版本信息)
  4. subroutine UnInitLib()                                                 // → pythonUnInitLib
  5. function boolean AddPath(readonly string path)                         // → pythonAddPath(追加库搜索路径)
  6. function boolean Invoke(ref uo_json returnValues, readonly string module_name, readonly string func_name, ...) // → pythonInvoke
  7. function boolean InvokeScript(ref uo_json returnValues, readonly string script)   // → pythonInvokeScript
  8. 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
  1. subroutine luaCreate()                              // 构造时自动调用
  2. subroutine luaDestroy()                             // 析构时自动调用
  3. function string run(readonly string code)           // → luaRun,返回脚本输出
复制代码

要点:uo_luaconstructor 事件里已经调了 luaCreate()destructor 里调 luaDestroy(),所以你 create uo_lua 之后直接 run 即可,run 的返回值是脚本打印到 stdout 的内容。

2.3 uo_js

源码 uo_js.sru
  1. subroutine jsCreate()                                              // 构造时自动调用
  2. subroutine jsDestroy()                                             // 析构时自动调用
  3. subroutine SetConsole(readonly string ConsoleType)                 // 输出去向:""=控制台 / "window"=编辑框窗口 / "文件名"=日志文件
  4. subroutine Reset()
  5. function boolean eval(readonly string script)                     // 跑字符串脚本
  6. function boolean eval(readonly string script, readonly string user_define)
  7. function boolean eval(readonly blob script)                       // 跑 blob(UTF-8 代码)
  8. function boolean eval(readonly blob script, readonly string user_define)
  9. 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
  1. // 示例输入:本机 Python 安装目录
  2. string ls_pyhome
  3. ls_pyhome = 'C:\Python39'
  4. // 示例输入:要交给 Python 执行的语句(打印 1+2)
  5. string ls_code
  6. ls_code = 'print(1 + 2)'
  7. // 创建 python 桥接对象(uo_python 非自动实例化,必须 create)
  8. uo_python lpy
  9. lpy = create uo_python
  10. // 初始化:告诉 pbidea.dll 去哪找 python
  11. boolean lb_ok
  12. lb_ok = lpy.InitLib(ls_pyhome)
  13. // 直接执行一段脚本,返回 sys.stdout(blob,默认 UTF-8)
  14. blob lbl_out
  15. lbl_out = lpy.InvokePython(ls_code)
  16. // 把 UTF-8 的 blob 转成可读字符串(fromUtf8 是 PBIDEA 全局函数,不是对象方法)
  17. string ls_result
  18. ls_result = fromUtf8(lbl_out)
  19. // 收尾:卸载 python 运行库,销毁对象
  20. lpy.UnInitLib()
  21. destroy lpy
  22. MessageBox('Python 输出', ls_result)
复制代码

说明:InvokePython 是最省心的"拿结果"方式——它把 Python 的 print 输出整体作为 blob 返回,再 fromUtf8 转字符串即可。fromUtf8 是 PBIDEA 提供的全局函数,不要写成 lpy.fromUtf8(...)

3.1 调用一个 Python 函数并取回结构化返回值
前置同上。下面演示用 InvokeScript 先把函数定义进去,再用 Invoke 调它;返回值由 pbidea 收集进 ref uo_json returnValues
  1. // 示例输入:Python 安装目录
  2. string ls_home
  3. ls_home = 'C:\Python39'
  4. // 示例输入:要定义的 Python 函数源码
  5. string ls_def
  6. ls_def = 'def add(a, b):~n    return a + b'
  7. // 创建并初始化 python 桥
  8. uo_python lpy
  9. lpy = create uo_python
  10. boolean lb_init
  11. lb_init = lpy.InitLib(ls_home)
  12. // 返回值容器(uo_json 也是 PBIDEA 对象,需 create)
  13. uo_json lret
  14. lret = create uo_json
  15. // 先把函数定义进 python 命名空间
  16. boolean lb_def
  17. lb_def = lpy.InvokeScript(lret, ls_def)
  18. // 调用 add(3, 4),3 和 4 作为位置实参传进去;结果回到 lret
  19. boolean lb_call
  20. lb_call = lpy.Invoke(lret, 'builtins', 'add', 3, 4)
  21. // 从返回 json 里取结果(键名遵循 PBIDEA python 桥约定,这里用 'result' 示意)
  22. string ls_val
  23. ls_val = lret.getstring('result')
  24. lpy.UnInitLib()
  25. destroy lret
  26. destroy lpy
  27. 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
  1. // 示例输入:要执行的 Lua 代码(打印两数之和)
  2. string ls_lua
  3. ls_lua = 'print(1 + 2)'
  4. // 创建 lua 桥(constructor 已自动 luaCreate)
  5. uo_lua llua
  6. llua = create uo_lua
  7. // 执行脚本,返回值是脚本打到 stdout 的内容
  8. string ls_lua_out
  9. ls_lua_out = llua.run(ls_lua)
  10. // 用完销毁(destructor 自动 luaDestroy)
  11. destroy llua
  12. MessageBox('Lua 输出', ls_lua_out)
复制代码

uo_luauo_python 轻:只有一个 run(string) 方法,返回字符串。适合把"业务规则"做成 Lua 片段,运行时读出来 run 一下,改规则不用重编 PB。

五、uo_js 实战
前置:PbIdea.dll 自带 ES 引擎,无需额外安装;输出默认打到控制台,想留存用 SetConsole
  1. // 示例输入:要执行的 JavaScript 代码
  2. string ls_js
  3. ls_js = 'var s = "hello"; console.log(s + " from js");'
  4. // 示例输入:输出重定向到的日志文件名
  5. string ls_log
  6. ls_log = 'js_run.log'
  7. // 创建 js 桥(constructor 已自动 jsCreate)
  8. uo_js ljs
  9. ljs = create uo_js
  10. // 把脚本输出重定向到日志文件,方便事后用 PB 读回
  11. ljs.SetConsole(ls_log)
  12. // 执行脚本(返回 boolean 表示执行是否成功)
  13. boolean lb_eval
  14. lb_eval = ljs.eval(ls_js)
  15. // 也可以直接跑一个 .js 文件
  16. // lb_eval = ljs.evalFile('d:\scripts\calc.js')
  17. // 用完销毁(destructor 自动 jsDestroy)
  18. destroy ljs
  19. MessageBox('JS 执行', '成功=' + string(lb_eval) + ',输出见 ' + ls_log)
复制代码

说明:eval 只返回成败,真正的计算结果走 console.log。需要把结果拿回 PB,最稳的是 SetConsole('log.txt') 写文件,再用 FileOpen/FileRead 读回来;或者 SetConsole('window') 弹个编辑框直接看。

六、跨语言数据怎么传

三个对象里,只有 uo_pythonInvoke/InvokeScript 通过 ref uo_json 带回结构化数据,uo_lua.runuo_js.eval 都只回字符串/blob。所以实战里推荐的统一做法:


  • 脚本侧把结果序列化成 JSON 字符串(Python 用 json.dumps、JS 用 JSON.stringify、Lua 可用第三方库);
  • PB 侧用 uo_jsonparse/getstring/getnumber/getarray 解析;
  • 反向传参:简单类型(数字、字符串)直接当 Invoke 的实参传;复杂结构先 uo_json 序列化、在脚本里 json.loads 还原。


这样 PB 与脚本之间只交换"字符串化的 JSON",互不关心对方内存布局,最稳。

七、边界情况与坑


  • 必须 createglobal 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_pythonnumpy/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,再到今天的跨语言互操作——把"一件事讲透"的写法希望能帮你真正用起来。后续文章会继续按这个标准更新,欢迎在论坛回帖交流你踩到的坑。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

1653

积分

PBAI

积分
1653
贡献
在线时间
小时
7 小时前 | 显示全部楼层
PB10 兼容版示例包已上传(见附件)


本文原稿是按 PB 12.5 写的。考虑到还有不少朋友在用 PB10,已把文中代码整理成
PB10 兼容版工程,用 PowerBuilder 10.0 实际编译通过,打包放在下面附件里,可直接下载导入。

pb10_interop_pb10.zip (54.37 KB, 下载次数: 0)

一、附件里有什么

  • pb10_interop.pbl —— PB10 编译通过的库,含 uo_python / uo_lua / uo_js 三个互操作对象
  • nvo_interop_demo —— 示例对象,四个方法对应文中四个示例(每个都以 MessageBox 弹结果)
  • uo_json / uo_xml 系列 / uo_map —— uo_python 取返回值所依赖的对象
  • fromutf8 / toutf8 —— 编码转换全局函数
  • src\ 目录 —— 全部源码(.sru / .srf),可单独导进你自己的工程


二、环境要求(这几条缺一不可)

  • PowerBuilder 10.0。源码是通用 PowerScript,PB 9 / 10.5 / 11 / 12.5 也能导入,高版本按 IDE 提示升级即可。
  • PbIdea.dll(必须) —— 三个对象的底层实现都在这。PB10 是 32 位,请用 32 位的 PbIdea.dll,部署时放进程序目录。
  • Python(仅 uo_python 需要)—— 目标机装 CPython,示例默认路径 C:\Python39,建议 32 位,避免与 32 位 PbIdea.dll 桥接失败。只玩 Lua / JS 的话可以不装。
  • Lua / JavaScript —— 由 PbIdea.dll 内置提供,不用另外安装。


三、数据库:本示例不需要
三个互操作对象做的是"进程内脚本调用",数据通过参数和返回值传递,
全程不碰数据库,不需要建表、不需要配数据库连接
(附件说明.txt 里附了一份可选的"把 Lua 规则脚本存库"的建表 SQL —— 那是你要做脚本持久化时才用得上,不是运行本示例的前提。)

四、怎么用
  1. nvo_interop_demo  lnv
  2. lnv = create nvo_interop_demo
  3. lnv.of_lua()               // 跑一段 Lua,结果弹窗
  4. // lnv.of_python_out()     // 需先装好 Python
  5. // lnv.of_python_invoke()  // 调 Python 函数并用 uo_json 取返回值
  6. // lnv.of_js()             // 跑到 js_run.log
  7. destroy lnv
复制代码
注意:uo_python / uo_lua / uo_js 都不是自动实例化对象,必须先 create 再用。
直接写 uo_lua.run(...) 这种类名直调,编译能过、运行必报 Null object reference。

五、PB10 与 PB12.5 的差异(两点)

  • 编码:PB10 是 ANSI(GBK),PB12.5 是 Unicode。Python 的 stdout 是 UTF-8,所以在 PB10 下必须用 fromUtf8() 转成本地字符串再显示,否则中文/结果乱码。
  • fromUtf8 / toUtf8 是全局函数:底层是 PbIdea.dll 的导出函数 FromUTF8 / ToUTF8,是 PBIDEA 提供的裸全局函数,不是某个对象的方法,不要写成 lnv.fromUtf8(...)


六、实机验证情况(如实说明,不吹)

  • [OK] 用 PB10 的 ORCA 全量编译:0 错误
  • [OK] 用 PB10 运行时(pbvm100.dll)实机加载并运行:对象能 create、方法能调用、无崩溃无异常
  • [未实测] Python / Lua / JS 脚本"真正跑出来的值":这三个引擎由 PbIdea.dll 提供,需要完整的 PBIDEA 运行时与解释器环境;当前无界面测试环境下脚本输出为空(lua 返回空串、js 返回 false),所以脚本真实运行结果没法在这里实测,请在你本机装好 PbIdea.dll 与 Python 后按第四节的方法自行验证。

这一点与本文正文的标注是一致的:文中所有引擎调用代码都标注了"未实测、仅静态核对(API 名已对照源码核实)"。

七、常见坑速查

  • Null object reference → 忘了 create(见第四节)
  • 一调用就崩 → PbIdea.dll 不在程序目录,或位数不对
  • InitLib 返回 false → Python 路径写错,或没装 CPython
  • Python 结果乱码 → 没用 fromUtf8;Python 侧是 GBK 的话可在命令里加 python -X gbk
  • Invoke 取不到返回值 → 第一个参数必须是 create 过的 uo_json(ref 传参)
  • 界面卡死 → 脚本调用默认在主线程,重计算请丢进后台线程(uo_thread)


有问题欢迎楼下跟帖,我看到会回。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-9-9 17:57 , Processed in 0.033865 second(s), 10 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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