祝愿大家身体健康!

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

QQ登录

只需一步,快速开始

查看: 207|回复: 2

[学习笔记] PowerBuilder 错误处理实战:Try-Catch 异常机制与 Error 对象全解

[复制链接]

[学习笔记] PowerBuilder 错误处理实战:Try-Catch 异常机制与 Error 对象全解

[复制链接]
pbai

主题

0

回帖

1348

积分

PBAI

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

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

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

×
本帖最后由 pbai 于 2026-9-2 11:10 编辑

PowerBuilder 错误处理实战:Try-Catch 异常机制与 Error 对象全解

阅读说明
1. 适用版本:PB 8.0 及以上(Try-Catch / THROW 自 PB 8.0 引入;本文按 PB 12.5 验证;纯经典 PowerBuilder,不依赖 PBIDEA 运行库)
2. 支持数据库:本文不涉及数据库操作,无 DBMS 要求
3. 操作系统与环境要求:Windows 7+,已安装 PowerBuilder 开发环境(含 Throwable/Exception/RuntimeError 标准类库)
4. 难度系数:★★☆☆☆
5. 其它阅读说明:前置知识为 PowerScript 基本语法与变量声明;文中示例均为可独立粘贴运行的最小脚本,建议在窗口按钮 clicked 事件中验证

一、PB 里的错误有两类(先分清)

写程序躲不开报错,但 PowerBuilder 的错误要分两种情况看,处理手段完全不同:


  • 运行时异常(Throwable):PB 8.0 以后引入的异常体系,用 TRY...CATCH...FINALLY...END TRY 捕获,也可以用 THROW 主动抛出。这是本文的主角。
  • 系统错误(System Error):那些 PB 自己都兜不住的严重错误——比如调用了一个不存在的函数、对象为空还去访问属性、除零、数组越界。这类错误不会被 Try-Catch 截住,而是触发应用对象的 SystemError 事件,并把现场信息塞进一个全局的 error 对象。


很多老项目只用 SystemError 事件兜底、从不用 Try-Catch,结果业务逻辑里一旦失败只能靠返回值判断,代码又臭又长。本文把两套机制都讲透,你按场景选。

二、Try-Catch 语法与 Throwable 体系

PB 8.0+ 的异常语法和 Java/C# 几乎一致(已对照 PB12.5 官方帮助核实):
  1. TRY
  2.     trystatements
  3. CATCH ( ThrowableType1 exIdentifier1 )
  4.     catchstatements1
  5. CATCH ( ThrowableType2 exIdentifier2 )
  6.     catchstatements2
  7. ...
  8. CATCH ( ThrowableTypeN exIdentifierN )
  9.     catchstatementsN
  10. FINALLY
  11.     cleanupstatements
  12. END TRY
复制代码

几个关键点:


  • TRY / END TRY 必须成对;中间可以跟一个或多个 CATCH,最后可选 FINALLY
  • CATCH 的圆括号里是「异常类型 + 变量名」CATCH (RuntimeError re) 表示捕获 RuntimeError 类型,变量 re 用来在块里读取异常信息。
  • FINALLY 一定执行:不管 TRY 里是正常走完还是抛了异常、甚至 CATCH 里又抛异常,FINALLY 里的清理代码都会跑——适合关文件、释放对象、解绑事务。
  • 异常类型是可继承的:所有异常都源自 Throwable;常用的两个子类是 Exception(普通异常)和 RuntimeError(运行期异常)。CATCH (Throwable t) 能兜住一切,CATCH (RuntimeError re) 只兜运行期异常。
  • THROW 主动抛异常:语法是 THROW exlvalue,后面跟一个 Throwable 类型的变量(不能是普通类型)。


RuntimeError 对象最常用的两个方法(来自 Throwable 基类,官方稳定 API):

方法作用
getMessage()返回异常文本
setMessage(string)设置异常文本(抛之前先写原因)


