PB9.0框架源码全解析:从数据库连接到数据窗口封装实践
2026/9/16 23:53:31 网站建设 项目流程

简介:面向PB 9.0开发者的完整框架源码包,随包附带可直接附加的MDF数据库文件。该版本是Sybase公司2003年发布的企业级开发工具,其数据窗口和对象向导能极大简化数据库应用开发,适合需要快速搭建桌面程序或通过实际代码学习数据窗口、PowerScript脚本与数据库交互的开发者。资源共157个文件,压缩后约3.67MB,其中包含107张位图素材用于窗体与按钮美化,18个PBL库文件存放核心逻辑,另有OCX控件、DLL动态库、MDF/LDF数据库文件和SQL脚本,整体覆盖界面、业务逻辑和数据存储三层,目前已有882人学习下载。框架内置数据库连接、事务处理、错误处理、数据访问对象等通用模块,数据窗口可直接操作Oracle、SQL Server、MySQL等常见数据库,无需编写复杂SQL即可完成增删改查,显著减少重复编码。拿到源码后可在PB9.0中打开工作区,研究窗口布局、对象向导生成的代码与目录组织方式,并基于已有模块扩展业务逻辑;对于刚接触PowerBuilder或希望项目结构更规范的开发者,这套带数据库的完整项目范本能辅助理解企业级应用的分层思想与数据库交互细节,非常适合作为项目启动模板或源码学习资料。

1. 拿到 pb9.0 框架源码包,先别急着连数据库

PowerBuilder 9.0 在今天的开发环境里已经算是老古董,但在医疗、金融、烟草等行业的遗留系统里,它依然是生产主力。很多团队手上拿到的就是类似“pb9.0不错的框架(源码带数据库).zip”这样的压缩包,体积不大,里面塞着 PBL 库、SQL 脚本和几个示例应用。这类资源通常来自老工程师的私藏或者项目交接,价值不在代码本身,而在你能不能快速让它跑起来、看懂它的封装思路、然后迁移到自己的业务里。

这个标题里最关键的几个词是“框架”“源码”“数据库”。市面上很多 PB 源码包只是业务功能的堆砌,谈不上框架;而真正能称为框架的,一般具备三个特征:统一的数据窗口访问入口、可配置的数据库连接管理、以及一套通用的窗口基类或用户对象。也就是说,拿到压缩包后,你首先要做的不是双击 exe,而是看它的 PBL 组织方式和数据库脚本,判断它是“能跑的示例”还是“值得长期维护的基础设施”。

这篇文章会按我评估 PB 框架的一贯路径来写:先讲框架的判定标准和源码包的常见结构,再讲数据库脚本的导入与连接配置,接着深入到数据窗口封装和事务管理这类核心机制的改造,最后落在 UI 层和发布部署的实战细节上。每一部分都配有可以直接抄走的代码或操作步骤。哪怕你手里的压缩包和某个具体的框架名对不上号,这套方法论也能让你在半天内摸清它的底细。

2. 框架选型的判断标准与源码包结构拆解

2.1 用一个最小案例判断是不是“真框架”

判断一个 PB9.0 源码包能不能被称为框架,最简单的办法是看它如何处理一个“新增用户”的功能。伪框架的做法是:在窗口里直接放一个数据窗口控件,然后在“保存”按钮里写dw_1.update(),事务对象直接用全局默认的SQLCA。这种代码写完就死,换个表就要重新写一遍。

真框架通常会把“取数”“保存”“校验”“事务提交”这些动作抽象到用户对象或全局函数里。你打开源码后,先搜一下有没有类似uo_datawindow这样的自定义用户对象,或者of_retrieveof_update这样的对象级函数。如果存在,说明这个框架对数据窗口做了统一封装,值得继续看下去。否则,它本质上只是“带界面的增删改查示例”,价值会低很多。

提示:PB9.0 的库文件后缀是 PBL,每个 PBL 可以看作一个代码包。框架类源码通常会拆成 app.pbl、base.pbl、common.pbl 这种按职责划分的多个库文件,业务代码单独放。如果你拿到手的压缩包里只有一个 PBL 或只有一个巨大的 PBL,那基本可以判定为“个人风格”的代码,维护风险较高。

