1. 数组
简述
数组几乎在所有的编程语言中都会涉及到,这里对概念不做详细解释。如果不明白,也可以简单的认为就是许多类型完全相同的变量。
定义
D DIMDATA S 4 DIM(3)
1. 首个“D”, 表示是D 行(如之前的手册中已提到:文件的定义为F 行,即首位为F;程序代码为C 行,即首位为 C)
2. DIMDATA , 表示数组的名字。数组中的第一条记录,就是DIMDATA(1),第二条记录,就是DIMDATA(2),依此类推。第N 条记录,就是DIMDATA(N) ,这个“N”,可以使用变量来表示。
3. S, 表示这是一个独立的变量,与其它定义的变量无关,通常都是这样定义的。(在结构体中会提到,结构体中可以包含数组;也就是说,如果这个数组的定义在结构体定义之下,且没有“S”,那么就表示这个数组是属于结构体的一个组成项)
4. “4 ”, 表示数组中的每条记录,都是 4 位长的字符;如果是数字,就是 13 2 (也用F4看)
5. DIM(3),表示这个数组共有三条记录。数组的定义,必须在定义时指定总记录条数 就最简单的赋值操作而言,可以认为定义这个 DIMDATA 数组,与直接定义三个 4 位长的字符变量DIMDATA1、DIMDATA2、DIMDATA3是等价的,只是定义数组可以少写几行代码;而且数组可以进行查询定位,而独立的变量就做不到这一点了。
初始化
数组的初始化,常使用操作码“MOVEA”。即将整个数组中,所有的记录,都赋值成为同样的一个值(通常是空,或0 ),如下,即表示将数组中所有记录都赋值为空
C MOVEA *BLANKS DIMDATA
使用方法
1. 数组中的记录,可以直接赋值,或被赋值;
C EVAL DIMDATA(1)=’TEST’
// 第一条记录赋值成为’TEST’
C EVAL DIMDATA(3)=DIMDATA(1)
//第三条记录赋值成为第一记录的值,即也为’TEST’
2. 数组的定位
数组也可以进行查询定位,使用 LOOKUP操作码。例如当前数组中,已赋值如下:
DIMDATA(1) = ‘0001’
DIMDATA(2) = ‘0002’
DIMDATA(3) = ‘0003’
C EVAL N=1
C '0002' LOOKUP DIMDATA(N) 54
首句N=1 ,表示从第一条记录开始查询;如果 N=2 ,即表示从第2 条记录开始查询;如果N 大于数组定义时的总记录数(如N=4 ),则下面的 LOOKUP语句会报错,跳出程序;
第二句,表示查询 数组 DIMDATA 中,内容为’0002’ 的,是第几条记录;其中,54 是EQ 指示器(最后面那个)。当找到记录时,*IN54 = ‘1’;当未找到记录时, *IN54=’0’。与操作码 chain, read的指示器含义相反;
在这个例子中,执行完 LOOKUP语后,*IN54=’1’, N = 2 (即数组 DIMDATA 中,
有内容为’0002’ 的记录,是第二条记录。
当数组中有多条内容相同的记录时,N 为顺序查询到的第一条记录
当数组中无相应记录时,*IN54=’0’, N = 初始值,此例中,即 N = 1
补充
在数组的使用中,还有一种在编译程序时,给数组赋初始值的定义方法,如下:
D DIMTEXT S 3 DIM(10) CTDATA PERRCD(2)
在keywords 这个地方,使用了DIM ,CTDATA ,PERRCD 三个关键字
DIM (10) 指DIMTEXT 这个数组共含有10 条记录
CTDATA 表明数组DIMTEXT 是一个编译时赋初值的数组。
PERRCD (2) 表明数组DIMTEXT 的初始值使用底部数据,每行包含 2 条记录
如底部为:
C SETON LR
C RETURN
**
123456
789ABC
DEFGHI
****************** End of data ****************************************
1. 底部数据的定义,要顶头写(在 RPGLE中,Shift+F7 屏幕左移, Shift+F8 屏幕右移;之后按两下 F11,可回到正常编辑的画面),位置可对比上面的C 行。
2. 首行的“**”,是标识以下为数据项,必须要有。
3. *****End of data********,是系统自动给出的结束行。
4. 如上述定义之后,那么当编译了这个程序后,程序会将DIMTEXT 初始化为:
DIMTEXT(1 ) = ‘123’
DIMTEXT(2 ) = ‘456’
DIMTEXT(3 ) = ‘789’
DIMTEXT(4 ) = ‘ABC’
DIMTEXT(5 ) = ‘DEF’
DIMTEXT(6 ) = ‘GHI’
DIMTEXT(7 ),DIMTEXT(8 ),DIMTEXT(9 ),DIMTEXT(10)都为空。
2. 数据域
概述
数据域(*DTAARA),也叫数据区,是系统目标中的一种类型。当建立了这种目标后,可以用来存入数据,以便任何程序进行读取和修改。数据域的典型用途如下:
· 提供用于几个程序中的常数字段,易于共享和修改。如:标题、说明等。
· 在一个作业中提供一个传递信息的区域。
· 在一个作业中提供一个字段作为控制参数,以便容易地得到修改。
一个数据区可以由一个用户锁住。这样就避免其它用户同时操作。
数据区要先生成然后才能使用。一个数据区可以生成为:
最长为2000字符的字符串。
根据数据区是仅用在CL过程或程序或也用在其它高级语言过程或程序,使用不同属性的十进制值。对CL过程或程序,最多15位整数9位小数,但总共不能多于15位。对其它语言,最多15位整数9位小数,但总共不能多于24位。
逻辑值‘0’或‘1’。
在生成数据区时,可规定初值。如果没规定,则用下列值:
十进制用零,字符用空格,逻辑值用‘0’。
如何建立数据区(DATAARA)
【注】下面例子只建立*CHAR型的数据区,其它类型的可以自己练习
在系统命令行输入CRTDTAARA,按F4,进入”Create data area(CRTDTAARA)”画面,参数如下:
(a) Data area:数据区的名字,这里假设为MYDTAARA
(b) Library: 存放数据区的库,默认值是*CURLIB,即当前库,这里我们输入MYLIBXX;
(c) Type: 数据区的类型
*DEC 数值型
*CHAR字符型
*LGL逻辑型
*DDM分布式数据管理
这里我们选择*CHAR
(d) Length: 数据区的长度,这里假设是20,当然你可以根据自己的需要输入对应的长度值
(e) Decimal positions: 指小数位位数,一般是针对*DEC型的才需要设置;
(f) Initial value: 数据区初始值,可填可不填,我们这里选择不填,在程序里进行更改;
(g) Text : 注释
整个命令请参考如下
CRTDTAARA DTAARA(MYLIBXX/MYDTAARA) TYPE(*CHAR) LEN(20)
TEXT(‘建立属于自己的数据区’)
如何修改数据区
(2)在RPG里面如何操作数据区,假设该源码文件名为ODTAARA,存放在MYLIBXX/SRCFILE下:
ODTAARA
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++++++++++++++++++++++
*************** Beginning of data *************************************
D#DTA DS 20
D CURDAT 1 8
D CURTME 11 16
C *DTAARA DEFINE MYDTAARA #DTA
C *LOCK IN #DTA
C TIME DATTME 14 0
C MOVE DATTME CURDAT
C MOVEL DATTME CURTME
C OUT #DTA
C UNLOCK #DTA
C
C SETON LR
****************** End of data ****************************************
1. 第一行#DTA是字段名,DS表示#DTA是个字段,20表示字段#DTA的长度,整行的意思是定义一个长度20的字段#DTA;
2. 第二行第三行进一步详细定义字段#DTA的内部结构
3. 第四行的DEFINE表示定义,一般用来定义字段和数据区,*DTAARA是定义数据区格式时必用的参数,整行的意思是把数据区MYDTAARA的格式按照字段#DTA定义,以后对字段#DTA进行操作就等于对数据区MYDTAARA进行操作。
4. 第五行的IN用来读数据区,*LOCK表示操作时把数据区锁住,一般只有对数据区进行更新操作才需要锁住,如果只是读数据区,则不用该参数;
5. 第六行的TIME是取系统当前日期和时间,整行的意思是把系统当前日期和时间赋给一个14位长0位小数的数值型变量DTATME中,由于系统当前的日期和时间是按照“hhmmssMMDDYYYY”存放,所以刚好也是14位;
6. 第七行意思是把变量DATTME右移给字段CURDAT,所以CURDAT的值刚好是日期“hhmmss”;
7. 第九行的OUT表示对数据区进行写操作,记住factor处是我们程序开始定义的大字段#DTA;
8. 第十行的UNLOCK表示解锁,因为我们在第五行使用了参数*Lock;
命令行CALL ODTAARA,数据区已经成功更改数据;
使用命令DSPDTAARA MYDTAARA就可以查看数据区内容了
如何使用数据区
DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords++++++++++++++++++++++++
*************** Beginning of data ************************************
D#DTA DS 20
D CURDAT 1 8
D CURTME 11 16
C
C
C *DTAARA DEFINE MYDTAARA #DTA
C *LOCK IN #DTA
C CURDAT DSPLY
C CURTME DSPLY
C* OUT #DTA
C UNLOCK #DTA
C
C SETON LR
****************** End of data ***************************************
3. 数据队列
数据队列的程序通讯
数据队列(*DTAQ)是系统目标中的一种类型,当建立了这种目标后,一个程序可以发送数据给它,另一个程序再从中接收数据,从而达到程序之间的数据通讯。
数据队列的优点
数据队列是两个作业之间进行异步通讯的最快方法。相对数据库文件、消息队列或数据域而言,它需要较少的额外开销。
多个作业可以向相同的数据队列送数据和取数据,而数据队列的先进先出、后进先出或关键字顺序排列属性,能够保证数据送取的正确性。
在任何高级语言程序中,通过调用系统提供的程序,就可以对数据队列进行操作,而且操作方法灵活方便。数据队列的操作和使用包括两类:第一类使用 CL 命令;第二类调用系统程序。
CL 命令:
CRTDTAQ 建立数据队列
DLTDTAQ 删除数据队列
WRKDTAQ 工作数据队列
系统程序:
QSNDDTAQ 发送数据队列
QRCVDTAQ 接收数据队列
QCLRDTAQ 清除数据队列
QMHQRDQD 检索数据队列
数据队列的发送
需要将数据发送给数据队列,只要在程序中调用 QSNDDTAQ 。在 CL 程序中,调用的格式如下:
CALL PGM(QSNDDTAQ) PARM(&QNAME &LIB +
&FLDLEN &FIELD &KEYLEN &KEY)
&QNAME: 是长度为10的字符型,它命名了数据队列,如:
IN_Q。
&LIB: 是长度为10的字符型,它命名了数据队列所在的
库,如:*LIBL。
&FLDLEN: 是长度为5的数字型,它规定了发送给数据队列的
字符数,如:100。
&FIELD: 是长度为&FLDLEN的字符型,它包含了具体发送
给数据队列的数据。
&KEYLEN: 是长度为3的数字型,它说明了传送给数据队列的
关键字长度,如:6。
&KEY: 是长度为&KEYLEN的字符型,它包含了传送给
数据队列的关键字数据。
注:后两个参数可以自选,如果说明了一个,则必须说明另一个。
数据队列的接收
需要从数据队列中接收数据,只要在程序中调用QRCVDTAQ 。在 CL 程序中,调用的格式如下:
CALL PGM(QRCVDTAQ) PARM(&QNAME &LIB &FLDLEN +
&FIELD &WAIT &ORDER KEYLEN &KEY &SNDRLEN &SNDR)
&QNAME: 是长度为10的字符型,它命名了数据队列。如:
OUT_Q。
&LIB: 是长度为10的字符型,它命名了数据队列所在的
库。如:*LIBL。
&FLDLEN: 是长度为5的数字型,它规定了发送给数据队列
的字符数。
&FIELD: 是长度为&FLDLEN的字符型,它包含了从数据
队列中接收到的具体数据。
&WAIT: 是长度为5的数字型,它说明了等待接收数据的
时间。负数表示无限制的等待;零表示不等待;
正数示要等待的秒数,最大值是9999。这个参数
只有在数据队列中无满足条件的数据时, 才起作 用。
&ORDER: 是长度为2的字符型,它说明了按关键字接收数
据的条件。可用的字符值是:GT、LT、EQ、
GE、LE。
&KEYLEN: 是长度为3的数字型,它说明了接收数据队列的关
键字长度。
&KEY: 是长度为&KEYLEN的字符型,它标识了用于从
数据队列中接收数据的关键字变量。
&SNDRLEN:是长度为3的数字型,它规定了发送者标识的长
度。
&SNDR: 是长度为&SNDRLEN 的字符型,它包含了发送
者标识的数据。
注:后三个参数可以任选,但是&ORDER、&KEYLEN和&KEY 必须同时说明。
数据队列的清除
需要从数据队列中清除数据,只要在程序中调用 QCLRDTAQ在 CL 程序中,调用的格式如下:
CALL PGM(QCLRDTAQ) PARMM(&QNAME &LIB)
数据队列的检索
需要检索一个数据队列的描述项,只要在程序中调用 QMHQRDQD。在 CL 程序中,调用的格式如下:
CALL PGM(QMHQRDQD) PARM(&RCVR &RCVRLEN +
&FORMAT &DQNAME)
&RCVR: 是长度为&RCVRLEN的字符型,它标识了含有
数据队列性的变量。
&RCVRLEN: 是长度为4的数字型,它说明了&RCVR长度。
&FORMAT: 是长度为8的字符型,它定义了接收模板的格
式。
&DQNAME: 是长度为20的字符型,它标识了数据队列和所
在库,前十个字符是队列名字,后十个字符是
库名。
程序举例
******CRT_DTAQ.RPGLE******
F*----从学生信息表读取记录到数据队列------------------------------------*
F*----用到的学生信息表--------------------------------------------------*
Fstudent1 IF E K DISK
F*------------------------------------------------------------------------*
D*-----声明数据队列-----------------------------------------------------*
D
D* QNAME S 10 INZ('STUDENTQUE')
D* LIB S 10 INZ('DESIGN2ND')
D* FLDLEN S 5 0 INZ(150)
D* FIELD S 10 INZ('*BLANK')
D*------------------------------------------------------------------------*
D*-----定义结构体与学生信息表类型一致用来赋值给数据字段-----------------*
D#STU_DATA DS 150
DSSTU_ID 1 4A
DSSTU_NME 6 55A
DSSTU_ADD 56 105A
DSSTU_BTD 110 119D
DSSTU_TEL 120 130A
DSSTU_SEX 132 133A
DSSTU_AGE 135 137A
DSSTU_FLG 139 140A
D*------------------------------------------------------------------------*
D
D
C*--------------程序从这里开始------------------------------------------*
C *LOVAL SETLL STU
C DOW 1=1
C READ STU 1617
C IF *IN17='0'
C
C MOVEL STU_ID SSTU_ID
C* SSTU_ID DSPLY
C MOVEL STU_NME SSTU_NME
C* SSTU_NME DSPLY
C MOVEL STU_ADD SSTU_ADD
C* SSTU_ADD DSPLY
C MOVEL STU_BTD SSTU_BTD
C* SSTU_BTD DSPLY
C MOVEL STU_TEL SSTU_TEL
C MOVEL STU_SEX SSTU_SEX
C MOVEL STU_AGE SSTU_AGE
C MOVEL STU_FLG SSTU_FLG
C ELSE
C LEAVE
C ENDIF
C
C CALL 'QSNDDTAQ'
C PARM 'STUDENTQUE' QNAME 10
C PARM 'DESIGN2ND' LIB 10
C PARM 150 FLDLEN 5 0
C PARM #STU_DATA FIELD 150
C
C ENDDO
C SETON LR
C RETURN
C*------------------------------------------------------------------------*
******* RCV_DTAQ.RPGLE******
F*----------从数据队列接受记录写入学生信息表2--------------------------*
F*--------需要的文件----------------------------------------------------*
FSTUDENT2 UF A E K DISK
F*------------------------------------------------------------------------*
D*----------定义的数据结构,与学生信息表中各字段对应--------------------*
D#STU_DATA DS 150
DSSTU_ID 1 4A
DSSTU_NME 6 55A
DSSTU_ADD 56 105A
DSSTU_BTD 110 119D
DSSTU_TEL 120 130A
DSSTU_SEX 132 133A
DSSTU_AGE 135 137A
DSSTU_FLG 139 140A
D*------------------------------------------------------------------------*
C*-----程序由此开始-----------------------------------------------------*
C DOW 1=1
C
C CALL 'QRCVDTAQ'
C PARM 'STUDENTQUE' QNAME 10
C PARM 'DESIGN2ND' LIB 10
C PARM 0 FLDLEN 5 0
C PARM FIELD 150
C PARM 5 WAIT 5 0
C
C EVAL #STU_DATA=FIELD
C
C IF FLDLEN=0
C LEAVE
C ELSE
C MOVEL SSTU_ID STU_ID2
C MOVEL SSTU_NME STU_NME2
C MOVEL SSTU_ADD STU_ADD2
C MOVEL SSTU_BTD STU_BTD2
C MOVEL SSTU_TEL STU_TEL2
C MOVEL SSTU_SEX STU_SEX2
C MOVEL SSTU_AGE STU_AGE2
C MOVEL SSTU_FLG STU_FLG2
C WRITE STU2
C ENDIF
C ENDDO
C SETON LR
C RETURN
C*------------------------------------------------------------------------*
***** STUDENT1.PF******
A*----学生信息表--------------------------------------------------------*
A*-----记录格式名-------------------------------------------------------*
A R STU
A*------------------------------------------------------------------------*
A*-------各个字段-------------------------------------------------------*
A STU_ID 4P 0 COLHDG(' 学号 ')
A TEXT(' 学生编号 ')
A STU_NME 25O COLHDG('姓名')
A TEXT('学生名称 ')
A STU_ADD 25O COLHDG(' 住址 ')
A TEXT(' 学生住址')
A STU_BTD L COLHDG('出生年月')
A TEXT('学生生日')
A STU_TEL 11A COLHDG(' 电话 ')
A TEXT('学生电话 ')
A STU_SEX 2A COLHDG(' 性别 ')
A TEXT('学生性别')
A STU_AGE 3P 0 COLHDG(' 年龄 ')
A TEXT('学生年龄')
A STU_FLG 2A COLHDG(' 备注 ')
A TEXT(' 学生信息备用 ')
A*------------------------------------------------------------------------*
A*--------键值----------------------------------------------------------*
A K STU_ID
A*------------------------------------------------------------------------*
***** STUDENT2*******
A*-----学生信息表-------------------------------------------------------*
A*------记录格式名------------------------------------------------------*
A R STU2
A*------------------------------------------------------------------------*
A*-------各个字段-------------------------------------------------------*
A STU_ID2 4P 0 COLHDG(' 学号 ')
A TEXT(' 学生编号 ')
A STU_NME2 25O COLHDG('姓名')
A TEXT('学生名称 ')
A STU_ADD2 25O COLHDG(' 住址 ')
A TEXT(' 学生住址')
A STU_BTD2 L COLHDG('出生年月')
A TEXT('学生生日')
A STU_TEL2 11A COLHDG(' 电话 ')
A TEXT('学生电话 ')
A STU_SEX2 2A COLHDG(' 性别 ')
A TEXT('学生性别')
A STU_AGE2 3P 0 COLHDG(' 年龄 ')
A TEXT('学生年龄')
A STU_FLG2 2A COLHDG(' 备注 ')
A TEXT(' 学生信息备用 ')
A*------------------------------------------------------------------------*
A*--------键值----------------------------------------------------------*
A K STU_ID2
A*------------------------------------------------------------------------*
4. 消息队列
1 消息
消息机制是AS/400最重要的通信手段。无论是工作站之间的通信,工作站与系统程序或应用程序之间的通信都是通过消息机制来实现的。在AS/400计算机系统中,发送的消息总是被送往消息队列中等候处理,直到用户处理完毕将消息删除。有了消息队列,即使用户不在机器上工作,消息也不会丢失,也不必对到来的消息做即刻处理。
本章介绍了信息队列等有关概念和怎样发送、查看、删除和答复信息。
通过本章学习,要求掌握发送,查看、删除、答复消息的方法,理解信息队列的概念和作用,并能够指定其传送模式。
1.1 Types of Messages
A message is a communication sent from a person or program to another person or program.
There are two types of message:
Informational -Not requires Reply
◇Manufacturing program completed successfully.
◇System shutdown at 9:00 PM.
Inquiry -Requires Reply
◇Are you finished with the report?
◇Do you have Licensed Program tape?
◇Verify alignment on device PRT01. (I C G N R)
名词解释:
Types of message:
Information:
These messages do not require a reply. The AS/400 system knows that an informational message was sent by the informational identifier(*INFO) you specify when you send the message.
Inquiry:
An inquiry message requires a reply. It may also contain information. The AS/400 system knows that an inquiry message was sent by the inquiry identifier(*INQ) you specify when you send the message.
在AS/400系统上,工作站用户之间,系统操作员和工作站用户之间,程序和工作站用户之间都是用消息进行通信的。
作为系统操作员,经常发送两种类型的消息:
信息型(Informational): 这类消息是不需要回答的。发送方只是把一定的信息告诉对方。在发送此类消息时,应设定类型标志符为*INFO。
询问型(Inquiry): 此类消息是需要接受方回答的,发送方根据收到的应答再决定下一步的活动。在询问型消息中,也可以携带有信息型消息。询问型消息的标志符为*INQ。
发送消息的每个命令都有参数(MSGTYPE)来控制消息的类型。默认情况下,发送的是信息型(*INFO)消息。
1.2 Where Messages Come From
|
Figure 3-1. Where Messages Come Form |
Messages can come from:
Other users (including the system operator)
System programs
Application programs
用户之间、系统操作员与用户之间、应用程序和系统系统程序与用户之间都是用消息进行通信的。发送到用户的消息可能来自于:其他用户(包括系统操作员),系统程序,应用程序。如图所示来自系统操作员、系统程序及应用程序的信息型消息;来自其他工作站的询问型消息。
1.3 What Is a Message Queue
|
Figure 3-2. What is a Message Queue? |
Message are always sent to message queue.
A message queue is similar to a mailbox.
消息不是直接发送给用户或终端的,而是发到用户或终端的信息队列里。消息队列是一种类型的对象,类型标识符为*MSGQ。信息队列的作用就如邮箱一样。邮箱先保存邮件直到收信人有空领取。消息队列的作用也一样,也许接受方无法立即读取消息,所以消息先发到消息队列里保存起来直到被读取。
1.4 How Messages Queue Are Created
|
Figure 3-3. How Message Queues Are Created |
A message queue can be created whenever:
1 A workstation (display) is described (device description is created)
2 A user (profile) is described (created)
3 IBM supplied
4 using CRTMSGQ command
1. A workstation (display) is described (device description is created)
A workstation message queue is automatically described to the AS/400 system at the same time a workstation (or display) is described to the system with the Create Device Description command. A workstation message queue is created for each display configured on the AS/400 system. The message queue name is the same as the display name.
2. A user (profile) is described (created)
A user message queue can be specified when a user profile is created with the Create User Profile command. A user message queue exists for each user profile. The name of the user message queue is the same as the user profile name unless it is specified differently. A user message queue is created the first time a user signs on the system.
3. IBM supplied
The system operator message queue (QSYSOPR) is supplied as part of OS/400. When the system is configured the first time, QSYSOPR is created.
4. using CRTMSGQ command
Message queues can be created by system implementers with the Create Message Queue (CRTMSGQ) command.
消息队列可通过下面的途径创建:
1.随终端的描述文件一起生成 当建立设备描述文件时,系统会自动为终端生成一个与终端同名的消息队列。
2.随用户描述文件一起生成 当建立用户描述文件(UserProfile),指定用户的消息队列,系统会自动为用户配置一个消息队列。若非特别指明,此消息队列与用户同名。指定的用户消息队列在用户第一次登录系统时被创建。
3.随操作系统OS/400一起提供 系统操作员消息队列QSYSOPR是在系统初次配置时由系统自动创建的。
4.用CRTMSGQ命令创建 用CRTMSGQ命令创建的消息队列不与任何指定用户或工作站相关,它是用来满足应用需求的。例如,图所示的ARDEPT队列作为艺术部门保留消息用。
1.5 Sending A Message
Sending an informational or inquiry message:
|
Figure 3-4. Sending an Informational or Inquiry message |
Sending and Displaying Messages:
|
Figure 3-5. Sending and Displaying Messages |
SNDMSG Using SNDMSG command to Send an informational or inquiry message:
|
Figure 3-6. SNDMSG |
重要参数解释:
TOUSR: 指定发送给哪个用户
TOMSGQ: 指定发送给哪个消息队列
MSGTYPE: 指定发送消息的类型。*INFO为信息型,*INQ为询问型。
RPYMSGQ: 若发送询问型消息,则此参数指定接收回答信息的消息队列。
任何用户都可以用SNDMSG命令来发送信息型或询问型信息给:
◇ 一个或多个用户消息队列
◇ 一个或多个工作站消息队列
◇ 所有当前活动的用户
◇ 系统操作员消息队列(QSYSOPR)
◇系统历史日志(QHST),QHST包含所有系统活动的信息,如作业的运行及中止、设备状态、错误检测等.
在用SNDMSG命令时必须指定TOUSR或TOMSGQ参数,但不可两个参数同时指定。信息型消息(*INFO)一次最多可发到50个消息队列里。询问型消息(*INQ)只可一次送给一个用户或终端。RPYMSGQ参数用来指定应答消息被送的队列。默认下,其应答信息发回到发送方的工作站消息队列里。
用DSPMSG命令显示消息,参数为MSGQ(*WRKUSR)时,先显示工作站消息队列中的消息,再按回车键时才显示用户的消息,若工作站消息队列中没有消息,则直接显示用户消息。参数为MSGQ(*WRKSTN)时,仅显示工作站消息队列中的消息。在参数MSGQ中可指定想查看的消息队列名。
用DSPMSG命令去查看消息队列时,对需要回答的消息在应答线上输入回答信息,消息处理完后,可用相应的选项(Option)或功能健将其删除。但只有获得该消息队列的作业才允许做消息删除操作。消息队列任何时候只能被分配给一个作业。
1.6 Sending a Break Message
|
Figure 3-7. Sending a Break Message Messages |
A break message temporarily interrupts one or more user's current jobs and displays the message directly on their displays.
SNDBRKMSG
|
Figure 3-8. SNDBRKMSG |
用SNDBRKMSG命令来发送中断消息。特别注意的是:参数TOMSGQ中的值*ALLWS表示向所有工作站消息队列发送此消息。
中断消息会打断用户的当前工作并直接显示在屏幕上。例如,系统操作员可以通过中断消息来告知其他用户一些重要消息(如系统将在10分钟后重启)。因为中断消息会打断用户的当前工作,所以只当有重要消息发送时才可使用。
用SNDBRKMSG命令可向一个或所有的工作站消息队列(*ALLWS)发送信息型或询问型的中断消息,但不能发到用户消息队列里。在有用户登录的工作站上,消息直接显示在屏幕上;若当前没有用户登录,消息会先保存在消息队列中,一旦有用户登录会自动显示在屏幕上。
1.7 How Messages Are Displayed
|
Figure 3-9. How Messages Are Displayed |
消息显示方式:
1.用户消息队列和工作站消息队列可以通过DSPMSG或WRKMSG命令或OA采单的选项3查看。
2.中断消息会打断用户当前作业直接显示在屏幕上。消息可以是信息型或询问型。它常常是系统操作员或其它工作站用户所发送。
3.消息行是系统或应用程序与您进行通信的地方。消息行接收的消息告诉你关于作业或系统状态及用户错误,或需要你响应。为了获得附加的消息,可以将光标移到包含消息的行上,按帮助键或F1
1.8 Removing Messages
Assistance Levels
|
Figure 3-12. Assistance Levels |
Assistant level决定用户与系统交流时所得到的帮助信息的多少。可通过F21键来选择assistance level为*BASIC或*INTERMED,但并不是所有界面下F21键都是激活的。
以下命令支持F21键:
DSPMSG - Display Message
WRKMSG - Work with Message
WRKUSRJOB - Work with User Jobs
WRKSPLF - Work with Spooled Files
WRKWTR - Work with Writers
WRKCFGSTS - Work with Configuration Status
WRKUSRPRF - Work with User Profiles
*BASIC Assistance Level
|
Figure 3-10. WRKMSG |
From the Work with Messages (WRKMSG) display shown on the visual, you can:
1. Remove one message at a time:option 4
2. Remove all messages not needing a reply:F16
*INTERMED Assistance Level
|
Figure 3-11. DSPMSG |
You will see the Display Messages display when you run the WRKMSG or DSPMSG command. You can:
1. Remove one message at a time:F11
2. Remove all except unanswered messages:F16
消息会一直保留在消息队列里直到被删除。如果积累太多消息,会占用大量系统存贮。所以用户应即时删除无用的消息。
在*BASIC Assistance Level下:
进入WRKMSG的界面后,可用以下方法删除消息:
1.删除某条特定的消息
在所要删除的消息前按选项4再回车。
2.删除所有信息型消息
按F16键。
在*INRTERMED Assistance Level下:
运行WRKMSG或DSPMSG命令都会出现Display Message的界面:
1.删除一条消息
把光标置于所要删除的消息上,按F11.
2.删除所有消息(除了未答复消息)
按F16键。
1.9 Message Queue Modes
|
Figure 3-13. Message Queue Modes |
Each message queue has a delivery mode that describes how you will be notified of messages.
There are four different delivery modes:
*BREAK
*BREAK
interrupts your work and the message is shown on the display.
*NOTIFY
for an interactive job, lets you know when a message arrives on your message queue by turning on your Message Waiting Light, sounding your display station alarm, or both. ( This is the way you normally will receive messages at your workstation.)
*HOLD
stops the system from notifying you that you have a message on your message queue. The messages are held in the message queue until a user or program requests them.
*DFT
answers any messages requiring a reply with the default reply set up for the message. Information-only messages are ignored. The message is not put on your message queue unless you are the system operator.
消息到达消息队列后,怎样处理这条消息呢,是马上中断用户的工作,在显示屏上显示该消息;还是提示一下用户,但不中断用户的工作;还是不做任何处理。这是由消息队列的一个属性:交付模式(Delivery Mode)来决定的。
消息队列的发送模式有:
*BREAK -打断用户当前工作,消息直接显示在屏幕上。
*NOTIFY -一旦消息队列接收到消息则在用户屏幕上设置相应的标志或打开工作站上的消息等待灯或发出相应的警告声。(用户一般使用这种模式来接收消息。)
*HOLD -接收到消息后不作任何提示,而是将其保存在消息队列里等待用户查看。
*DFT -对于接收到的询问式消息自动以该消息的缺省值回答,对接收到的信息式消息则忽略。除系统操作员外其他用户的消息在这种模式下不被放入消息队列。
当设备描述文件、用户描述文件或用户的消息队列建立时,相应的消息队列的发送模式就在里面设定。QSYSOPR消息队列的发送模式默认为*NOTIFY.可用CHGMSGQ命令来改变发送模式。
QSYSOPR的发送模式应设为*BREAK,否则,一些发到QSYSOPR的重要的系统信息可能会被忽略。
1.10 User and Workstation Messages Queues
|
Figure 3-14. User and Workstation Meaages Queues |
当用户在工作站上登录时创建一个交互式作业(interactive job),用户消息队列和工作站消息队列自动被分配给该交互式作业。工作站消息队列被设置成*NOTIFY模式;用户消息队列也被设置成*NOTIFY模式,但用户可以修改。两个消息队列中的消息被设置成新消息。只有被分配消息队列的作业可以从队列中删除消息,当拥有足够权限时,其他用户也可以查看或者应答消息队列中的消息。当用户从工作站上退出时,用户消息队列和工作站消息队列自动与该作业分离,消息队列的发送模式均被设置成*HOLD模式,任何发送的消息队列中的消息被自动保存起来。
1.11 Controlling Message Delivery
|
Figure 3-15. Controlling Message Delivery |
SNDMSG -Delivers the message according to the message queue delivery mode
SNDBRKMSG -Interrupts the work station job and displays the message
每一条发出的消息都有一个表示该消息重要程度的紧急代码(Severity)。代码值为0―99,值越大,紧急级别越高。可以在消息队列中设置这个紧急代码的最低值,以便使到达该消息队列的紧急代码超过这个最低值的消息,按消息队列的Delivery Mode值进行相应的处理。消息的类型和发送消息时使用的命令决定消息的紧急代码,SNDBRKMSG发送的消息紧急代码为99;SNDMSG发送的*INQ类型的消息紧急代码为99;SNDMSG发送的*INFO类型的消息紧急代码为80。系统中的每一个消息队列都有一个重要性级别与之关联,我们可以通过CHGMSGQ命令来设置和改变该值。只有当消息队列接收到的消息紧急代码大于或等于该重要性级别,系统才会中断当前用户的工作显示消息,否则只将消息保存在消息队列中。
使用CHGMSGQ命令,用户可随时根据需要修改相应消息队列的Delivery Mode和Severity的值。当一个用户注销(Sign off)时,系统会自动将该用户消息队列的Delivery Mode修改成*HOLD;用户再次注册时,系统又会将该消息队列的Delivery Mode设置成 *NOTIFY,如果用户脱机这一段时间有消息到达就会给用户一个提示。
1.12 Allocating QSYSOPR Message Queue
|
Figure 3-16. Allocating QSYSOPR Message Queue |
QSYSOPR是一个最重要的消息队列,系统在发现异常,或对设备进行某种操作,或发生一些较重要的事件时都会将消息送到这个消息队列中,操作员要随时查看这个消息队列,以便使问题得到及时处理。
像任何一个消息队列一样,QSYSOPR在某个时刻只能分配给一个用户作业。其它用户可以用DSPMSG QSYSOPR命令查看消息队列的消息。当用户用QSYSOPR登录时,QSYSOPR消息队列自动地分配给相应的交互式作业,模式为*BREAK。
使用图所示的步骤可以将QSYSOPR消息队列分配给另一个用户或工作站。具体为:系统操作员退出所在工作站,然后作为系统操作员在另一工作站上登录或系统操作员改QSYSOPR消息队列的模式为*HOLD(CHGMSGQ QSYSOPR *HOLD)。然后另一个工作站用户改QSYSOPR消息队列的模式为*BREAK(CHGMSGQ QSYSOPR *BREAK)或*NOTIFY(CHGMSGA QSYSOPR *NITIFY)。
2.1 常用命令
总结:
1. 消息的类型、来源。
2. 消息队列的作用、种类及生成途径。
3. 发送消息的常用命令有两个SNDMSG和SNDBRKMSG,它们的区别在于SNDBRKMSG发出的消息只能送到工作站的消息队列中,且不受消息队列的Delivery Mode制约,消息到达对方后,马上在工作站上显示出来。发出的消息紧急代码一律为99。其他功能同SNDMSG。
4. 学会用DSPMSG命令显示消息和响应查询型消息。
5. 消息队列的交付方式及控制。
6. Assistant Level的含义,不同的Assistant Level(*BASIC、*INTERMED)下,如何删除消息队列中的消息及F11、F16键的功能。
|
模拟练习测试: |
|
1. What command would you use to display the system menu for messages?
|
5. *PSSR-程序出错处理
*PSSR 是程序出错处理程序,如果在执行程序时出现错误;而程序中使用了*PSSR子过程,那么程序出错总是跳转到*PSSR去处理。我们可以在*PSSR根据情况编写各种错误处理语句。
另外,ENDSR后面’*CANCL’表示如果出错,其程序退出方式为以取消(cancel)方式退出。
*PSSR BEGSR
…………
ENDSR ‘*CANCL’
*************** Beginning of data *************************************
0001.00 F**PSSR 程序出错处理
0002.00 DDIVDEND S 2P 0
0003.00 DDIVSOR S 2P 0
0004.00 DRESULT S 2P 0
0005.00 C Z-ADD 5 DIVDEND
0006.00 C Z-ADD 0 DIVSOR
0007.00 C EVAL RESULT= DIVDEND/DIVSOR
0008.00 C RESULT DSPLY
0009.00 C SETON LR
0010.00 C RETURN
0011.00 C
0012.00 C *PSSR BEGSR
0013.00 C MOVEL 'DIVSOR IS 0' ERRMSG 12
0014.00 C ERRMSG DSPLY
0015.00 C ENDSR '*CANCL'
****************** End of data ****************************************
6. 批处理作业方式DEBUG
准备
1. 建立物理文件数据字典、部门表、员工信息表
2. 编写RPG程序,实现从员工信息表中的小计来统计部门表中的合计,并更新部门表
REFILE PF 数据字典
A* 数据字典
**
A R REFMT
**
A* CHARACTER SETS
A CODE 5A
A ID 7A
A SEX 1A
**
A*DBCS-OPEN SETS
A NAME1 12O
A NAME2 20O
A DESCRPT 30O
**
A*NUMERIAL SETS
A AGE 4 1
A AMOUNT 15 2
A PRICE 11 2
A QUATITY 7 0
**
A*DATE SETS
A DATE1 L
**
DEPARTMENT PF 部门信息
A*
A UNIQUE
A R DEPART
**
A DPCODE R REFFLD(REFMT/CODE *LIBL/REFILE)
A COLHDG('部门码 ')
A DPNAME R REFFLD(REFMT/NAME1 *LIBL/REFILE)
A COLHDG('部门名 ')
A DPTOTAL R REFFLD(REFMT/AMOUNT *LIBL/REFILE)
A COLHDG('合计 ')
A
A K DPCODE
**
EMPLOYEES PF 员工信息表
A REF(*LIBL/REFILE)
A UNIQUE
A R EMPLOY
**
A EYCODE R REFFLD(ID)
A COLHDG('雇员码 ')
A DPCODE R REFFLD(CODE)
A COLHDG('部门码 ')
A EYNAME R REFFLD(NAME2)
A COLHDG('雇员名 ')
A SUBTOTAL R REFFLD(AMOUNT)
A COLHDG('小计 ')
A K EYCODE
A K DPCODE
**
EMPL LF 员工信息表以dpcode为key
A R EMPLOY PFILE(EMPLOYEES)
A K DPCODE
TOTALDEMO RPGLE按部门统计总数
FDEPARTMENTUF E K DISK
FEMPL IF E K DISK
DTOTAL S 15 5
C *LOVAL SETLL DEPART
C DOW NOT %EOF(DEPARTMENT)
C READ DEPARTMENT
C IF NOT %EOF(DEPARTMENT)
C DPCODE SETLL EMPLOY
C IF %EQUAL
C EVAL TOTAL=0
C DPCODE READE EMPLOY
C DOW NOT %EOF(EMPL)
C EVAL TOTAL= TOTAL + SUBTOTAL
C DPCODE READE EMPLOY
C ENDDO
C
C EVAL DPTOTAL=TOTAL
C UPDATE DEPART
C ENDIF
C
C ENDIF
C ENDDO
C
C SETON LR
C RETURN
C
C
C *PSSR BEGSR
C 'ERROR' DSPLY
C ENDSR '*CANCL'
C
***
命令介绍
SBMJOB,使用本命令将一个作业提交到后台队列,在后台排队等待执行。
STRSRVJOB 本命令将为指令进行远程服务,经常用于DEBUG批处理作业,本命令的终止命令为ENDSRVJOB
WRKSBMJOB 本命令可以对后台队列中的作业进行调度管理操作。
DSPMODSRC在执行STRDBG以及STRSRVJOB命令之后显示源程序,设置断点。注意:这个命令脱离STRSRVJOB、STRDBG不能单独使用。
执行步骤
首先用CHGCURLIB命令指定需要DEBUG的程序所在的LIBRARY。注意需要DEBUG的程序编译时DEBUG选项应该定义为*SOURCE 或者为*ALL以支持SOURCE DEBUG.
1)使用SBMJOB提交TOTALDEMO程序至后台批处理作业,为了明确分辨作业,可以为此作业命名为TEST01,作业定义为HOLD(*YES),其含义是作业设置为挂起状态,作业酱紫批处理作业队列里等候执行,一旦发出了释放(Release)指令,作业立即开始运行。
SBMJOB CMD(CALL PGM(TOTALDEMO) JOB(TEST01) HOLD(*YES)
2)键入WRKSBMJOB命令获得刚刚提交的作业的作业名、用户名、作业号。
可以观察TEST01作业为挂起(HELD)状态。这里键入选项5进入TEST01作业:
键入执行,可以看到第二行所标注的JOB:TEST01 USER:TRNUSR09 NUMBER:286641
注意作业号的唯一性,每一次作业号都不可能相同。将此三项记录下来。可以键入两次F3退出本屏幕。这个例子作业号是286641,作业是TEST01,用户名是TRNUSR09。
3)使用STRSRVJOB命令填入第2步获得的信息,例如:
键入执行,开始执行批处理作业服务过程。键入STRDBG命令,提示F4如下所示:
需要调试的源程序应该显示出来:
5)使用F21以获得命令行。
6)在命令行上键入WRKSBMJOB,键入执行:
使用选项6,键入执行释放挂起的作业TEST01
7)键入F10进入命令行为本作业输入DEBUG命令,注意:不要键入执行,否则在设立断点之前键入执行,程序就会运行,因而无法进行DEBUG断点设置
8)在命令行上使用DSPMODSRC以显示源程序,键入执行。
使用F3退出断点设置;
10)使用F12退出命令行,此时又可以看见第6步的系统信息:
11)此时键入执行释放挂起的批处理作业,批处理作业TEST01开始运行。注意这一次不要使用F10键。
12)程序运行时将停留在我们设置的断点上:例如,停留在第8步设定的断点第23行上
可以如同交互式DEBUG使用DEBUG命令进行处理。
13)一旦程序或者作业结束,必须使用ENDDBG命令以及ENDSRVJOB命令结束操作。
顺便提醒读者,如果在批处理作业的RPGIV程序中出现了交互式语句,例如DSPLY:显示文件输入输出语句程序的调用,例如EXFMT,作业将会被挂起处于MESSAGE WAIT状态,这是因为后台批处理作业无法处理对外显示信息引起的。
7. 指针
概念
什么是指针
指针是数据在内存或磁盘上的物理地址。因为OS400是统一寻址指令系统,即物理内存与磁盘是统一寻址,所以,指针没有区分内存指针还是磁盘指针,都是统一的指针。
指针的表示
在OS400下,指针(pointer)是一个16byte,即16字节的符号,是OS自动分配指针和其内容。
指针在计算机语言中的应用范围
指针用在除CL之外的高级开发语言中,如C/C++、RPGLE等。
如何查看指针地址带出的数据?
在RPGLE debug方式下,在命令行对命名的指针,如Ptr,键入:
EVAL Ptr : C 100
C表示是字符型方式显示指针带出的数据;X表示16进制显示指针地址带出的数据。
100表示显示数据的长度。
补充,os400下,debug用F11显示程序变量的长度,默认值是1024字节。如果需要查看1024之后的数据,用eval方式,比如字符变量C_Var;eval C_Var : C 8096,在debug方式下,os400就开辟一个8096字节区域存放变量C_Var的显示内容。
如何确定指针是否有效?
在debug方式下,用eval或F11显示命名的指针,比如Ptr,如果显示为:PTR=SPP:FC27C1E4F206E1B0,说明这个指针是有效的,否则,OS400会报指针无效信息。
指针在RPGLE中的应用
指针在RPGLE中的定义
指针在RPGLE D表中的定义用符号’*’ 进行定义。
被指针定义的对象范围较广,通常在RPGLE中用指针定义指向一个数据结构DS; 或字符型变量。例如
*************** Beginning of data *************************************
**
DREFDS DS 5
D FLD1 1 3A
D FLD2 4 5P 0
**
D* 指针定义方式一
DMY_DS DS LIKEDS(REFDS)
D BASED(PTR_1)
DPTR_1 S *
D*LIKEDS
D* 关键字用来将数据结构、原型返回值或原型参数定义为与另一个数据结构相
D* 似。新项的子字段将与另一数据结构的子字段完全相同。
D
D* BASED(basing_pointer_name)
D* 对数据结构或独立字段指定 BASED
D* 关键字时,将使用指定为关键字参数的名称来创建基指针。此基指针存放正
D* 在定义的基数据结构或独立字段的地址(存储器位置)。
**
D* 指针定义方式二
DD_STRING S 1024 VARYING BASED(PTR_2)
DPTR_2 S *
D*VARYING 定义变长的变量
D
D
C SETON LR
****************** End of data ****************************************
注意:在D表用based到指针的数据结构或变量,不能用INZ关键字,即不能用程序变量初始赋值键字。
指针的赋值
在RPGLE中,指针的赋值可以直接指针间的直接赋值,例如
PTR_1 = PTR_2
也可以通过RPGLE的内置函数%ADDR进行赋值,例如
*************** Beginning of data *************************************
DCHAR1 S 10 INZ('ABC')
DPTR S *
/FREE
PTR = %ADDR(CHAR1);
*INLR =*ON;
/END-FREE
****************** End of data ****************************************
指针PTR指向字符长度为10,内容为’ABC’的数据。
指针的应用范围
在RPGLE中,通常都是把指针用在程序间传递参数上。
早期的RPG程序基本上都是通过plist对每一个具体参数进行定义。这样的定义对项目联调、修改和代码最终定版,都造成非常大的难度,增加项目的实际开发难度。
我们的成功经验:
在程序间每一个程序代码都定义统一 的参数格式,比如
C *ENTRY PLIST
C PARM PTR_1
C PARM PTR_2
C PARM PTR_3
这段RPGLE代码放在统一的copybook中,在每一程序代码中只要定义:
C/COPY 库名/文件名,member名
其中,PTR_1只能用在应用项目的系统变量范围,比如交易日,时间等;
PTR_2只能用在应用项目的输出参数范围,比如PGMA调用PGMB时,PGMA对PGMB传递的参数只能用PTR_2带出;PTR_3只能用在返回结果参数范围,及PGMB对PGMA的返回值。
对三个PTR指针引入的参数结构,都可以放在统一的copybook中,在程序代码中直接进行copy定义。这样做的好处,一个应用项目的数据结构是唯一的。
用指针偏移取数据
通常情况下,最简单的应用就是一个指针直接指向数据,比如:
D D_inDS DS LIKEDS(D_MARK1DS)
D D_outDS DS LIKEDS(D_MARK2DS) based(ptr_3)
当程序代码进行调用后,
/free
…
D_inDS.fld1 = ‘ABC’;
…
//把数据结构D_MARK1DS的地址赋值给指针ptr_2
PTR_2 = %ADDR (D_MARK1DS);
Callp PGMA(PTR_1:PTR_2:PTR_3);
调用PGMA之后,PTR_3就直接把返回参数引入到程序数据
结构D_outDS中了,直接引用。
如果程序的返回结果是包括一个以上的数据结构,比如通过
PTR_3返回的参数如下:
D D_outDs ds based(Ptr_3)
D D_DataDs1 likeds(RefDataDs1)
D D_DataDs2 likeds(RefDataDs2)
这时,在程序中就要这样定义和用指针偏移读取数据结构的
数据:
D D_DataDsA ds likeds(RefDataDs1) based(Ptr_A)
D D_DataDsB ds likeds(RefDataDs2) based(Ptr_B)
D Ptr_A s *
D Ptr_B s *
/free
...
Callp PGMA(ptr_1:ptr_2:ptr_3) ;
Ptr_A = Ptr_3 ; // 返回数据结构的数据D_DataDs1已经放入D_DataDsA中了。
Ptr_3 = Ptr_3 + %len(D_DataDsA); //把指针Ptr_3偏移到指向
//第二个数据结构的起始地址。
Ptr_B = Ptr_3 ; //返回数据结构的数据D_DataDs2已经放入D_DataDsB中了
深度探讨指针用法
指针层次的概念和用法
在RPGLE 中指针是可以嵌套的,是有层次概念的,比如
d D_Ds1 ds based(P_Ptr1)
d D_ParmDs1 likeds(RefDs1)
d P_PtrA *
*
d D_Ds2 ds likeds(RefDs2)
*
d Ptr1 s *
d PtrA s *
/free
……
P_PtrA = %addr(D_Ds2)
Callp Proc(P_Ptr1) ;
这时,在代码中表示的指针层次是两层
多层指针结构下,注意事项
指针引入的数据结构DS,最好立即用新的数据结构DS进行转移保护,如
d D_DsIn ds based(Ptr3)
d P_PtrA *
d D_DsParmDs ds likdds(RefDs) based(P_PtrA)
d D_DsInBak ds likdds(RefDs)
*
d P_Ptr3 s *
d P_PtrA s *
/free
…..
Callp Proc(Ptr1:Ptr2:Ptr3) ;
//判断Ptr3数据结构中的返回码
……
//如果返回码没有错误,立即备份返回数据结构
D_DsInBak = D_DsParmDs ;
在指针引入复杂结构中,实数据放在前面,变长数据结构放在复杂结构后部,如
d D_InDs ds based(PtrA) qualified
d c_String 10
d s_Digit 5 0
d D_ArrDsSet likeds(D_ArrDs)
d P_PtrA *
d P_PtrB *
*
d D_ArrDs ds qualified
d s_count 3s 0 dim(2)
d a_ymlst 10a dim(500)
d a_mmlst 10a dim(500)
*
d D_DsA ds based(P_PtrA)
d c_fld 30
d p_fld 12 5
*
d D_DsB ds based(P_PtrB)
d s_Atrr 3 0 dim(20)
d c_String 1024 varying
指针的迁移
在程序之间用指针进行传参调用,因为指针仅仅把参数的起始指针进行传递。很多情况下,因为程序运行时,OS400在ASP中给每一个程序都分配一个程序运行临时区,当程序获取下一级程序返回的临时区域的变量(参数)指针地址时,本身的程序运行临时区,OS400就会自动进行调整,这时,可能就会在调整过程中清除掉进入程序的运行临时区的指针引入的内容。
针对上述情况,采取保护措施是必要的。保护方法有:
指针中设置选项用const;
对输入指针引入的内容进行同类数据结构转移保护。
用指针传递多个数据结构的常用两种方法
指针分层:
d D_Ds ds based(Ptr)
d PtrA *
d PtrB *
*
d D_Ds1 ds likeds(RefDs1)
d D_Ds2 ds likeds(RefDs2)
*
/free
……
PtrA = %addr(D_Ds2) ;
PtrB = %addr(D_Ds1) ;
归集多个数据结构,采用一个指针进行传递
d D_Ds ds based(Ptr)
d D_Ds1 likeds(RefDs1)
d D_Ds2 likeds(RefDs2)
或者,
d D_Ds1 likeds(RefDs1)
d D_Ds2 likeds(RefDs2)
*
d c_String s 10000 varying based(Ptr)
/free
…..
c_String = D_Ds1 + Ds2 ;
用指针分层方法的风险,因为这种方法是多个指针同层传参,即每一个指针引入一个数据结构,或复杂数据结构,比多个数据结构进行归集,然后用一个指针进行传参,前者的数据安全性比后者差。特别情况下,如果经过多级调用,且都是通过指针进行结果返回,可能出现数据丢失。
用数据归集方法的风险,存在前面数据结构中有变长字段。
8. CALL/CALLB/CALLP(程序调用及传参)
RPGIV提供了3中程序间调用的方法:
call 是程序对程序的动态调用。如程序A call 程序B,程序B要放在运行程序A环境的库列表中,且如库列表中有重复名字程序B,排在第一位子的程序B总是被调用。
callb是在一个bind程序中,多个module程序的相互调用。是程序静态调用。要注意不要陷入调用不合理循环。调用一个绑定模块,静态调用
callp是最有趣的调用,它结合call和callb的特点,把call的灵活性和callb的响应效率高特点结合在一起。调用原型过程或者原型子过程。callp的最主要用法,应用程序用callp调用服务程序的某个组成程序;或在一个bind程序中调用某个过程例程。都是静态调用。callp主要有两种方式调用:1)原形接口调用;2)例程点调用。
RPGIV程序之间的调用以及参数传递
Call首先表述调用的过程,当程序执行到call语句时,控制转向到call语句后跟的程序并且执行这个程序,当遇到return返回语句,程序转向调用程序并且执行call语句下一条语句。
Call语句后跟的可以是常量、字段以及变量、数组元素、数据结构包含的程序名。在call语句紧跟的1至n个parm参数语句可以是PRGIV所认可的类型,调用与被调用的程序之间的参数数量、参数类型应该一致。
被调用的程序由*ENTRY PLIST开始下跟PARM参数,下例:
调用程序:
CALL ‘prog02’
Parm fld1
Parm fld2
Parm fld3
被调用的程序:
*ENTRY PLIST
PARM FLDX
PARM FLDY
PARM FLDZ
RPGIV默认的参数传递方式是传参,而不是传值。
如果需要在参数传递过程中不改变参数值,可以使用如下方法:
CALL ‘Pro g02’
PARM FLDx FLDy
将想要传递的值置入FLDx,当调用发生的时候,系统复制FLDx的值到FLDy并且将FLDx的地址传入被调用的程序,即便FLDy返回值也许被改变,但是FLDx不受干扰。
补充知识:
参数根据调用后的效果不同,即是否改变参数的原始数值,可以分为两种: 按值传递的参数与按引用传递的参数。
他们的区别是什么呢?我们看一个案例说明:比如有个女孩非常喜欢QQ,
还给自己起了个浪漫的名字“轻舞飞扬”,飞扬小姐认识了一个网友“痞子蔡”(n年前非常火的一个网络小说《第一次亲密接触》的两个主人公),他们聊的很投缘,有天飞扬小姐竟然把自己的电话号码告诉了痞子蔡,有天痞子蔡竟然电话过来约飞扬小姐见面,考虑到网络的虚幻与现实人心的叵测,飞扬小姐面临着艰难的选择:是否去见网友?
那么见网友就是调用的程序,方法的参数就是轻舞飞扬,如果痞子蔡是个披着羊皮的狼,那飞扬小姐就可能面临危险,比如身上少些东西或者多些东西,就是说在方法体中有可能改变参数的原始数值。
CALL ‘见网友’ /*做些事情,改变轻舞飞扬。*/
PARM 轻舞飞扬
现实中飞扬小姐只有两种选择,第一,为了爱情奋不顾身,上刀山下火海,再所不辞,但这有可能改变飞扬状态,即数值;第二,委婉拒绝以求自保,但如果痞子蔡为人特别好,她也许会失去一段大好的姻缘。这里,如果科技足够发达,我们可以完全给出第三种选择,轻舞飞扬制作一个自己的替身,即把自己备份一份,然后把备份传入方法体,这样不论痞子蔡对她做了什么都不会对她的源体发生影响,又能检测痞子蔡对自己是否真心。
OK,这里我们就把飞扬小姐本人去见网友叫按引用传递,这样在方法体中发生的改变在方法调用完对参数还有影响,而把让她替身去叫按值传递,这样方法调用完对参数原始数值没有影响,发生改变的只是参数的备份,比如痞子蔡给她一个大钻石,这份备份在方法调用完会自动消亡,也就是说飞扬的替身在见完网友自动消亡,当然,大钻石也会随着替身消亡。最后可以 简单概括为一句话:按值传递是让自己备份去,参数数值不变;按引用传递是自己亲自去,参数数值会改变。
CALLP
原型调用过程:
在命令行键入CALL PGM(QCMDEXC) PARM(‘DSPMSG’ 6)可以显示信息:
CALL PGM(QCMDEXC) PARM('DSPMSG' 6)
Display Messages
System: IPACS@D
Queue . . . . . : GAOYONG Program . . . . : *DSPMSG
Library . . . : QUSRSYS Library . . . :
Severity . . . : 00 Delivery . . . : *NOTIFY
Type reply (if required), press Enter.
(No messages available)
我们在命令行上调用了外部程序QCMDEXC,仍然在RPGIV中用这个程序:使用CALL的方法:其中CALL末端的99是表示*IN99指示符变量,检测调用命令是否成功。
*************** Beginning of data *************************************
0001.00 C CALL 'QCMDEXC' 99
0002.00 C PARM 'DSPMSG' COMMAND 80
0003.00 C PARM 6 CMDLEN 15 5
0004.00 C EVAL *INLR = *ON
0005.00 **
****************** End of data ****************************************
改用CALLP的方法:需要指定原型调用PR分别指定了外部程序关键字:EXTPGM(QCMDEXC),参数COMMANDX,CMDLENTHX,在第一行第二行使用D描述指定参数类型定义。在一个程序中最大可以使用255个参数。调用时过程名后面的参数之间使用‘:’分隔。
*************** Beginning of data *************************************
0001.00 DCOMMAND S 80
0002.00 DCMDLEN S 15 5
0003.00 **
0004.00 DQCMDEXC PR EXTPGM('QCMDEXC')
0005.00 DCOMMANDX LIKE(COMMAND)
0006.00 DCMDLENX LIKE(CMDLEN)
0007.00 **
0008.00 C EVAL COMMAND = 'DSPMSG'
0009.00 C EVAL CMDLEN = 6
0010.00 C CALLP(E) QCMDEXC(COMMAND : CMDLEN)
0011.00 C EVAL *IN99 = %ERROR
0012.00 C EVAL *INLR = *ON
0013.00 **
****************** End of data ****************************************
为什么要使用CALLP?最大的好处是对程序的参数校验是在编译时进行的,这样许多参数类型、数量问题在编译时就被检查出来,实际调用的时候会减少调用程序参数出问题。
由于使用较复杂的过程调用可以嵌套在内置函数中使用,可以省略CALLP直接在程序中使用过程名返回值。
例如:定义 D ZIPCODE PR 10A
D NUMBER 10P 0 VALUE
/FREE
调用: STRING = ‘ADDRESS’ + ZIPCODE(NUMBER)
/END-FREE
我们在使用自由格式的时候CALLP显得更加简练,由于省去在CALL程序时必要的PLIST、PARM固定格式,可以说CALLP实际上是使用自由格式调用程序时必要的写法。
CALLP最大的好处是可以使用静态调用方式,提高程序运行效率。如果要进行调用检查,可以显式使用CALLP(E)的方法。E是错误检测扩展符,可以使用%ERROR()内置函数对调用状况进行检测。
下例表示两个使用自由格式RPGIV程序ParmTest1调用ParmTest2:
第2行为PR(原型定义)指明需要调用的外部程序名,PR指定的名字可以和外部程序名不同。第3行到第5行说明PR所调用的程序的参数类型和个数。实际上编译器对参数名P_Code、P_Amount、P_Lgcal是不做检查的,只是为了书写清除的目的。第7行至第9行为程序内部的局部变量定义,分别说明了CODE、AMOUNT、LGCAL3个变量,并且PR参数表继承他们的属性,见第3行至第5行LIKE语句。第14行使用CALLP调用了这个外部程序,它将传递3个参数,第一个参数将编译码‘00001’传给外部程序PARMTEST2,第二个参数使用AMOUNT取回值,第三个参数是取回调用是否成功的标志。第15行利用%EDITC()函数用0编辑吗并且数值左部带$显示Amount值。
用到的文件定义:
LIVOOP1.pf
A UNIQUE
A R FMT1
A CODE 5A
A NAME 20O
A QUANTITY 9 0
A AMOUNT 15 2
A K CODE
ParmTest1.rpg
DParmTest PR ExtPgm('PARMTEST2')
DP_Code Like(Code)
DP_Amount Like(Amount)
DP_Lgcal Like(Lgcal)
**
DCode S 5A Inz('00001')
DAmount S 11P 2
DLgcal S N
**
/FREE
CallP ParmTest(Code:Amount:Lgcal);
Dsply %EditC(Amount : 'O' : '$');
Dsply Lgcal;
*InLr = *On;
Return;
/end-free
使用14即CRTBNDRPG PGM(*CURLIB/PARMTEST1)命令编译此程序。编译器将对所使用的参数进行类型、数量检查,这是CALL方式调用程序所不及的。
被调用的程序ParmTest2在第5行定义PR(原型),第6行至第8行为参数属性个数的定义。第10行至第13行是PI(Procedure InterFace)定义了被调用程序的入口参数的属性、个数。需要指出的是PI定义的入口参数个数及其属性一定要与调用此程序的参数、个数相同。并且位置相同。
ParmTest2.rpg
**
FLIVOOP1 IF E K DISK
**
D ParmTest PR EXTPGM('PARMTEST2')
D P_Code Like(CodeX)
D P_Amount Like(AmountX)
D P_Lgcal Like(Lgcal)
**
D PArmTest PI
D CodeX 5A
D AmountX 11P 2
D Lgcal N
**
/Free
Chain Code LivOOp1;
if %Found(LivOOp1);
AmountX = Amount;
Lgcal = %Found(LivOOp1);
endif ;
*INLr = *On ;
return;
/End-Free
使用CRTBNDRPG PGM(*CURLIB/PARMTEST2)编译此程序。
可以使用Call ParmTest1检查程序调用结果。
CALLB(call a bound *Module)
对于使用模块(MODUEL)进行BINDING的程序,使用CALLB的方法调用模块(MODUEL).
其使用方法为:固定格式:首先将每个模块使用CRTRPGMOD的方法进行编译,然后使用CRTPGM的方法进行组装。
其调用模式为:
CALLB ‘ModuleName’
PARM x
PARM y
。。。。。。
注意:这种方法是属于静态调用,运行效率比动态调用高,缺点是必须用固定格式,在自由格式中已经使用CALLP取代了CALLB。
使用预定义原型的方式调用MODULE中的过程(Procedure)。
使用自由格式:首先在主模块中定义原型MOD2:PR是原型定义:PI是调用的原型接口(Prototype Interface)参数定义。
使用CRTPGM将MOD1以及MOD2组装成为程序ABC.
可以看出在自由格式中必须使用CALLP。最多在一个程序中可以定义调用399个过程(Procedure)。
9.MODULE、CALLB、CALLP、SRVPGM的概念
*MODULE(模块)是类似*PGM的一种OBJ,可以用多个*MODULE组装成一个*PGM。但是*MODULE本身不能直接被调用。*MODULE的优点是调用速度快、模块化。在ILE环境下各种高级语言c、RPG、cobol、CLLE、JAVA编写的*MODULE可以拼装在一起使用。
一、有关*MODULE的操作:
1) 将源码(SOURCE MEMBER)编译为*MODULE,在PDM中用选项15将源码编译为模块(CRTRPGMOD/CRTCLMOD……)
2) 将*MODULE组合(BINDING)为一个PGM,使用CRTPGM。注意:由于一般都是绑定(BINDING)超过一个以上的*module生成程序*PGM,仅可以在命令行上使用此命令(CRTPGM)。
3) 当PGM中的某个*MODULE需要修改,使用CRTXXXMOD编译之后,可以用UPDPGM将修改的MODULE替换到这个*PGM,不用重新编译所有模块。这种方法的好处是节省了编译时间,由于模块化的原因,检查源码的范围缩小,使得维护源码的效率提高。好比我们维修汽车,如果是轮胎损坏。不必将整个汽车拆散,仅仅将轮胎更换即可。
二、ACTIVATION GROUP(激活组)
ACTGRP相当于在作业(JOB)内部的一个子系统,当一租程序使用同一个ACTGRP时,它们可以共享相同的资源。一个作业内部可以有一个以上的ACTGRP。
ACTGRP共有3种:
1. 自定义命名,名字可以由定义程序时自行指定。
2. *NEW 调用程序时由系统产生一个新的ACTGRP。程序结束系统自动释放此ACTGRP。
3. *CALLER使用上级程序的ACTGRP。如果是(最初始的)第一级程序,则使用系统提供的*DFTACTGRP(默认激活组)
可以用CL命令RCLACTGRP释放某个自定义命名ACTGRP中的资源。
三.在程序中CALLB、CALLP都使用*MODULE的方法。
CALLB是调用一个完整程序的*MODULE(程序里可以定义F文件定义)
在使用CALLB时需要:
1) 生成*MODULE(PDM使用选项15-CRTPGMMOD)
2) 调用*MODULE(CALLB MODULE
PARM X
PARM Y
PARM …)
3) CALLB的调用与一般的CALL(调用程序一样)
CALLP是调用一个PROCEDURE(PROCEDURE里不能定义F文件定义)
在使用CALLP时需要:
<1>声明PROCEDURE(声明的源码不用编译)
<2>定义PROCEDURE(定义的源码需要编译)
<3>生成*MODULE PDM使用选项15 CRTPGMMOD
<4>调用PROCEDURE
固定格式: EVAL RTNVAL = PROC(X:Y:Z … )
自由格式:CALLP PROC(X:Y:Z …)
四.一个完整的例子
(1)PROCHD
第1行声明了PROC01及返回值。
第2,3行声明了PROC01的参数X、Y、VALUE指参数传值。
PROC02与PROC01类似
(2)PROC01
第1行的H定义NOMAIN指明本程序中没有主过程。
第2行将PROCEDURE的声明COPY到本程序中。使用*LIBL比较灵活,实际应用中可以指定不同LIB下得内容。
第4、10行和定义了PROC01的头(BEGIN)和尾(END),第4行的EXPORT指明这个PROCEDURE可供外部调用。
第5行定义了PROC01的返回值为(11,2)数字型。
第6、7行定义了PROC01的2个入口参数X(5,0)和Y(7,2),VALUE指参数是传值的。
C定义中可以写具体的处理。
第9行结束PROC01,并且返回值等于X*Y。
(3)PROC02与PROC01类似。
用15将PROC01,PROC02编译为*MODULE。
(4)RPG01:
第13行将PROC01的声明COPY到本程序中。
第38行调用了RPG02,使用CALLB方式。
第47行调用PROC01,使用TOTAL接收返回值。
第50行调用了PROC01,没有接收返回值。
(5)RPG02是一个普通的RPGIV程序。
用PDM选项15将RPG01和RPG02编译为*MODULE.
(6)CLP01
第2行使用CALLPRC方法调用PRG01,CALLPRC是CLLE中调用*MODULE的方法。注意:如果CL程序的类型室CLP(非ILE类型),一定用将其类型改为CLLE(ILE类型)。
(7)用PDM选项15将CLP01编译为*MODULE。
(8)在命令行上输入CRTPGM TEST1,按下F4键,编译组合(BINDING)一个PGM,PGM的名称可以指定。(这里用TEST1),
编译参数:
PROGRAM ENTRY PROCEDURE 指定哪一个*MODULE作为程序的入口,本程序的入口点为CLP01。
ACTIVAION GROUP 根据应用指定为显示命名或者*NEW 或者*CALLER。注意一定要谨慎使用*NEW参数。
五、SRVPGM(服务程序)
是将一些公用的PROCEDURE组成一个可以被其它程序调用的程序。一般程序可以调用在服务程序中的这些PROCEDURE。
在使用SRVPGM时需要:
(1) 定义PROCEDURE。(定义的源码需要编译)
(2) 生成*MODULE(PDM选项15-CRTPGMMOD)
(3) 产生一个BND语句的源码(源码的类型为BND,不需要编译),声明SRVPGM中可以外部调用的PROCEDURE。
(4) 产生一个BNDDIR目标。(CRTBNDDIR),声明一个包含PROCEDURE的*MODULE的目录。
(5) 生成服务程序SRVPGM(CRTSRVPGM)
六、一个完整的例子(注:本例与上例基本一样,只是从使用PROC01的*MODULE改为使用SRVPGM)
(1) 将PROC01,PROC02生成为*MODULE。(同四)
(2) BND1
第2行声明可供外部调用的PROCEDURE PROC01
(3) 输入CRTBNDDIR BNDDIR
定义一个包含PROCEDURE的*MODULE的目录。
将PROC01的*MODULE指定进入目录。
(4) 输入CRTSRVPGM SRV1,按F4键。
利用PROC01的*MODULE产生SRVPGM。SRVPGM的名称可以指定(这里用SRV1)。*MODULE处输入要组成SRVPGM的*MODULE。
SRCMBR处输入BND的源码(这里用BND1)
BINDING路径BNDDIR处输入BNDDIR的名称。(这里用BNDDIR1)
(5) 用PDM选项15,按F4键。将RPG01生成为*MODULE。期中SRVPGM中输入SRV1。
(6) 将RPG02,CLP01编译成为*MODULE.(同四)
(7) 输入CRTPGM TEST2 产生一个PGM(同四)
最后,可以调用CALL PGM(TEST2)进行校验。
浙公网安备 33010602011771号