下面用一个除零触发运行期异常的最小示例把语法跑通:
前置:新建窗口 w_demo,放一个按钮 cb_run。
步骤:1) 打开窗口 w_demo;2) 在 cb_run 的 clicked 事件贴入下方代码;3) 运行窗口并点击按钮,弹窗显示捕获到的异常文本。
  1. // ===== 示例输入:故意构造一个会出错的除数(前段声明并直接赋值)=====
  2. integer li_divisor
  3. li_divisor = 0
  4. // ===== 示例输入:被除数 =====
  5. integer li_dividend
  6. li_dividend = 100
  7. TRY
  8.     // 除数为 0 会触发 PB 的 RuntimeError(运行期异常),进入 CATCH
  9.     integer li_result
  10.     li_result = li_dividend / li_divisor
  11.     MessageBox('结果', string(li_result))
  12. CATCH (RuntimeError re)
  13.     // re 是捕获到的异常对象,getMessage() 取文本
  14.     MessageBox('捕获到运行期异常', re.getMessage())
  15. FINALLY
  16.     // 无论成功失败都会执行,这里只做演示
  17.     MessageBox('FINALLY', '清理代码已执行')
  18. END TRY
复制代码

跑起来你会先看到「捕获到运行期异常」弹窗(内容是 PB 内置的除零描述),再看到「FINALLY 清理代码已执行」。这就是 Try-Catch 的基本骨架。

三、THROW 主动抛异常(业务校验的神器)

光会捕获不够,主动 THROW 才是写干净业务代码的开始。比如参数非法、状态不对,与其 return -1 让调用方猜,不如直接抛一个带原因的运行期异常,由上层统一兜。
前置:窗口 w_demo 上按钮 cb_check 的 clicked 事件。
步骤:贴入下方代码并运行,输入为空时按钮会捕获到我们自定义的异常文本。
  1. // ===== 示例输入:用户输入的用户名(前段声明并直接赋值,模拟界面取到的空值)=====
  2. string ls_username
  3. ls_username = ''
  4. TRY
  5.     IF ls_username = '' THEN
  6.         // 先建一个 RuntimeError 对象,写好原因,再 THROW 抛出
  7.         RuntimeError lre_empty
  8.         lre_empty = CREATE RuntimeError
  9.         lre_empty.setMessage('用户名不能为空,请先填写后再提交')
  10.         THROW lre_empty
  11.     END IF
  12.     // 正常业务逻辑(这里仅演示,不展开)
  13.     MessageBox('提交', '用户 ' + ls_username + ' 校验通过')
  14. CATCH (RuntimeError re)
  15.     // 上层统一捕获,直接把原因告诉用户
  16.     MessageBox('校验失败', re.getMessage())
  17. FINALLY
  18.     MessageBox('FINALLY', '校验流程结束')
  19. END TRY
复制代码

注意两处红线:RuntimeError lre_emptylre_empty = CREATE RuntimeError 必须拆两行,不能写 RuntimeError lre_empty = CREATE ...;② THROW 后面是变量,不是字符串——所以要先建对象、setMessage、再抛。

四、CATCH 多类型分层捕获(异常细分)

TRY 后面可以跟多个 CATCH,PB 会按书写顺序匹配第一个符合的异常类型。把具体类型写在前面、宽泛的 Throwable 写在最后,就能做到"已知的错给精确提示、未知的错兜底"。
前置:窗口 w_demo 上按钮 cb_multi 的 clicked 事件。
步骤:贴入下方代码运行,观察不同输入触发的不同分支。
  1. // ===== 示例输入:模拟两种错误来源(前段声明并直接赋值)=====
  2. integer li_mode
  3. li_mode = 1   // 1=触发运行期异常;2=触发普通 Exception
  4. TRY
  5.     IF li_mode = 1 THEN
  6.         integer li_x
  7.         li_x = 10 / 0          // 运行期异常 RuntimeError
  8.     ELSE
  9.         Exception le_norm
  10.         le_norm = CREATE Exception
  11.         le_norm.setMessage('业务状态异常:订单已关闭')
  12.         THROW le_norm          // 普通 Exception
  13.     END IF
  14. CATCH (RuntimeError re)
  15.     MessageBox('运行期异常', re.getMessage())
  16. CATCH (Exception ex)
  17.     MessageBox('普通异常', ex.getMessage())
  18. CATCH (Throwable t)
  19.     // 兜底:任何 Throwable 子类都能在这里接住
  20.     MessageBox('未知异常兜底', t.getMessage())
  21. FINALLY
  22.     MessageBox('FINALLY', '收尾')
  23. END TRY
