PowerBuilder 函数与事件的调用
本文讲述调用函数的不同方法,如何使用内置函数和事件处理函数,如何定义和使用自己的函数和事件处理函数,以及说明远程过程调用(RPCs)和属于动态链接库(DLLs)的外部函数。
一、函数和事件
PowerScript 语言中许多强大功能的实现依赖于它的那些可以在表达式和赋值语句中使用的内置函数。
PowerBuilder 对象具有内部事件响应函数和其他功能函数,可以通过自定义函数和外部函数增强其功能。
PowerScript 还具有不附属于对象的系统函数,也可以定义自己的全局函数、说明外部函数和远程过程调用。
1、调用函数和事件
如果不需要精确控制何时和如何调用函数,可以使用下面的简单语法调用函数和事件响应函数:
[objectname.][ type ] methodname([argumentlist])
objectname 对象实例名,在前面加父对象名或域,并用域区分符(::)分割。系统或全局函数省略 objectname;
type EVENT 或 FUNCTION(默认值)
methodname 事件响应函数或函数名
argument 传递给函数的参数
例如:
下面的语句调用了系统函数 Asc:
charnum = Asc("x")
下面的语句在 DataWindow 的脚本中调用了对象的函数:
Update() dw_1.Update() This.Update()
下面调用了用户自定义函数 gf_setup_appl:
gf_setup_appl(24,"Window")
下面的语句在 DataWindow 控件的 Clicked 事件脚本中调用了同一控件的 DoubleClicked 事件响应函数,系统传递给 Clicked 事件的参数被传递给 DoubleClicked 事件响应函数:
This.EVENT DoubleClicked(xpos,ypos,row,dwo)
2、各种类型函数和事件响应函数
术语 | 含义 |
事件 (event) | 对象或控件的引起脚本运行的动作,用户可以通过单击控件的方法触发事件,也可以在另一脚本中用语句触发事件 |
用户事件 (user event) | 一个用户自定义的事件,由用户定义该事件的参数、返回值以及如何映像系统消息 |
系统或内置事件 (system or build-in event) | PowerBuilder 定义对象的一部分,通常由用户动作和系统消息触发,传递给它事先定义的一组参数,返回 long 型或不返回值 |
函数 (function) | 执行指定操作的程序或例程 |
系统函数 (system function) | 独立于对象的 PowerScript 的内置函数 |
成员函数 (object function) | 该函数作为对象定义的一部分,PowerBuilder 有许多定义过的成员函数,用户也可以定义自己的成员函数 |
用户定义函数 (user-define function) | 用户定义的函数,可以在函数画板中定义全局函数,在窗口画板和用户对象画板中创建成员函数 |
全局函数 (global function) | 可以被所有脚本调用的用户定义的函数,系统函数也为全局可见,但它们在查找顺序中处于不同位置 |
局域外部函数 (local external function) | 属于一个对象的外部函数,可以在窗口画板和用户对象画板中说明,但其定义在另一DLL 中 |
全局外部函数 (global external function) | 在所有画板中说明的外部函数,全局可见,但其定义在另一 DLL 中 |
远程过程调用 (RPC,Remote Procedure Call) | 数据库内置过程,可以在脚本中调用,可以说明为全局或局域的(属于某对象),过程的定义在数据库中 |
3、函数和事件的比较
函数和事件没有太大不同,它们的相同点有:
• 都具有参数和返回值
• 可以动态地或静态地调用它们(静态调用只支持成员函数,不支持全局函数或系统函数)
• 用户可以调用或者触发它们
它们的不同点在于:
• 事件与对象相联系,而函数可以为全局函数或者作为对象定义的一部分
• PowerBuilder 使用不同的查询顺序对待二者
• 调用未定义的事件不会触发错误,而函数会触发错误
• 函数可以重载,而事件不可重载
• 定义函数时,用户可以规定其作用范围,事件则不能规定
• 可以方便地在子对象中编写脚本时扩充或重载父对象的事件。而对于重载函数,用户必须重新定义该函数。如果要扩充函数,可以在子对象版本的函数中调用父对象版本的函数;
二、PowerBuilder 寻找、执行事件和函数
事件和函数的一个主要不同之处在于 PowerBuilder 如何寻找它们。PowerBuilder 按照函数和事件的名字和参数列表查找匹配的函数或事件。PowerBuilder 会认为相互兼容的数据类型也是匹配的(例如所有的数值类型数据)——这种匹配不一定是精确的。PowerBuilder 比较兼容类型,从中寻找最佳的匹配类型。
1、函数
1.1 未加限制的函数名
PowerBuilder 搜寻该函数,并执行与函数名和参数匹配的函数。搜寻的顺序为:
(1)全局外部函数
(2)全局函数
(3)对象成员函数和局部外部函数——如果该对象有父对象,PowerBuilder 通过继承关系向上查找匹配的函数原型
(4)系统函数
1.2 加限制的函数名
用户可以使用点操作符限制成员函数,以确保只有该对象的成员函数被找到,而不是一个具有相同名字的全局函数被找到。使用了限制,搜寻范围仅在该对象的家族内(相当于未加限制函数名搜寻顺序),例如:
dw_1.Update() w_employee.uf_process_list() This.uf_process_list()
当 PowerBuilder 在对象家族中查找匹配函数时,用户可以指明调用父类的成员函数,而不是子类的。
2、事件
当调用函数时,PowerBuilder 直到找到该函数并执行它时才停止搜索。与此不同,在子对象中的事件默认为是父对象的衍生物。PowerBuilder 在该对象的家族中直到到达继承关系树的根,或者找到重载了其祖先的该事件才结束查找。接下来从父类的事件响应脚本开始,到子对象的事件响应脚本,依次执行。
图 描述了事件和函数之间查找顺序的差别。
三、调用事件或函数
1、触发(triggering)和告知调用(posting)函数和事件
触发事件或函数指立刻执行一事件或函数,这时脚本可以使用该事件或函数的返回值。
告知调用事件或函数指把执行该事件或函数的指令加入到对象执行队列,等待依次被执行。大多数情况下是在当前执行的脚本被执行完后执行调用的事件或函数的。不过,要是另一个系统事件同时发生,被调用的事件或函数在执行队列中可能排在其他脚本之后。调用事件或函数的脚本不能使用它们的返回值,因为 POST 关键字标明调用者无法使用返回值,所以可以把 POST 的作用看做把函数或事件转换成语句。
在当前任务需要在状态信息检查或后续处理之前被完成时,POST 就必不可少了。由于没有返回值,使用 POST有如下限制:
• 不能使用告知调用函数或事件作为其他函数的参数
• 不能使用告知调用函数或事件作为表达式中的操作符
• 告知调用不能作为层叠调用(Cascaded sequence of calls)的最后一个调用
下面的语句会造成编译错误,因为它们都需要返回值:
IF POST IsNull() THEN ... w_1.uf_getresult(dw_1.POST GetBorderStyle(2))
为了向下兼容,TriggerEvent 和 PostEvent 仍然可以使用,但是不能向被调用的事件传递参数。要想传递参数,必须通过 PowerBuilder 的 Message 对象向事件传递数据。
2、静态对动态调用
PowerBuilder 提供三种方式调用函数,
函数类型 | 编译器测定类型 | 说明 |
全局或系统函数(global & system function) | 强测定类型(Strongly typed),函数在编译时必须存 | 函数存在并被直接调用,它们不是多态的,执行时不会被替 |
静态对象成员函数(STATIC 类型) | 强测定类型(Strongly typed),函数在编译时必须存在 | 函数是多态的,编译时对象成员函数必须存在,在运行时,若另一对象被实例化,则该对象的函数被执行 |
动态对象成员函数(DYNAMIC 类型) | 弱测定类型(Weakly typed),函数在编译时不一定存在 | 函数是多态的,被执行的函数在运行时决定 |
对于对象成员函数和事件,可以通过指定 STATIC 或 DYNAMIC 选择 PowerBuilder 何时寻找它们。用户不能动态调用全局或系统函数。DYNAMIC 关键字只适用于与对象联系的函数。
1. 静态调用
默认情况下,PowerBuilder 静态查找函数和事件,这意味着它在编译代码时按函数名和参数类型区分函数。匹配函数和事件必须在编译时存在。静态调用不能保证编译时识别的函数也是被执行的函数。
2. 动态调用
使用动态调用时,被调用的函数在编译时不必一定存在。用户告诉编译器:“相信我”——会有合适的事件或函数在运行时供使用。对于动态调用函数,PowerBuilder 具有很大的灵活性,允许调用父类中没有的子类的成员函数。但是动态调用增加了运行时产生错误的机会,特别在该函数不存在时。
3. 动态调用的缺点
因为动态调用在运行时决定,所以它比静态调用慢。如果需要使应用有最快的表现,应避免在设计时使用动态调用。
4. 过载(overloading)和重载(overriding)函数
在 PowerBuilder 中,过载指定义了多于一个具有相同名字、不同参数的函数。这些附加的函数可用于同一个对象中,也可用于该对象的子对象。PowerBuilder 在调用函数时,把参数与函数原型进行比较,以决定调用哪一个函数。重载指在某对象的子对象中定义一个函数,其函数名和参数与父对象中定义的函数一样。在子对象中,通常调用子对象版本的函数,而不是父对象的函数(除非使用作用域区分符::)。用户只能过载或重载对象成员函数。定义过载和重载函数与定义普通函数一样(例如,在窗口画板中使用菜单项 Declare-Window Funtions)。函数名称及其参数决定该函数是过载还是重载。
5. 过载函数中参数类型转换问题
如果程序员能够清楚地知道提供何种参数时调用哪一个版本的函数,那么这是一个良好的过载函数。
由于数值类型数据转换比较复杂,最好不定义依靠不同数值类型区别的过载函数。如果别人做的修改导致错误版本过载函数被调用,它对应用的影响会超乎用户的想象。
当 PowerBuilder 计算表达式时,它转换常量和变量的类型以保证处理的正常进行。
当 PowerBuilder 计算数值表达式时,将根据参数类型和操作符转换数据。例如,表达式 n/2 是 double 的,因为它包含了除号。
当 PowerBuilder 计算字符类型表达式时,如果包含 char 和 string 两种类型,char 型数据会转换为 string型。
在这种情况下,可以使用强制类型转换函数。这种转换可以保证表达式结果的类型和函数原型中定义的相匹配。例如,因为表达式 n/2 包含除法,数据类型为 double,假如要调用的函数需要 long 型,可以使用函数 Long 来保证与原型匹配,如下所示。
CalculateHalf( Long(n/2) )
6. 扩充和重载事件
在子类中编写事件脚本时,可以扩充或重载父类中的脚本。扩充指先执行父类的脚本,在子类运行时,子类的脚本是默认的。重载指略去父类的脚本,只执行子类的脚本。选择扩充或者重载,需要在脚本画板中选择 Compile-Extend Ancestor 或者 Compile-Override Ancestor,前者为默认选项。
7. 向函数和事件传递参数
向内置函数或用户定义函数和事件传递参数的方法有三种,如表 6.4 所示。
(1)传递对象
可以传递对象给函数或事件,在函数和事件中引用对象的属性或称原函数时,该对象必须存在。如果对象已被释放,此时调用引用它的函数会产生运行错误,无论是使用哪种传递参数的方法均如此。
(2)传递结构体
结构体作为参数传递与变量类似。
(3)传递数组
传递数组时,需要在说明函数或事件时在参数中加入方括号。
8. 返回值
全部 PowerScript 内置函数都有返回值,可以使用或忽略它们。使用返回值可以把它赋给具有合适类型的变量,或者在任何可以使用该类型的值的地方调用它们。如果使用告知调用函数,它的返回值无法被使用。
用户自定义函数和外部函数可以有也可以没有返回值。
大多数系统事件具有返回值。它们的返回值是一个 long 型整数——每个事件的具有某种意义的数字代码。在事件中,可以使用 RETURN 语句返回特定的返回代码。
当触发一事件时,值将返回调用脚本,可以在适当的地方使用它。当发送事件时,同样不能使用其返回值。
内置函数 Asc 用 String 作为参数,并返回字符串首字符的 ASCII 值。
string s1="Catran" int Test Test=Asc(s1)+32 // Test 现在值为 99 // C 的 ASCII 值为 67
SelectRow 函数需要列编号作为其第一个参数,GetRow 的返回值提供了该列的编号:
dw_1.SelectRow(dw_1.GetRow(), TRUE)
要忽略返回值,把调用函数作为一个单独的语句:
Beep(4) // Beep 返回一个值,但极少使用
9. 调用函数和事件的错误
使用动态调用函数时,不同情况产生不同的结果,甚至产生运行错误。表 6.5 描述了使用如下语句的出错情况分析。
语句 1:这一语句调用一事件,并忽略返回值。
object.EVENT DYNAMIC eventname()
语句 2:这一语句使用了返回值。
int li_int
li_int=object.EVENT DYNAMIC evnetname()
语句 3:这一语句使用 Any 类型接收返回值。
any la_any
la_any=object.EVENT DYNAMIC eventname()
动态调用函数与调用事件的规则类似,所不同的是,函数必须存在,否则就会发生 65 号错误:动态函数没找到;另外,事件可以在没有脚本的情况下存在,而函数在定义时必须包含脚本。如果函数存在并可以运行,而且没有返回值,若调用它的脚本需要函数返回值,则会发生 63 号错误。
当发生运行错误时,用户可以在 SystemError 事件中进行错误处理。不过用户不应允许应用继续运行——在 SystemError 事件中清理并终止应用。如果不处理错误,应用程序将自动终止运行。
函数参数是函数定义的一部分。所以如果参数不匹配(非精确匹配),则调用的完全是不同的函数,其结果如同函数不存在。如果动态调用函数的参数不匹配,调用失败,控件返回调用脚本,但不会产生出错信息。
动态调用函数和事件使应用容易存在潜在错误。避免这种情况最可靠的方法是使用静态方法调用函数和事件。这样,在设计和调试中可以确保调用的函数和事件是所需要的,而且具有正确的返回值。
3、调用函数和事件的语法
调用函数和事件的完整语法格式是:
{objectname.} {type} {calltype} {when} functionname({argumentlist})
其中 objectname 代表函数和事件所在对象,type 可以选择 EVENT 或 FUNCTION(默认)中的一个关键字,calltype 是关键字 DYANMIC 或 STATIC(默认)中的一个,when 代表 POST 或 TRIGGER(默认),functionname 是被调用的函数或事件名(大小写无关),argumentlist 是事件或函数的参数。
这个语法格式适用于调用 PowerScript 系统函数、用户定义函数和事件、系统事件。虽然在这个格式中有多个选项,但并非同时使用。
4、说明和调用外部函数
外部函数指使用 PowerScript 之外的语言编写的保存在动态链接库(DLL)中的函数,可以使用任何支持动态链接库的语言编写外部函数。
在使用动态链接库中的外部函数之前,必须先说明它。按照外部函数的可见范围可以把它分为两种类型:
Local 型和 Global 型。前者在限定的范围中可以使用,后者则在整个应用中都能被调用。说明外部函数的语法格式为:
[access] FUNCTION ReturnDataType name([ [REF] DataType1 arg1,...[REF]
DataType argn ])LIBRARY "libName" [ALIAS FOR "extName"]
[access] SUBROUTINE name([ [REF] DataType1 arg1,...[REF] DataType
argn ])LIBRARY "libName" [ALIAS FOR "extName"]
其中的参数 access 指明局部函数的使用权限,可以使用 PUBLIC,PRIVATE 和 PROTECTED 三个关键字。PUBLIC 说明该函数可以在应用的所有脚本中使用,PRIVATE 表示该函数只能在脚本所在对象中使用,PROTECTED 允许该函数在其所在对象及其继承对象中被调用;FUNCTION 和 SUBROUTINE 规定调用的类型,前者接收返回值,后者在无返回值或返回 VOID 时使用;ReturnDataType 是返回值的数据类型;name是在 DLL 中的函数名;REF 说明它后面的参数是被引用传递的;DataType arg 是在 DLL 中定义的参数类型和参数名;LIBRARY 关键字和 libName 指明了包含函数的动态链接库的文件名(libName 中不要包含 DOS路径),动态链接库所在目录必须满足以下四种情况中的一种:当前目录、Windows 目录、Windows 的 SYSTEM子目录、DOS 的 PATH 中的目录,这样以保证在应用运行时,外部动态链接库能够被应用找到;如果在 DLL或数据库中的函数名不能在 PowerScript 脚本中引用,ALLAS FOR "extName"可以建立 PowerScript 和外部函数之间的联系。
在说明外部函数时,应当使函数参数类型与外部函数定义的参数类型相匹配,请参见表 6.6 中外部函数的参数类型与 PowerBuilder 对应数据类型的对照规定。
PowerScript 是一个跨平台的设计工具,它不但支持 16 位 Windows 3.1,还支持 32 位的操作系统,如Windows95,Windows NT,Macintosh 和 UNIX(Solaris)。在 16 位和 32 位平台上,参数数据类型的规定是
有所不同的,所以在使用 16 位和 32 位的 DLL 时应该注意区别对待。
作为外部调用的函数在外部程序中说明时,必须使用 FAR PASCAL 创建该函数,并把它们连接在 DLL中。
5、把 DBMS 的存储过程说明为远程过程调用
可以使用点操作符(object.function)调用无结果集存储过程。可通过存储过程调用 Sybase,Oracle,Informix和其他 ODBC 数据库的数据库过程。远程过程调用支持 Oracle PL/SQL 中被定义为输入输出的表和参数。还
可以调用多义过程。下面是说明的语法格式,它适用于事务对象:
FUNCTION rtnDataType functionName([ [REF] dataType1 arg1,...,[REF]
dataTypen argn]) RPCFUNC [ALIAS FOR "spName"]
SUBROUTINE functionName([ [REF] dataType1 arg1,...,[REF] dataTypen argn ])
PRFUNC [ALIAS FOR "spName"]
其中,FUNCTION 和 SUBROUTINE 是指明过程有返回值,还是没有返回值或返回 VOID;rtnDataType
是返回值的数据类型;functionName 是在 PowerBuilder 中使用的过程名;关键字 REF 代表其后的参数为引用调用;dataType arg 是参数的数据类型和参数名;PRFUNC 表明本说明是 DBMS 存储过程,而不是 DLL 中的
外部函数;ALIAS FOR "spName"指出与 functionName 对应的 DBMS 中的存储过程名。
若函数没有返回值(例如,返回 Void),应指定声明为过程而非函数。通常远程调用的说明总是与事务对象相联系。可以作为局部外部函数说明它们,在说明局部外部函数对话框中有一个 Procedure 按钮,使用该按钮可以访问数据库的存储过程列表(如果数据库支持存储过程)。
以下举两个例子说明其用法。
【例 1】 本例为 GIVE_RAISE 存储过程的声明。它在事务对象的 User Object 画板中声明。声明占用了一行。
FUNCTION double GIVE_RAISE(ref double SALARY)
RPCFUNC ALIAS FOR "GIVE_RAISE_PROC"
下述代码在脚本中调用该函数。
double val=20000
dOUble rv
rv = SQLCA.give_raise(val)
【例 2】本例是存储过程 spm8 的声明。因为 PowerBuilder 与 DBMS 名称一致,故该声明不需要 ALIAS FOR短语。
FUNCTION integer SPM8(integer value) RPCFUNC
下述代码调用 spm8 的存储过程。
int mvresult
myresult = SQLCA.spm8(myresult)
IF SQLCA.sqlcode<>0 THEN
messagebox("Error",SQLCA.sqlerrtext)
END IF