我再提供一个判断维度:看它对 SQL 的处理方式。框架级代码会把 SQL 写到数据窗口对象的 SQL 属性里,或者集中在数据存储(DataStore)的数据源中,而不是散落在窗口的按钮事件里。你可以随机打开 3 到 5 个窗口,检索SQLCA的出现次数。如果每个窗口都直接操作 SQLCA,说明事务管理没有收敛,这属于“能用”但不适合二次开发的代码。

2.2 源码包目录的常规布局与识别要点

下载并解压“pb9.0不错的框架(源码带数据库).zip”后,我一般会按下面的顺序快速浏览目录结构:

  • 根目录下的 .pbl 文件列表
  • Database 或 SQL 文件夹下的建库脚本
  • 是否有 .pbd 文件(这是编译后的库文件,源码包中通常不应该出现,除非是第三方控件)
  • 示例应用和主程序的入口对象名称
  • 是否有 README 或部署说明文档

下面是一个典型 PB9.0 框架源码包的目录示意:

root/ │ ├── app.pbl // 应用入口,通常包含 open 事件 ├── base.pbl // 基类对象,如 uo_datawindow、uo_command 等 ├── common.pbl // 公共函数、日期处理、字符串工具 ├── business.pbl // 业务对象和数据窗口 │ ├── Database/ │ ├── create_db.sql // 建库脚本 │ └── init_data.sql // 初始化基础数据 │ └── exe/ // 编译输出目录,可能为空

这种拆分方式的优势在于分层清晰:base.pbl的改动会影响所有窗口,属于“牵一发动全身”的核心层;common.pbl是无状态工具函数库,是排查问题时的“第一现场”。遇到结构混乱的压缩包——比如 PBL 命名随意、脚本和代码混在一起——我建议先重建目录,再把源码按依赖关系重新组织,否则后续接手的人会非常痛苦。

2.3 从入口对象反推框架的初始化流程

在 PB9.0 中,应用对象的open事件是框架的启动入口。打开应用对象,你会发现框架级代码和普通代码的关键区别:普通应用在 open 事件里直接连数据库然后打开主窗口,而框架应用会先做环境初始化,再加载配置,然后才进入登录或主界面。

举一个常见的框架初始化代码片段:

// app.pbl -> Application Object -> Open 事件 // 框架的标准启动流程 int li_rtn string ls_cfg_path // Step 1: 读取应用配置文件 ls_cfg_path = "app.ini" li_rtn = of_read_config(ls_cfg_path) if li_rtn <> 0 then MessageBox("初始化失败", "读取配置文件失败,请检查 app.ini 是否存在") return end if // Step 2: 建立数据库连接(事务对象由框架统一创建) li_rtn = of_connect_database() if li_rtn <> 0 then MessageBox("初始化失败", "数据库连接失败,请联系管理员") return end if // Step 3: 加载用户权限与公共缓存 of_load_security() of_load_cache() // Step 4: 打开主窗口 open(w_main)

这段代码逻辑上分四步,每步都值得展开说:of_read_config通常读取的是 INI 文件或注册表,PB9.0 时代 INI 文件更常见,它会填充一个全局配置结构体,包括数据库服务器地址、数据库名、用户名等;of_connect_database内部做的是SQLCA.DBMSSQLCA.ServerNameSQLCA.LogID等属性的赋值,然后调用CONNECT USING SQLCA;of_load_securityof_load_cache是框架的“增值服务”,负责把菜单权限、基础数据字典提前加载到内存中,避免后续窗口反复查库。

注意:有些源码包的应用对象 open 事件里直接写死数据库连接参数,这是最糟糕的写法之一。如果发现这种问题,应该立即改为从配置文件读取,否则部署到新环境时每次都要改代码重新编译。

3. 数据库脚本导入与连接配置的完整流程

3.1 识别脚本是 SQL Server 还是 Oracle——从表结构语句看起

PB9.0 时代最常见的是 SQL Server 2000/2005 和 Oracle 9i/10g。“源码带数据库”的压缩包里,建库脚本的方言会直接告诉你这个框架原本的“生长环境”。拿到 SQL 脚本后先看 CREATE TABLE 语句的特征:如果用的是varcharnvarchardatetime,大概率是 SQL Server;如果用varchar2numberdate,那就是 Oracle。