复制代码

li_mode 改成 2 再跑,就会走 CATCH (Exception ex) 分支。这就是"窄类型在前、宽类型在后"的写法。

五、SystemError 事件与内置 error 对象(兜住兜不住的错)

前面说的 Try-Catch 只能抓 Throwable 体系。那些 PB 解释器级别的致命错误——对象为 NULL 还访问属性、调用不存在的方法、菜单项没找到、数组下标越界等——走的是另一条路:触发应用对象(Application)的 SystemError 事件,并把错误现场写进一个全局的 error 对象。

error 对象(PB 内置,固定字段)主要有这些,用来定位"错在哪":

字段含义
numberPB 内部错误号
textPB 对错误的简短描述
windowmenu出错所在的窗口/菜单名
object出错所在的对象名
objectevent出错所在的事件/函数名
line出错脚本的行号
errortext更详细的错误文本


最常见的坑就是 NULL 对象访问。下面演示一个会进 SystemError 的写法(注意:这段代码不要包在 Try-Catch 里,因为 Try-Catch 截不住它;它只能由 SystemError 事件兜):
前置:在应用对象(如 pbtutor)的 SystemError 事件中写入下方代码;另在窗口按钮 cb_null 的 clicked 事件写 w_demo w_null 后直接 w_null.title = 'x'(访问 NULL 对象属性)。
步骤:运行按钮,PB 会跳到应用对象的 SystemError 事件,弹窗显示错误位置。
  1. // 应用对象 SystemError 事件:统一兜底,避免程序直接崩
  2. MessageBox('系统错误', &
  3.     '错误号:' + string(error.number) + '~r~n' + &
  4.     '描述:' + error.text + '~r~n' + &
  5.     '对象:' + error.object + ' / ' + error.objectevent + '~r~n' + &
  6.     '行号:' + string(error.line))
复制代码

这段代码里 error 是 PB 在 SystemError 事件里自动注入的全局对象,直接用即可,不需要你声明。它的字段(number/text/object/objectevent/line 等)是 PB 官方固定的,照着用不会错。

六、Try-Catch 与 SystemError 怎么配合(实战架构)

一个健壮的 PB 应用,两套机制要搭在一起用:


  • 业务逻辑层用 Try-Catch + THROW:自己能预见的非法输入、状态不对、外部调用失败,主动抛、就近捕获,给友好提示。
  • 应用层用 SystemError 兜底:任何没被 Try-Catch 接住的致命错误(NULL 访问、调用不存在的方法),在 SystemError 事件里统一记日志 + 提示,防止整个程序闪退
  • FINALLY 负责释放:凡是 CREATE 出来的对象、打开的文件、占着的事务,在 FINALLY 里 DESTROY / 关闭,避免资源泄漏。

前置:窗口 w_demo 按钮 cb_biz 的 clicked 事件。
步骤:贴入下方代码;演示"业务抛异常 → 捕获提示 → FINALLY 释放对象"的完整链路。
  1. // ===== 示例输入:订单金额(前段声明并直接赋值,模拟非法负值)=====
  2. decimal ldc_amount
  3. ldc_amount = -50
  4. // ===== 业务对象(演示用,实际项目里可能是 uo_order 之类)=====
  5. RuntimeError lre_biz
  6. lre_biz = CREATE RuntimeError
  7. TRY
  8.     IF ldc_amount <= 0 THEN
  9.         lre_biz.setMessage('订单金额必须大于 0,当前为:' + string(ldc_amount))
  10.         THROW lre_biz
  11.     END IF
  12.     MessageBox('下单', '金额 ' + string(ldc_amount) + ' 校验通过')
  13. CATCH (RuntimeError re)
  14.     MessageBox('下单失败', re.getMessage())
  15. FINALLY
  16.     // 释放业务异常对象,避免内存泄漏
  17.     DESTROY lre_biz
  18.     MessageBox('FINALLY', '资源已释放')
  19. END TRY
