Tivoli 服务台 6.0 开发工具包脚本程序设计指南
开发工具包允许用户把语句组合到一起成为例程,并把例程组合成为可重新使用的知识库(或模块)。此模块化特性降低维护大量源代码的难度。
本章包括理解和建立多知识库应用程序必需的基础。
关键字 ROUTINES 用来引入包括子例程或过程的任何知识库段。
在 KNOWLEDGEBASE 首部和 PRIVATE 关键字之间的公用段中,例程段包括过程和函数的说明,这些过程和函数在专用段实现。
在专用段,ROUTINES 关键字后跟完整的过程和函数实现。
下面给出一个实例:
KNOWLEDGEBASE Print; TYPES EmployeeRecord IS RECORD first_name, last_name: String; employee_ID: String; hireDate: Date; salary: Real; END; ROUTINES PrintEmployee(REF e: EmployeeRecord); PRIVATE ROUTINES PROCEDURE PrintString(VAL s: String) IS ACTIONS ... END; PROCEDURE PrintEmployee(REF e: EmployeeRecord) IS ACTIONS PrintString(e.first_name); PrintString(e.last_name); ... END;
每个开发工具包知识库文件包括两个主要段。
在下例中,有一个叫作 Statistics 的知识库(包括在文件 statisti.kb 中),另一个叫作 Application 的知识库(在文件 applicat.kb 中)使用它:
KNOWLEDGEBASE Statistics; ROUTINES PROCEDURE Regression; PROCEDURE TimeSeries; PROCEDURE ChiSquare; ... KNOWLEDGEBASE Application; ... PRIVATE USES Statistics; ...
通过在其 uses 段说明 Statistics,Application 中的例程能调用 Statistics 定义的 3 个过程。
USES 关键字引入 uses 段,必须把该段放在这些位置中的一处(或两处):
一个知识库可按需要使用多个其它知识库。必须在 uses 段列出每个被使用的知识库,并后跟分号:
KNOWLEDGEBASE User; ... PRIVATE USES Used1; Used2; ... Used99;
当知识库包含在知识库的专用段 USES 语句中时,则它是专用的。
当知识库为专用时,在整个专用段其公用变量和过程可用,以实现在公用段说明的例程。换句话说,知识库的专用实现可由另一知识库的公用例程组成。
两个知识库可无限制地互相专用,只要它们在专用段互相列出名称。即知识库 A 可使用知识库 B,反之亦然。
当知识库包含在的公用段 USES 语句中时,则公用地使用它。
当公用地使用知识库时,被使用的知识库的符号在整个知识库可用。
当使用中的知识库中的一些公用说明必需另一知识库中的符号,应该公用地使用该知识库。
这种情况的一个实例是,一个知识库中的记录类型说明使用在另一知识库中定义的记录类型。
当知识库 A 使用知识库 B 且 B 也使用 A 时,出现循环使用。在 PUBLIC 段,知识库不能互相循环使用。
避免这一限制的一个方法是,把两个知识库都需要的公共说明移到一个单独的知识库,该知识库自身不公用地使用任何部分。这种方法不总可行,因为把非常不同的函数的说明组合到一个知识库中可能没有意义。但是有时这种解决方案有效。
作为一个实例,假定用户创建一个程序,它跟踪关于员工和他们工作的部门的信息。用户想把与员工有关的大多数信息封装在名为“Employee”的知识库中,把与部门有关的信息封装在名为“Department”的知识库中。
但是,处理员工记录的公用例程有时需要部门记录作为参数,反之亦然。因此,需要两个知识库公用地互相使用。因为这行不通,必须改为如下例所示组织知识库:
KNOWLEDGEBASE EmplType; TYPES Employee IS RECORD ... END; ... END; KNOWLEDGEBASE DeptType; TYPES Department IS RECORD ... END; ... END; KNOWLEDGEBASE Employee; USES EmplType; DeptType; ROUTINES PROCEDURE AddEmployee (VAL lst: LIST OF Employee, REF dept: Department); ... PRIVATE ... END; KNOWLEDGEBASE Department; USES EmplType; DeptType; ROUTINES PROCEDURE AssignOffice (REF dept: Department, REF empl: Employee); ... PRIVATE END;
过程是开发工具包语句集合,它用名称引用的,并不给调用者返回值。
过程是嵌套在知识库中的程序块。像知识库自身一样,过程可包括常量、类型、变量和例程等段。在这些段中的标识符对过程是局部的,在过程外不可见。
每个过程有操作段,此段是可执行的开发工具包语句的位置,它确定过程执行的操作。
过程的一般形式如下所示:
PROCEDURE <procedure-name>(<formal-parameter-list>) IS CONSTANTS <constant-declarations> TYPES <type-declarations> VARIABLES <variable-declarations> ROUTINES <subroutine-implementations> ACTIONS <KML statement-list> END;
下例是一个简单的过程,其操作段包括两条开发工具包语句。当调用此过程时,执行操作段中的这两条语句:
PROCEDURE CopyrightMessage; ACTIONS WinWriteLN($DeskTop,'Copyright 1996 by'); WinWriteLN($DeskTop,'Software Artistry, Inc.'); END;
过程在嵌套的变量段包含局部变量,这经常很有用。局部变量是程序变量,其作用域局限在特定的代码程序块。作为一个规则,局部变量局限在子例程内。
下例创建一个名为 WriteTenLines 的过程。过程使用名为 i 的局部变量,在 FOR 循环中重复,并把 10 个字符串写到窗口:
PROCEDURE WriteTenLines; VARIABLES i: Integer; ACTIONS FOR i:=1 TO 10 DO WinWriteLn(myWindow,''); END; END;
进一步增强本实例,加入一个局部常量:
PROCEDURE WriteTenLines; CONSTANTS MAX_LINES IS 10; VARIABLES i: Integer; ACTIONS FOR i:=1 TO MAX_LINES DO WinWriteLn(myWindow,''); END; END;
经常需要创建局部例程,特别是如果用户要使用的变量只在程序的一部分出现。局部例程是在另一过程或函数内部说明的过程或函数。局部例程只能由包含它的过程或函数或与它一起被包括的其它局部例程调用。看下例:
PROCEDURE PrintEmployeeInfo(REF e: EmployeeRecord) IS ROUTINES PROCEDURE PrintDemographics IS ACTIONS PrintString(e.first_name); PrintString(e.last_name); PrintString(e.ssn); END; PROCEDURE PrintManages IS ACTIONS FOR e.manages DO PrintString(e.manages[$Current]); END; END; ACTIONS PrintDemographics; PrintManages; END;
在块结构化语言中的一个重要概念问题是作用域。在任何给定的程序块中,只有一定的标识符存在,即在作用域中。一般情况下,嵌套的程序块能"看见"在包围的作用域中说明的标识符。在包围的作用域中的对象或标识符仅对于该程序块是局部的。
考虑下例:
KNOWLEDGEBASE Scope; CONSTANTS MAX_EMPLOYEES IS 500; TYPES EmployeeRecord IS RECORD first_name, last_name: String; END; VARIABLES employeeList: List of EmployeeRecord; PRIVATE CONSTANTS ARRAY_SIZE IS 1000; VARIABLES employeeArray[ARRAY_SIZE]: ARRAY OF EmployeeRecord; ROUTINES PROCEDURE ProcTwo IS VARIABLES i: Integer; ACTIONS ... END;
PROCEDURE ProcOne IS VARIABLES i: String; j: Integer; ACTIONS ... END;
MAX_EMPLOYEES、EmployeeRecord 和 employeeList 在整个知识库可见,因为在最外层程序块中说明它们。这些元素出现在知识库的公用段,所以对使用此知识库的其它知识库它们也可见。
类似地,因为在最外层程序块的专用段说明 ARRAY_SIZE 和 employeeArray,它们在整个专用段可见。ProcTwo 和 ProcOne 都可引用这些标识符,但是因为它们在专用段,其它知识库不能访问它们。
ProcTwo 中的变量 i 只在 ProcTwo 中可见。ProcOne 中的变量 i 和 j 只在 ProcOne 中可见。ProcOne 中的 i 变量与 ProcTwo 中的 i 变量完全没有关系。
只有当包括变量的程序块执行时,变量值才存在。例如,只有在 ProcOne 执行期间,变量 i 和 j 才有值。
当调用过程时,经常需要把信息传送给过程。
下列步骤可完成信息传送:
PROCEDURE ComputePercent (VAL Dividend : INTERGER, VAL Divisor : INTEGER, REF Quotient : INTEGER) IS (*parameter to PROCEDURE ComputePercent*)
ACTIONS Quotient := Dividend/Divisor*100; END;
当调用过程时,传送的表达式数目等于为过程说明的参数数目。传送的表达式必须与指定的参数有相同的数据类型和参数类型。
VARIABLES (*Compute the percentage of cars of differing colors *) NumBrownCars : INTEGER; NumBlackCars : INTEGER; NumMixedCars : INTEGER; TotalCars : INTEGER; PercentBrownCars : INTEGER; PercentBlackCars : INTEGER; PercentMixedCars : INTEGER; ACTIONS (*Expressions passed to compute the percentage of cars of differing colors in a garage*) ComputePercent (NumBrownCars, TotalCars, PercentBrownCars); ComputePercent (NumBlackCars, TotalCars, PercentBlackCars); ComputePercent (NumMixedCars, TotalCars, PercentMixedCars); End;
在下一个实例中,注意一些参数采用值传送(VAL)而一些采用引用传送(REF)。
在 VAL 参数中,把值从调用者复制到被调用例程,被调用例程执行的操作不影响调用者的值。
REF 参数寻址调用者处理的同一存储器。这样,在被调用例程执行期间对参数值的更改立即影响调用者的值。
考虑下例:
ROUTINES PROCEDURE DistanceFormula(VAL x1, y1, x2, y2: Integer, REF distance: REAL) IS ACTIONS distance:=Sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); END;
PROCEDURE ComputeDistances IS VARIABLES distance: Real; ACTIONS DistanceFormula(1,1,9,9,distance); WinWriteLN(myWindow,'The distance is ' & distance); DistanceFormula(9,9,15,87,distance); WinWriteLN(myWindow,'The distance is ' & distance); END;
在上述实例中,说明了过程 DistanceFormula。
该过程有五个形式自变量:
当调用过程时,把传送的前四个整数值的拷贝发送给过程。但是对第五个参数,把调用者发送的实际变量传送给过程。
如果过程希望采用值传送参数,调用者可发送:
但是,如果过程希望采用引用传送参数,调用者只能发送变量。
过程把所有传送给它的参数当作变量。过程可:
对采用值传送的参数的更改不发送给调用者。
在前面的实例中,ComputeDistances 把四个整数自变量的字面数值发送给 DistanceFormula,因为相应的参数是用 VAL 关键字说明的。但是,ComputeDistances 只为第五个自变量传送一个变量,因为第五个参数是用 REF 关键字说明的。
注释:没有缺省参数类型。在过程说明中,用户必须为每个参数指定 VAL 或 REF。
除了给调用者返回一个值外,函数与过程一样。因为此原因,只能在赋值或表达式中使用函数。不能把函数当作语句使用。
函数可返回简单类型(如字符串、整数、实数、布尔值、日期和时间)、结构化类型和用户定义类型。
函数也可返回记录列表。
函数有下列一般形式:
FUNCTION <function-name>(<formal-parameter-list>): <Type> IS CONSTANTS <constant-declarations> TYPES <type-declarations> VARIABLES <variable-declarations> ROUTINES <subroutine-implementations> ACTIONS <KML statement-list> END;
下例更改 DistanceFormula,这样它是函数而不是过程:
ROUTINES FUNCTION DistanceFormula(VAL x1, y1, x2, y2: Integer): REAL IS ACTIONS Exit Sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); END;
PROCEDURE ComputeDistances IS VARIABLES distance: Real; ACTIONS distance:=DistanceFormula(1,1,9,9); WinWriteLN(myWindow,'The distance is ' & distance); distance:=DistanceFormula(9,9,15,87); WinWriteLN(myWindow,'The distance is ' & distance); END;
注:每个函数必须有显式的返回类型,它跟在参数列表后,用冒号与参数列表分开。
$Result 可用在函数体内,以访问该函数的当前返回值。用户可在函数体内设置或测试它。
考虑对 DistanceFormula 所作的下列更改:
FUNCTION DistanceFormula(VAL x1, y1, x2, y2: Integer): REAL IS ACTIONS $Result := Sqrt((x2-x1)*(x2-x1) + (y2-y1)* (y2-y1)); END;
在此实例中,用 $Result 设置函数返回值。
在函数体内可对 $Result 多次赋值。$Result 自动有为函数返回值说明的数据类型。
当在函数中使用 Exit 时,其使得:
当在函数内使用 Exit 语句时,可给该语句一个参数,指示要返回的值。
Exit (42);
其作用同于:
$Result := 42; Exit;
如果没有使用 Exit 语句而函数终止,返回 $Unknown 值。
Exit 组合了下列任务:
开发工具包外部例程机制提供开发工具包程序访问 C 或 C++ 例程的功能。
外部例程包括两部分:
从开发工具包 4.2 开始,开发工具包说明语法支持大多数接口需求,而不必对基础的开发工具包数据结构的深入理解。
要调用外部例程,开发工具包解释器必须有例程的说明,该说明指出:
下例显示外部例程说明语法。像任何开发工具包例程实现一样,这些说明只可出现在开发工具包知识库的专用段。
FUNCTION name [(VAL|REF parameter : type [, ...])] : type IS EXTERNAL dll_name [, entry_point][, linkage]; PROCEDURE name [(VAL|REF parameter : type [, ... ])] IS EXTERNAL dll_name [, entry_point][, linkage];
DLL 名称是常量字符串表达式,它标识外部例程所在的库。.dll 扩展名通常在每种平台上都不同,且一般从给定名称忽略。
入口点是程序或例程内的位置,执行可在该处开始。一个例程通常只有一个入口点。入口点可是下列二者之一:
字符串代表入口点的名称,整数代表其在库中的序数。
C++ 编译器通常使用被称作名称 mangling 的技术,它保证正确连接分立的已编译目标模块。
对于用 C++ 编写的外部函数,通常必须在 C++ 源代码中说明为外部“C”或通过其序数装载。
外部例程说明中的连接规范必须是下列符号之一:
注:如果调用的 DLL 不是用 Microsoft Visual C++ 编译的,开发工具包 DLL 挂起调出工具“采用引用传送”自变量会失败。
对于 OS/2 上的 IBM C++,协议是_Optlink._
对于 OS/2,协议是 _System。
如果从外部例程说明中忽略了连接规范,则连接缺省设置是 $STANDARD。
对所有 UNIX 操作系统,约定是编译特定的。如果用户的库以 C 或 C++ 编写,使用 $C。否则,使用 $System。
为了促进第三方库的使用,TSD 脚本提供了许多预定义类型名,这些类型名直接映射到简单 C/C++ 类型。
类型 SHORT IS INTEGER: INT(2) DEFAULT($ERROR); USHORT IS INTEGER: UINT(2) DEFAULT($ERROR); LONG IS INTEGER: INT(4) DEFAULT($ERROR); ULONG IS INTEGER: UINT(4) DEFAULT($ERROR); PSZ IS STRING : POINTER DEFAULT(0); FLOAT IS REAL : FLOAT(4) DEFAULT($ERROR); DOUBLE IS REAL : FLOAT(8) DEFAULT($ERROR);
文件 TSD Script.KB 中的系统知识库中提供类型 SHORT、USHORT、LONG、ULONG、PSZ、FLOAT 和 DOUBLE。
TSD 脚本把外部 DLL 类型映射到 C 和 C++ 中使用的最公共的低级数据类型。附加说明语法告诉 TSD 脚本解释器如何映射数据。
例如,SHORT 的说明指明该值应存为两字节整数。缺省说明指示,如果试图通过 SHORT 参数把 $Unknown 传送给外部例程,它引起运行时错误。
PSZ 说明指明该值应存为指针,应把 $Unknown 作为零(NULL)指针传送。
此实例显示用户如何说明 Microsoft Windows 提供的 ExitWindows 函数:
FUNCTION ExitWindows (VAL reserved: INTEGER, VAL returnCode: INTEGER ): BOOLEAN IS EXTERNAL `USER.EXE', `ExitWindows', $SYSTEM;
注:TSD 脚本“integer”是长(32 位)值,在 C 中“int”不总起作用。
像普通 TSD 脚本例程的参数一样,指定外部例程的参数采用下列方法之一传送:
外部例程的 VAL 参数很象非外部例程的 VAL 参数。
把值从调用者复制到被调用例程,被调用例程执行的操作不影响调用者的值。
对于采用 VAL 方法传送的字符串,外部例程也作为指针接受,指针指向把 TSD 脚本字符串复制到的临时存储器地址。
REF 参数与外部例程的 VAL 参数稍有不同。
当非外部例程接受 REF 参数时,它寻址存储器,该存储器与调用者拥有的相同。在被调用例程执行期间对参数值的任何更改立即影响调用者的值。
向外部例程传送的引用实际是复制入或复制出。差别微小,但在有些情况下,此行为是可检测到的。例如,从外部例程调度消息是此行为的实例。
数据类型 STRING(和别名如 PSZ)是个例外,当采用引用传送时,被指针传送给实际 TSD 脚本字符串值。
对于所有 REF 参数,都作为指针传送给外部引用 C 或 C++ 例程。TSD 脚本不支持 C++ 引用表示法。如果需要调用使用引用参数的 C++ 例程,可以写一个小包装函数。包装函数从 TSD 脚本获取指针,去除引用,再把它传送给需要的 C++ 函数。
因为 TSD 脚本不直接支持 ANSI C 和 C++ const 表示法,当 C/C++ 函数使用 const 指针自变量时,TSD 脚本程序员通常说明参数为 REF。
在 TSD 脚本和外部例程之间传送数据经常需要把数据翻译成另一种格式。在调用外部例程前,把调用者的值:
如果参数是 REF 参数,那么在调用者返回后,把临时区释放回调用者的存储器。
尽管使用简单类型可构造相当好的接口,但用户不可避免地要向外部例程传送聚合数据结构。
开发工具包 4.2 向 TSD 脚本增加了新语法,它有如何把 TSD 脚本数据映射到 C 或 C++ 数据结构的显式详细规范。以后把这种映射称为二进制压缩。
在记录说明中无论是已命名的类型说明还是字段规范,二进制压缩信息都作为类型规范的注释。两种情况下的总的语法如下所示。
TYPES type_name IS type : annotation ... ; type_name IS RECORD [ field_name : type ] [ : annotation ... ] ; ... END;
下列注释指定压缩 TSD 脚本数据的格式。它们都是互斥的。
注释 | 说明 |
INT(宽度) | INT 注释指定用指定宽度把字段压缩入本地整数格式。宽度的合法值是 1、2 与 4。对于任何可显式地转换成 INTEGER 的 TSD 脚本数据类型可作压缩为 INT。 |
UINT(宽度) | UINT 注释指定以指定宽度把该字段压缩入本地整数格式,作为无符号值。当把一个值从 TSD 脚本传送到外部例程时,INT 和 UINT 之间没有明显的差别,因为在两种情况下传送相同的位模式。但是,当把 1 字节或 2 字节值从外部例程传送回 TSD 脚本时,INT/UINT 的区别决定如何对该值进行符号扩展。宽度的合法值仍是 1、2 与 4。 |
NUMERIC(宽度) | 用给定的宽度把带 NUMERIC 注释的字段作为插入空格字符序列压缩。TSD 脚本实数、整数和可转换为整数的任何数据类型都可作为 NUMERIC 字段压缩。 |
FLOAT(宽度) | 使用指定的宽度把带 FLOAT 注释的字段作为 IEEE 浮点数压缩。有效的宽度是 4 和 8。可显式转换为 REAL 的任何 TSD 脚本类型可作为 FLOAT 压缩。 |
BCD(宽度) | 把带 BCD 注释的字段作为二进制编码十进制压缩。可显式转换为 REAL 的任何 TSD 脚本类型可作为 BCD 压缩。 |
CHAR(宽度) | 使用给定的宽度把带 CHAR 注释的字段作为简单字符数组压缩。可转换为 STRING 的任何 TSD 脚本类型可作为 CHAR 压缩。 |
ZSTRING(宽度) | 使用给定宽度把带 ZSTRING 注释的字段作为零终止(C 风格)字符串压缩。可转换为 STRING 的任何 TSD 脚本数据类型可作为 ZSTRING 压缩。 |
LSTRING(宽度) | 把带 LSTRING 注释的字段作为 Pascal 风格字符串压缩,首字节存储字符串的长度。可转换为 STRING 的任何 TSD 脚本类型可作为 LSTRING 压缩。 |
ASE_DATE | 把带 ASE_DATE 注释的字段作为 DATEREC struct 压缩。可显式转换为 DATE 的任何数据类型可作为 ASE_DATE 压缩。 |
BTRV_DATE | 把带 BTRV_DATE 注释的字段作为 Btrieve 风格日期压缩。可显式转换为 DATE 的任何数据类型可作为 BTRV_DATE 压缩。 |
CCYYMMDD | 把带 CCYYMMDD 注释的字段作为字符串压缩,包括世纪、年、月和日,把每项压缩进两字节。可显式转换为 DATE 的任何类型可作为 CCYYMMDD 压缩。 |
ASE_TIME | 把带 ASE_TIME 注释的字段作为 TIMEREC 压缩。可显式转换为 TIME 的任何数据类型可作为 ASE_TIME 压缩。 |
BTRV_TIME | 把带 BTRV_TIME 注释的字段作为 Btrieve 风格时间。可显式转换为 TIME 的任何数据类型可作为 BTRV_TIME 压缩。 |
HHMMSS | 把带 HHMMSS 注释的字段作为字符串压缩,包括小时、分钟和秒,把每项压缩进两字节。可显式转换为 TIME 的任何数据类型可作为 HHMMSS 压缩。 |
POINTER | 把带 POINTER 注释的字段作为指向其实际 TSD 脚本数据的 32 位指针压缩。TSD 脚本中的任何非聚合数据类型可作为 POINTER 压缩。 |
NOEXPORT | NOEXPORT 注释标出在外部二进制代表中根本没有的字段。可把任何类型的字段标为 NOEXPORT。 |
除了格式注释,可指定缺省值注释。缺省值注释向 TSD 脚本解释器说明,当不知道 TSD 脚本值时,如何填充二进制结构中的字段。缺省值注释的语法是:
DEFAULT( $ERROR|expression )
当指定 $ERROR 时,压缩未知值导致 TSD 脚本解释器显示错误信息并终止外部例程。
当不指定值注释时,这是缺省设置。
当指定表达式时,如果 TSD 脚本值未知,则求值该表达式并按照格式注释压缩其值。此特性用来把未知字符串映射为空。
在字段规范序列中,用户可提供压缩注释,而不需任何相应字段名或数据类型。这些注释指定二进制结构中的字段,在 TSD 脚本记录中不出现这些字段。因为在 TSD 脚本记录中不出现字段自身,出现这种现象。用户必须用该字段的值提供附加注释。
这种特殊注释的语法是
VALUE( expression )
每次压缩字段时,对给定的表达式求值,然后按照相应的格式注释压缩。
最后,出于字段对齐的目的,可用 FILL 注释在二进制结构中的字段间放任意数目的填充字节。
FILL 注释的语法是
FILL( 宽度 [,值 ] )
宽度指定字节数目,把包括给定值的那些字节压缩进二进制结构。尽管 FILL 注释可指定任意常量整数值,实际压缩的字节只包括最少 8 个有效位,因此 -128 到 255 代表有用值范围。缺省值是零。
没有显式的压缩注释的已命名类型和字段按照其基础类型的缺省值压缩。
下表指示内部非聚合 TSD 脚本数据类型的缺省情况。
类型 | 缺省压缩信息 | 缺省结果 |
布尔 | INT(1) | DEFAULT($ERROR) |
整型 | INT(4) | DEFAULT($ERROR) |
实型 | FLOAT(8) | DEFAULT($ERROR) |
字符串 | POINTER | DEFAULT(0) |
时间 | ASE_TIME | DEFAULT($ERROR) |
日期 | ASE_DATE | DEFAULT($ERROR) |
窗口、文件(句柄类型) | UINT(4) | DEFAULT($ERROR) |
Tivoli 服务台 6.0 开发工具包脚本程序设计指南