还有一种情况是脚本同时兼容多个数据库,通常会在文件头部声明“-- Support SQL Server and Oracle”,这种情况下代码里会大量使用SQLCA.DBMS判断来动态拼 SQL。遇到这种脚本时,你要特别注意数据类型映射问题,尤其是日期类型和自增字段的实现方式。我见过不少框架源码在 SQL Server 下跑得好好的,换到 Oracle 就挂,最后发现是自增主键的生成方式完全不同。

下面是一段典型的 SQL Server 建表脚本:

-- create_db.sql 片段 -- SQL Server 2000+ 方言 CREATE TABLE t_user ( user_id INT IDENTITY(1,1) PRIMARY KEY, user_code VARCHAR(20) NOT NULL, user_name VARCHAR(50) NOT NULL, dept_id INT NULL, create_time DATETIME DEFAULT GETDATE() ) GO CREATE INDEX idx_user_code ON t_user(user_code) GO

而在 Oracle 中,同样的表需要这样写:

-- Oracle 9i+ 方言 CREATE TABLE t_user ( user_id NUMBER(10) NOT NULL, user_code VARCHAR2(20) NOT NULL, user_name VARCHAR2(50) NOT NULL, dept_id NUMBER(10) NULL, create_time DATE DEFAULT SYSDATE ); ALTER TABLE t_user ADD CONSTRAINT pk_t_user PRIMARY KEY (user_id); CREATE SEQUENCE seq_user_id START WITH 1 INCREMENT BY 1;

如果你发现脚本里同时有IDENTITYSEQUENCE,说明它自带双数据库兼容层。这种框架在选型时占优势,但也会带来一个后遗症:为了兼容,代码里很多 SQL 会写得偏向“最小公约数”,比如不用TOP N也不用ROWNUM,改用自己的数据窗口排序逻辑。这个细节在源码评审时值得注意。

3.2 用 isql 或 sqlplus 重建数据库的实操步骤

拿到脚本后不要直接在数据库客户端工具里手动一句一句执行,那是在浪费时间。我习惯用命令行方式批量导入脚本,因为速度快、报错信息清楚,而且可以写进部署文档里重复执行。

在 SQL Server 环境下,用osql工具导入,命令如下:

# Windows 命令行环境 osql -S 127.0.0.1 -U sa -P your_password -d master -i create_db.sql osql -S 127.0.0.1 -U sa -P your_password -d your_db -i init_data.sql

-S指定服务器实例名,-d指定默认数据库,-i指定要执行的脚本文件。第二步的your_db需要在第一步成功执行后才会存在,所以顺序不能颠倒。

Oracle 环境用sqlplus,命令稍有不同:

sqlplus system/your_password@127.0.0.1:1521/orcl @create_db.sql sqlplus your_username/your_password@127.0.0.1:1521/orcl @init_data.sql

执行过程中如果报错,不要慌张。常见的报错有两类:一是脚本里引用了不存在的数据库对象——先建用户后建表,顺序很重要;二是中文字符集乱码——检查 NLS_LANG 环境变量是否和服务器端一致,Windows 下设置set NLS_LANG=SIMPLIFIED CHINESE_CHINA.ZHS16GBK通常能解决问题。

3.3 PB9.0 的数据库连接画板配置与代码级连接参数

脚本导入成功后,下一步就是让 PB9.0 应用连上数据库。在开发环境里,最常见的配置方式是通过 Database Profiles 画板,在窗口里选择相应 DBMS 然后逐一填写参数。这种方式适合本地调试,但发布到生产环境时还是应该走 INI 文件加代码的方式。

PB9.0 的 Database Profiles 配置界面里需要填写的内容比较固定,以下面这张表为参考:

配置项SQL Server 建议值Oracle 建议值说明
Profile Namedev_sqlserverdev_oracle随意命名,便于区分
Server127.0.0.1127.0.0.1数据库主机地址
Databaseyour_dbORCLSQL Server 用库名,Oracle 用 SID
Login IDsasystem数据库账号
Password你的密码你的密码建议开发环境与生产分离

画板配置只能保证你在开发环境能连上数据库。框架要“可部署”,必须支持配置化的连接方式。下面是 PB9.0 中通过代码读取 INI 并连接 SQL Server 的惯用写法:

// 在应用对象的 open 事件中调用 // 或单独封装在 of_connect_database() 函数中 // 读取 app.ini 中的 [Database] 段 SQLCA.DBMS = ProfileString("app.ini", "Database", "DBMS", "MSS Microsoft SQL Server") SQLCA.ServerName = ProfileString("app.ini", "Database", "ServerName", "127.0.0.1") SQLCA.Database = ProfileString("app.ini", "Database", "Database", "your_db") SQLCA.LogID = ProfileString("app.ini", "Database", "LogID", "sa") SQLCA.LogPass = ProfileString("app.ini", "Database", "LogPassword", "") SQLCA.AutoCommit = False // 执行连接 CONNECT USING SQLCA; // 判断连接是否成功 if SQLCA.SQLCode <> 0 then MessageBox("连接失败", "数据库连接错误: " + SQLCA.SQLErrText) return end if

这段代码的关键在于ProfileString函数的三个参数:配置文件名、段名、键名。DBMS的取值比较特殊,如果是 SQL Server 2000 就是"MSS Microsoft SQL Server",如果是 Oracle 则是"O90 ORACLE 9i""O10 ORACLE 10g"。这个值不能写错,否则连接时会报“Unable to load DBMS"一类的错误,因为它本质上是去加载对应数据库接口的 DLL。

另外要注意SQLCA.AutoCommit这个属性。在 PB9.0 中,若设成True,每条 SQL 自动提交,这通常在执行 DDL 语句时才需要;业务数据操作一律设成False,交给事务对象统一管理。

提示:压缩包里如果自带一个名为“数据库配置工具”的 PBL 或窗口,恭喜你,这通常是框架级代码的核心体现。这个工具的本质上就是在界面层封装了上述配置项,然后写入 INI 文件。

4. 框架核心——事务管理、数据窗口封装和分页逻辑改造

4.1 为什么说事务对象是 PB 框架的“引擎”

PowerBuilder 的事务对象(Transaction Object)是一种特殊的非可视对象,保存了数据库连接的属性和状态。默认的SQLCA是一个全局事务对象,但框架级的代码通常不直接用SQLCA作为唯一事务对象,而是创建多个事务对象来支持多数据源或线程内并发。

“源码带数据库”的框架中,事务管理的好坏直接决定你在并发场景下会不会踩“连接泄漏”的坑。在 PB9.0 里,数据窗口对象和数据存储对象(DataStore)都有一个SetTransObject方法,它接收一个事务对象作为参数,并把这个事务对象和数据源绑定。

常见的管理模式是把“连接、提交、回滚、断开”封装到一个自定义用户对象uo_transaction中,然后让所有窗口持有该对象的一个引用。下面是一段典型的提交和回滚封装代码:

// uo_transaction 用户对象中的方法定义 // of_commit(): 提交事务,并返回执行状态 // 在框架中,所有保存按钮事件最终都调用这个方法 integer li_return // 提交前先检查事务状态 if SQLCA.SQLCode = 0 then COMMIT USING SQLCA; li_return = 1 else ROLLBACK USING SQLCA; li_return = -1 MessageBox("事务错误", SQLCA.SQLErrText) end if return li_return

这段代码的意图很明确:确保每一次提交都有事务状态校验,任何一次失败都触发回滚。

更关键的一点是事务对象的生命周期管理。PB 应用是事件驱动的,窗口打开时建立连接、窗口关闭时释放连接,这种思路本身没错,但要防止所有窗口共用同一个全局事务对象时出现互相干扰。

如果框架为每个窗口新建事务对象,一定要在窗口的 close 事件里显式调用DISCONNECT USING ltr_sqlca;来释放连接。否则,连接会一直被占用,数据库的进程数会被耗尽。

4.2 数据窗口基类对象——PB 框架的灵魂

如果只让我从压缩包里挑一个文件来评价框架好坏,我会看uo_datawindow——一个继承自数据窗口控件的自定义用户对象。它是 PB 框架的核心抽象层,所有业务窗口上的数据窗口控件都应该用这个对象,而不是系统的原始控件。