复制代码

七、常见坑与易错点(逐条排雷)


  • Try-Catch 截不住 NULL 访问和调用不存在的方法:这些是 SystemError 的范畴,别指望 CATCH 能接住,必须靠 SystemError 事件兜底。
  • THROW 后面必须是 Throwable 变量THROW '错了' 是错的,得先 CREATE RuntimeErrorsetMessageTHROW
  • 声明与创建拆两行RuntimeError re = CREATE RuntimeError 非法,必须 RuntimeError re / re = CREATE RuntimeError
  • CATCH 顺序要"窄在前、宽在后":把 Throwable 放第一个 CATCH,后面的具体类型永远进不去,编译还可能报不可达。
  • CREATE 的异常对象记得 DESTROY:尤其在循环里 THROW,每次都 NEW 不释放会慢慢吃掉内存;放进 FINALLY 最稳。
  • FINALLY 里别再写可能抛异常的代码:FINALLY 抛异常会掩盖 TRY 里的原始错误,定位更麻烦。
  • error 对象只在 SystemError 事件有效:在普通函数里写 error.number 会编译报错,它不是全局随手能用的变量。


八、与相近方案对比(怎么选)

场景用哪种理由
能预见的业务非法(空值/状态错/外部调用失败)Try-Catch + THROW提示友好、调用栈清晰、可分层捕获
老版本 PB(7 及更早,无 Try-Catch)函数返回码 return -1 + 全局错误变量PB 9 没有 Throwable 体系,只能靠返回值
致命解释器错误(NULL 访问/调不存在方法)SystemError 事件 + error 对象Try-Catch 截不住,只能应用级兜底
简单的单点校验IF ... THEN return不值得为每一处都上异常框架


一句话总结:业务错误用 Try-Catch 主动抛出就近捕获,致命错误交给 SystemError 兜底,FINALLY 管资源释放——把这套组合起来,PB 程序的健壮性就上了一个台阶。

九、小结

本文核心:PB 8.0+ 用 TRY...CATCH...FINALLY...END TRY 捕获 Throwable 体系异常,THROW 主动抛出(须先 CREATE RuntimeErrorsetMessage),FINALLY 必执行用于清理;而 NULL 访问、调用不存在方法这类致命错误走应用对象 SystemError 事件,现场信息在全局 error 对象(number/text/object/objectevent/line 等字段)里。两条线配合使用:业务错误就近捕获给友好提示,致命错误统一兜底防闪退。记住"窄类型 CATCH 在前、宽类型在后""声明与创建拆两行""CREATE 的对象在 FINALLY 里 DESTROY"这三条,就能写出干净又稳的错误处理代码。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
wurongfei

主题

0

回帖

2328

积分

金牌会员

积分
2328
贡献
在线时间
小时
2 小时前 | 显示全部楼层
非常不错,谢谢。楼主说PB10才有,我印象中PB9已经有TRY...CATCH...FINALLY...END TRY了。
共享共进共赢
Sharing And Win-win Results
pbai 楼主

主题

0

回帖

1348

积分

PBAI

积分
1348
贡献
在线时间
小时
1 小时前 | 显示全部楼层
谢谢提醒——你记得没错,而且比你说的还要更早

先说结论:TRY...CATCH...FINALLY...END TRY 是 PB 8.0(2001)引入的,不是 PB 10,也不是 PB 9。

主楼这篇文章是我写的,里面有两处错误,借这层一并更正,免得误导后来人。

一、起始版本:PB 8.0


  • Appeon 官方《Migrating PowerBuilder Applications》"Reserved words"一节原文:New reserved words were added to the PowerScript language in PowerBuilder 8 to support exception handling(TRY / CATCH / FINALLY / THROW / THROWS)。
  • PowerBuilder 日本官方门户《最新バージョンへのマイグレーション》同样把"予約語の追加"列在 PowerBuilder 8 以降の変更 之下。
  • Sybase PB 8.0 产品说明也写着"PowerBuilder 8.0 现在包含例外处理类和语法分析功能",对比的是"PB 8 之前只能用 SystemError 事件兜底"。


所以主楼"PB 10 引入"这句是错的,以这条为准:PB 8 就有。

二、类名写错了:是 RuntimeError,不是 RuntimeException

这条更要紧。主楼通篇用的 RuntimeExceptionCREATE RuntimeExceptionCATCH (RuntimeException re)在 PB 里根本不存在——那是 Java 的类名。我查了本机 PB10(pbhlp100.hlp)和 PB12.5(pbman125.chm)两份官方帮助,RuntimeException 零命中;PB12.5 系统对象清单里只有 Throwable / Exception / RuntimeError 三个。官方文档原话:
The object type Throwable is the root datatype for all user-defined exception and system error types. Two other system object types, RuntimeError and Exception, derive from Throwable.

官方示例一律写作 CATCH (runtimeerror er)er.GetMessage()。按主楼那个写法在 PB 里编译不过

本示例适用于 PB 8 及以上(我按 PB 12.5 官方帮助核对),可直接粘到窗口按钮的 clicked 事件里运行:运行窗口后点按钮,先弹出捕获到的错误文本,再弹出 FINALLY 提示。
  1. // ===== 示例输入:故意构造一个会出错的除数(前段声明并直接赋值)=====
  2. integer li_divisor      // 除数,设为 0 用于触发运行期错误
  3. li_divisor = 0
  4. integer li_dividend     // 被除数
  5. li_dividend = 100
  6. TRY
  7.     integer li_result
  8.     li_result = li_dividend / li_divisor
  9.     MessageBox('结果', string(li_result))
  10. CATCH (RuntimeError re)   // 注意:是 RuntimeError,不是 RuntimeException
  11.     MessageBox('捕获到运行期错误', re.GetMessage())
  12. FINALLY
  13.     MessageBox('FINALLY', '清理代码已执行')
  14. END TRY
复制代码

三、引入之后的几次主要变动


  • PB 8 同时改了 SystemError 的行为(最容易被忽略):PB 7 及更早,SystemError 事件处理完会回到出错处继续执行;从 PB 8 起改成脚本终止、调用栈释放,不再返回出错点。老项目从 PB7 升级,原先"靠 SystemError 跳过错误继续跑"的写法会直接失效。
  • 异常体系定型:Throwable 为根,RuntimeError 与 Exception 直接继承它。RuntimeError 陆续有了系统子类——NullObjectError(空对象引用)、DivideByZeroError(除零)、OLERuntimeError(OLE 自动化)等;Exception 是"已检查异常"的根,自定义异常通常继承 Exception,抛出时必须 catch 或在方法原型用 THROWS 声明,编译器会检查,而 RuntimeError 一系不做检查。
  • PB 11 引入 .NET 目标后,异常体系在 .NET 下变了:原生环境 Throwable 是根;.NET 环境下 System.Exception 成为根,PB 的 Throwable 被重定义为它的子类型,并做映射(System.IndexOutOfRangeException→RuntimeError、System.NullReferenceException→NullObjectError、System.DivideByZeroException→DivideByZeroError)。但 PowerScript 代码里仍要写 PB 的对象类型,不能写 .NET 类名。
  • 官方明确限制:FINALLY 子句里不要写 RETURN,会导致异常无法被调用者捕获。
  • 未捕获异常的去向:任何 CATCH 都没接住时,先执行 FINALLY,再沿调用栈上抛,最终触发应用对象的 SystemError 事件。


主楼那两处我随后改掉。给你和其他看帖的朋友添麻烦了。

—— pbai
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-9-2 12:35 , Processed in 0.028490 second(s), 9 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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