框架级的uo_datawindow至少要封装以下几个能力:

  • of_retrieve():统一取数入口,内部处理 Retrieve 前的参数设置和游标状态
  • of_update():统一保存入口,内部处理 Update 属性校验和事务边界
  • of_setfilter():统一过滤入口,防止 SQL 注入风险(虽然是桌面应用,但同样要注意)
  • of_export_excel():统一的导出能力
  • of_clear():清理数据和状态

在 PB 开发中,你会大量接触 DataWindow 的RetrieveUpdate方法。在框架封装下,业务窗口里通常只写一行代码:dw_list.of_retrieve(),而不需要关心内部事务对象是哪个、当前有没有启动事务。

我的建议是,拿到源码后第一步就全局搜索dw_1.Retrieve(这种直接调用系统方法的代码。如果业务窗口大量绕过封装直接调用系统方法,那这个框架的抽象层基本形同虚设。你依然可以拿它来学习数据窗口的用法,但要“直接当基础框架用”就需要谨慎了。

4.3 兼容 Oracle 与 SQL Server 的 Sequence 取号实现

PB9.0 框架经常被用于重新开发行业管理系统,这类系统的单据号(如采购单号、审批编号)通常需要“流水号 + 日期 + 序号”的格式。在源码包里,如果框架已定义好取号函数,你不必去重造轮子。但如果没有,就非常容易踩到不同数据库的 ID 生成差异。

在 SQL Server 中,取号通常用一个计数器表,然后通过事务控制并发。在 Oracle 中,最常见的是用 Sequence。一个兼容两者的取号函数大概长这样:

// uo_common 用户对象中的 of_get_next_number 函数 // 参数:as_bill_type 单据类型,返回下一个流水号字符串 string ls_prefix, ls_serial long ll_next // 处理语句根据数据库类型不同而不同 if SQLCA.DBMS = "O90 ORACLE 9i" or SQLCA.DBMS = "O10 ORACLE 10g" then // Oracle 用 Sequence 取号 DECLARE cur_seq CURSOR FOR SELECT TO_CHAR(SEQ_BILL_NO.NEXTVAL) FROM DUAL; OPEN cur_seq; FETCH cur_seq INTO ls_serial; CLOSE cur_seq; else // SQL Server 用 UPDATE + SELECT 方式取号 UPDATE t_bill_no SET current_no = current_no + 1 WHERE bill_type = :as_bill_type; SELECT current_no INTO :ll_next FROM t_bill_no WHERE bill_type = :as_bill_type; ls_serial = String(ll_next) end if ls_prefix = left(String(Today(), "yyyymmdd"), 8) + "-" + as_bill_type + "-" return ls_prefix + ls_serial

这段代码展示了一个非常务实的处理方式:不追求华丽的抽象,而是根据数据库类型走不同的取号逻辑。中间用了嵌入式 SQL(SELECT INTO与游标)来获取数据。在 PB9.0 中,如果你在函数体里写了游标,并且函数内部还有事务操作,最终还是要小心游标未关闭的问题。

在 Oracle 下如果代码中大量使用 Sequence 取号,记得关注 Sequence 的缓存大小设置。如果CACHE 20遇到系统重启,会丢失部分序号,出现跳号。这是正常现象,不是代码 bug,但需要提前解决业务心智预期。

4.4 数据窗口检索与保存的标准写法

业务窗口的开发几乎都是这个套路:窗口 open 事件里设置事务并取数;查询按钮里动态拼接过滤条件后再次检索;保存按钮里做数据验证并提交。如果框架封装到位,你在窗口里写的代码会非常薄。

一个典型的数据窗口保存流程:

// 保存按钮的 clicked 事件 // 先从数据窗口取修改状态 if dw_detail.ModifiedCount() = 0 and dw_detail.RowCount() > 0 then MessageBox("提示", "没有需要保存的数据") return end if // 做数据窗口业务校验 if dw_detail.AcceptText() <> 1 then MessageBox("校验失败", "请检查当前行数据格式") return end if // 调用框架封装的保存函数 if dw_detail.of_update() = 1 then COMMIT USING SQLCA; MessageBox("成功", "数据已保存") else ROLLBACK USING SQLCA; MessageBox("失败", "保存时出错: " + SQLCA.SQLErrText) end if

AcceptText在 PB 数据窗口中专门用来把当前编辑框中的内容写入数据缓冲,是整个保存过程中最容易被忽视的步骤。很多新手直接调Update(),但最后少只修改单行数据时一起丢失,主因就是没调AcceptText这一下。

在框架级的代码里,of_update()内部会做数据窗口Update属性的设置,把可更新字段和主键字段做比较,再根据Row的状态来决定执行 INSERT 还是 UPDATE,这是 PB 数据窗口最强大的特性之一。用好这个机制,你基本不需要在业务代码里写满手 SQL 字符串。

5. 窗口基类、菜单权限和登录过滤——把框架“盘活”的关键

5.1 从 w_base 窗口基类看框架的 UI 组织方式

PB9.0 框架里的窗口通常继承自一个基类窗口,常命名为w_basew_sheet。这类窗口里预埋了统一的事件处理逻辑:打开时居中显示、根据用户权限分配菜单按钮、关闭时释放资源。

很多“不错的框架”会在基类窗口的 open 事件里写一段权限初始化代码,通过全局用户对象判断当前用户的角色,然后动态修改窗口上各按钮的 Visible 或 Enabled 属性。这样业务窗口自身只需要关注业务数据加载,不需要再写权限判断。

下面是一段窗口基类中常见的“按权限控制按钮”的逻辑:

// w_base 的 open 事件 integer i string ls_perm // 从全局用户对象获取当前用户权限串 ls_perm = gn_security.of_get_user_permission() if IsNull(ls_perm) or len(ls_perm) = 0 then MessageBox("提示", "当前用户权限为空") return end if // 遍历当前窗口所有按钮,根据 Tag 属性匹配权限项 for i = 1 to this.ControlCount() if this.Control[i].TypeOf() = CommandButton! then if pos(ls_perm, this.Control[i].Tag) = 0 then this.Control[i].Visible = False end if end if next

这段代码的思路是把按钮的Tag属性当作权限码,全局权限串里包含对应权限码则显示按钮,否则隐藏。它能跑通一个“菜单级 + 按钮级”的权限控制模型。

5.2 登录窗口与密码加密的几种在线做法

“框架 + 源码带数据库”的资源往文档里都会附一套登录模块。讲真,不要嫌 PB9.0 老,机制放今天依然有参考价值。常见的登录验证流程是:用户输入账号密码,用 SQL 从用户表查询,比对密码后打开主窗口。

但如果你接手的是真正“不错”的框架,密码不会是明文存储。有的用 MD5 摘要,有的用可逆的对称加密算法。PB9.0 原生没有直接开箱即用的 MD5 函数,但可以调用 Windows 的 CryptoAPI 或封装一个外部扩展函数来实现。比较早期的框架,甚至只是把密码字段 XOR 一下,也算“加了密” —— 别惊讶,那个年代的合规性状态就那样。

如果你要把这套登录模块升级到 PB9.0 的极限——也就是与现代 Web 应用同思路——至少要做到两件事:一是在客户端内存中不保存明文密码;二是在 SQL 查询中传递加密摘要而不是原始字符串。提供一段调外部 DLL 的思路代码:

// 调用本地 user32.dll 或特定加密 dll // 这不做真正的加密,只展示 PB9.0 外部函数声明方式 FUNCTION long CryptAcquireContext( ... ) LIBRARY "advapi32.dll" FUNCTION long CryptCreateHash( ... ) LIBRARY "advapi32.dll" FUNCTION long CryptHashData( ... ) LIBRARY "advapi32.dll"

实际上在 PB9.0 中更常见到的是用BlowfishDES等算法封装在外部 DLL 中。

PB 应用通过声明FUNCTION ... LIBRARY调用系统级 API。框架里如果有这种代码,对了解 Windows 程序设计和 PB 内部调用机制都很有帮助。

5.3 菜单与窗口的挂接方式——MDI 框架的基本功

PB9.0 传统企业应用的界面框架通常是 MDI(多文档界面)结构,用一个主窗口(w_main)作为容器,里面有菜单m_main,所有业务窗口作为子窗口在客户区内打开。

主窗口的菜单项点击后,通过OpenSheet函数打开对应业务窗口:

// 菜单 m_main 的“用户管理”菜单项 clicked 事件 OpenSheet(w_user_list, "用户管理", w_main, 0, Layered!)

OpenSheet的参数值得解释一下:第一个是窗口实例,第二个是显示在子窗口标题栏上的文本,第三个是父窗口,第四个是层索引,最后是排列方式。Layered!是 PB 枚举类型之一,表示层叠排列;其他常见值还有Original!Cascad!等。

框架源码包里,菜单权限通常通过数据库表驱动,而不是直接在菜单编辑器里写死。菜单项的Tag与权限表关联,点击前检查当前用户是否拥有该菜单权限。这种方式在 PB9.0 中很成熟,但需要开发人员提前设计好权限表结构和菜单 code 的对应关系。

提示:如果压缩包里的菜单是继承自m_base,那么权限控制的代码多半在基类菜单里,业务菜单只做功能挂接。这是框架化的一个明显提现。

6. 发布部署时需要做好的几个验证与调优动作

框架在开发环境跑通只是第一步。真正让你在领导面前有底气的,是你把程序连同数据库脚本部署到一台新服务器上,打开 exe 就能登录、进菜单、做增删改查全流程稳定。以下几个验证动作和参数调整,是我每次部署 PB9.0 框架应用时的固定动作。

首先验证数据库连接池的负载表现。PB9.0 的数据库连接是进程内直连,没有真正的连接池概念。也就是说,并发用户数越多,数据库进程就越多。框架如果用全局事务对象,它会反复在同一连接上执行操作,这时不会有泄漏。但若是每窗口自行CONNECT/DISCONNECT,你要注意高并发下的会话切换开销。

我的习惯是在应用初始化时做一次“连接健康检查”,顺便把数据库的@@VERSIONsysdate拉出来打印到日志里:

string ls_version SELECT @@VERSION INTO :ls_version FROM t_dummy; // t_dummy 是只有一个空字段的辅助表,专门用于连接测试 if SQLCA.SQLCode <> 0 then // 记录失败日志 end if

其次,检查应用编译后是否需要携带运行库 DLL。PB9.0 发布的 exe 不能开箱即用,通常需要以下文件伴随部署:

  • pbvm90.dll:虚拟机核心库
  • pbdwe90.dll:数据窗口引擎
  • pbrtc90.dll:运行时库
  • 数据库接口 DLL,如pbodb90.dll(ODBC)或pbo9290.dll(Oracle 9i 专用接口)

如果少了pbdwe90.dll,程序打开后所有数据窗口控件都会变成空白框,这是新手部署最常遇到的“数据窗口不显示”问题。重新安装 PB9.0 运行时环境,或者手动拷贝对应 DLL 到 exe 目录下,都能解决。

第三,做一次“清零环境测试”。在一台没装过 PB 的干净虚拟机上,只拷贝 exe、DLL、INI 文件和数据库脚本,测试从零环境是否能跑通整个流程。这样做的目的是暴露漏编译的资源文件——比如有的框架源码里引用了图片文件.bmp、图标.ico或第三方自定义控件,这些资源在开发机上有,但没被编译进 PBD 或 EXE,部署后就会自动消失。

第四,把配置文件从代码中剥离出来。框架级源码里app.ini应该包含数据库类型、服务器地址、端口、日志开关等项目,其中日志开关值得展开:

[System] ; 日志级别:1=错误 2=警告 3=信息 4=调试 LogLevel=3 [Database] DBMS=O10 ORACLE 10g ServerName=127.0.0.1 Database=orcl LogID=app_user LogPassword=encrypted_string

LogPassword这个地方要注意,不推荐明文存储。PB9.0 框架中常见的做法是使用Registry或专用加密工具将脱敏字符串写入配置,运行时代码里再解密回密码。如果你在压缩包里看到encrypted_string这种值,说明框架作者已经为此留好了接口。

最后做一次极端条件测试:在断网或数据库停机状态下启动应用。框架级应用应该弹出友好的错误提示,然后安全退出。如果出现未捕获异常或闪退,说明 open 事件里的异常捕获不完整。你要在应用对象的SystemError事件里补上全局兜底处理,例如记录到日志文件、给出可读的提示信息、终止执行。

把以上五点做完,这套“pb9.0 框架(源码带数据库)”才算真正接手成功。这时你再回头翻源码,会发现当初让你困惑的封装逻辑都有了实际意义——它一直在解决部署环境、并发连接、权限一致这些具体问题,而在开发机上你永远遇不到这些情况。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询