As long as human beings are entering data into computers, there are going to be data entry errors. Data entry errors are as inevitable as death and taxes (our apologies to Ben Franklin). A good computer system can diagnose some of these errors and not others. But one thing is certain: How interactive data entry programs are written can have a big impact on the quality of the data in the system. The better the error messages presented in the case of error are, the more likely it is that the resultant data entered into the system will be correct.
This article deals with using APIs and ILE procedures to create informative and useful error messages in your interactive RPG programs. We will also explore how to use a technique called wrapping to remove some of the complexities of using APIs in your RPG programs. Let’s get down to the nitty-gritty.
Begin at the Beginning
Error message subfiles have been with RPG almost since the beginning of interactive midrange programming (or at least as long as we can remember, which is pretty much the same thing). The concept of presenting an error with the message subfile is rather simple. The user fills in multiple data entry fields on a screen and presses Enter. In turn, the program validates all of the entries and writes an error message to the program’s message queue for each of the errors it finds. The program then redisplays the screen along with a message subfile of all errors found in the message queue. The operator may page through the resulting error messages, correct the data, and press Enter again. The process repeats itself until the program deems the data valid, at which point the appropriate data is written to a database file.
This all sounds simple enough, but, as you know, the devil is in the details. One of the best ways to make your data entry programs more intuitive is to deliver quality error messages to the operator. For example, “Invalid customer number” is not nearly as precise as “Customer number 12345 is invalid.” Obviously, error messages containing the erroneous data are much more helpful. One of the ways to employ this technique is to use the Send Program Message (QMHSNDPM) API.
You can use the Send Program Message API to send a message to the message queue along with related message data. But the challenge with this technique is that the Send Program Message API includes 10 parameters, nine of which are required. This makes using the API a lot of work. We are going to show you a way to avoid all of that. Figure 1 shows an example of a program using our Send Error Message (SndErrMsg) and Remove Error Message (RmvErrMsg) ILE procedures. As you can see from the code, this type of error handling logic is not difficult. The sample code in Figure 1 displays a screen asking for input. In the next step, the RmvErrMsg procedure is called to remove any previously sent messages from the program message queue. Since our RmvErrMsg procedure does not return any values, our sample code uses the CALLP op code to execute it. The user then edits the data on the screen, and the SndErrMsg procedure executes for each error found. We coded our message subfile using the SFLINZ (Initialize Subfile) keyword so that multiple error messages will be displayed in the subfile when the message control record is displayed. The operator may then “page” through the error messages using the Page Up and Page Down keys, as with any standard subfile.
Signed, Sealed, and Delivered
We’ll take a closer look at how the SndErrMsg procedure is used in Figure 1. As with any ILE procedure, we’ll start with the procedure interface. The first keyword you see is Operational Descriptor (OPDESC). This keyword tells the system that information about the parameters will be accessible to the procedure via the Retrieve Operational Descriptor API. We’ll talk more about this option further on.
Notice that every parameter has the CONST (Constant) keyword. This tells you that the procedure that is called will not change any of those parameters. It also tells you that the calling program can pass the parameters with literals as well as fields.
Now, let’s look at the PMSGDATA parameter. This parameter has two keywords, *VARSIZE and *NOPASS. The *NOPASS value indicates that this parameter does not have to be passed to this procedure. The *VARSIZE keyword indicates that the length of the field being passed for this parameter can be variable in size, or, in other words, of any length. With both keywords in place, message data does not have to be passed with the error message, but, if the data is passed, it can be of any length.
Three parameters of our procedure remain: type of error message, error file name, and error file library. All of these parameters contain the *NOPASS keyword and are therefore not required parameters. The variables DefMsgFile and DefMsgLib contain the default error message file name and default library name that the program uses if you do not pass in these parameters. For our purposes, we’ve set the defaults to the qualified message file name CPUMSGF/C50LIB. You probably do not have this library or message file on your system. Therefore, you must change these fields to default to your own message file, or you must always pass these parameters when you call the procedure. The field DefMsgType contains the default message type.
Procedurally Speaking...
Figure 2 shows the SndErrMsg procedure. Now, let’s take a look at what this procedure actually does. If you have passed message data to this procedure (%PARMS are greater than 1), the procedure must determine the length of that data. To figure out the length of the data, we call the Retrieve Operational Descriptor API, CEEDOD.
The first parameter for the CEEDOD API is a number that corresponds to the parameter order that was passed to our procedure. We have coded a numeral 2 because we are trying to gather information about the second parameter passed to this procedure, which happens to be our message data. The next four parameters return some basic information about the type of data being passed. Because we are not interested in the type of data, only the length, we can ignore these parameters. The sixth parameter, INLEN, gives us the
length of the data being passed. The last parameter is an optional feedback code indicating errors. We have omitted this parameter.
Once we have used the CEEDOD API to determine the length of our message data, we can call the Send Program Message API. This API will actually send the error message to the message queue of the calling program. If you look closely at the parameters for this API, you will see that the first five parameters correspond roughly to the procedure interface for the SndErrMsg procedure. The message data length that we have retrieved is the fourth parameter for the API. The message file and library are one of the API parameters. We simply pass on the information to this API using either our established default values or the values passed to this procedure as parameters. The user does not need to know about the last four parameters—message queue, message queue number, message key, and error data structure. We’ve supplied the values for those parameters that make the SndErrMsg procedure work.
The reason we went through all of this is that we are able to hide some of the complexity of the API from the programmer using the procedure. This technique is called wrapping. (No, this does not refer to gift wrap, and we certainly are not going to break into rhyme.) We use this process to simplify working with the API.
You Don’t Know What You’ve Got till It’s Gone
Last but not least is getting rid of messages in the program message queue. One of the difficulties that can occur when presenting subfile error messages is making the last error message go away. For instance, say a program presents a screen, the user edits the entries, and he or she receives an error message. The user corrects the error and presses the Enter key, but he or she makes another error in a different field. The program edits the fields and wants to display a new error. But when the program displays the subfile, it contains both the old and the new errors! In order to eliminate this problem, you need to remove the messages from the message queue after you have displayed them.
You can use the Remove Program Messages (QMHRMVPM) API to eliminate those old errors in the message queue. We have wrapped a procedure around the Remove Program Messages API, too, and named it RmvErrMsg. Figure 3 shows this code. All you have to do is call the procedure after displaying your input screen.
The RmvErrMsg procedure accepts one optional parameter, and that is the message ID that you want to remove. The default value for this is *ALL, which removes all messages from the program message queue. Some of the other values that you could pass are the following:
• *KEEPRQS—clear all messages except request messages
• *NEW—clear messages that have not been received
• *OLD—clear only messages that have been received
The Remove Program Messages API accepts other values for this parameter (such as *BYKEY) that work in conjunction with a message key parameter. If you enter any of these other values or any invalid value, the procedure will not remove messages. In order to keep the code in this procedure to a minimum, we did not design the program to edit any of the values in this field. It’s up to you to put in only valid values or modify the procedure to edit the values.
Putting It All Together!
Compiling the procedures in Figure 2 and Figure 3 is simple. You should create the procedures using the Create RPG Module (CRTRPGMOD) command shown at the top of
each source listing. You should then bind the modules into a service program using the Create Service Program (CRTSRVPGM) command: We recommend that you store the prototype statements in source members and bring them into the program using the /COPY function. In order to minimize the number of figures for this article, we have not done that here. As with any program using a procedure, you cannot compile it using the default activation group, so your Create Bound RPG Program (CRTBNDRPG) statement might look something like this:
CRTBNDRPG PGM(SOMEPGM) DFTACTGRP(*NO) ACTGRP(QILE)
If you’d like, you can see an example of these procedures in action. The editors of Midrange Computing have developed a sample program that uses SndErrMsg and RmvErrMsg. You can download it at www.midrangecomputing.com/mc. If you need more details regarding procedures, prototypes, and ILE concepts, refer to our article “Wanna Play Center Field?” in the February 2000 issue of MC.
ILE can be complex and confusing, but it does not have to be. We are big believers in the KISS (Keep It Simple, Stupid!) method, and you should be, too. While the procedures can be somewhat complex, using them is a snap!
References and Related Materials
“Wanna Play Center Field?” Doug Pence and Ron Hawkins, MC, February 2000
* This is sample code intended only to demonstrate how to use
* SndErrMsg and RmvErrMsg procedures.
*
FDisplayFilcf e workstn
FCustomer if e k disk
* Prototype for send error message (should actually be a /COPY)
d SndErrMsg PR opdesc
d PMsgId 10 const
d PMsgData 32766 const options(*varsize:*nopass)
d PMsgInType 10 const options(*nopass)
d PMsgFile 10 const options(*nopass)
d PMsgLib 10 const options(*nopass)
* Prototype for remove error message (should actually be a /COPY)
d RmvErrMsg PR
d PRmvId 10 const options(*nopass)
c Dou *in61 = *off
* Display the messages in the subfile
c WRITE MSGCTL
* Display a screen for the user to enter information. User enters
* a customer number that is not on file in the Customer file
c exfmt format4
* Remove any previously displayed messages from the joblog
c callp RmvErrMsg
* If customer does not exist, issue error message and pass customer
* number to display in error message
c @CustNbr Chain Customer 61
c if *in61
c callp SndErrMsg(‘USR0453’:@CustNbr)
c endif
c enddo
CRTSRVPGM SRVPGM(XXXLIB/SOMENAME)MODULE(XXXLIB/
SNDERRMSG XXXLIB/RMVERRMSG)
KISS!
Figure 1: This program fragment shows how to use the SndErrMsg and RmvErrMsg ILE procedures.
HNOMAIN
*
* CRTRPGMOD MODULE(XXXLIB/SNDERRMSG) SRCFILE(XXXLIB/QRPGLESRC)
*
* procedure name: SndErrMsg
*
* procedure function: Send a program message wrapper 4 QMHSNDPM API.
* SndErrMsg prototype
d SndErrMsg PR opdesc
d PMsgId 10 const
d PMsgData 32766 const options(*varsize:*nopass)
d PMsgInType 10 const options(*nopass)
d PMsgFile 10 const options(*nopass)
d PMsgLib 10 const options(*nopass)
P SndErrMsg B export
d SndErrMsg PI opdesc
d MsgInId 10 const
d MsgInData 32766 const options(*varsize:*nopass)
d MsgInType 10 const options(*nopass)
d MsgFile 10 const options(*nopass)
d MsgLib 10 const options(*nopass)
d DefMsgFile s 10 inz(‘CPUMSGF ‘)
d DefMsgLib s 10 inz(‘C50LIB ‘)
d DefMsgType s 10 inz(‘*DIAG ‘)
d MsgFileLib s 20
d MsgData s like(MsgInData)
d MsgDtaLen s 6 0
d MsgId s 10
d MsgKey s 9b 0
d MsgQueue s 10
d MsgQueNbr s 9b 0
d MsgType s 10
D ErrorDs DS INZ
D BytesProv 1 4B 0 inz(116)
D BytesAval 5 8B 0
D MessageId 9 15
D Err### 16 16
D MessageDta 17 116 *-----------------------------------------------------------------*
* Prototype for CEEDOD (Retrieve operational descriptor)
*-----------------------------------------------------------------*
D CEEDOD PR
D ParmNum 10I 0 CONST
D 10I 0
D 10I 0
D 10I 0
D 10I 0
D 10I 0
D 12A OPTIONS(*OMIT)
* Parameters passed to CEEDOD
D DescType S 10I 0
D DataType S 10I 0
D DescInfo1 S 10I 0
D DescInfo2 S 10I 0
D InLen S 10I 0
D HexLen S 10I 0 *
c move MsgInId MsgId
c if %parms > 1
c callp CEEDOD(2 :DescType:DataType:
c DescInfo1 : DescInfo2: Inlen:
c *OMIT)
c move Inlen MsgDtaLen
c move MsgInData MsgData
c else
c clear MsgDtaLen
c clear MsgData
c endif
c if %parms >= 3
c eval MsgType = MsgIntype
c else
c eval MsgType = DefMsgType
c endif
c if %parms >= 4
c movel MsgFile MsgFileLib
c else
c movel DefMsgFile MsgFileLib
c endif
c if %parms >= 5
c move MsgLib MsgFileLib
c else
c move DefMsgLib MsgFileLib
c endif
C CALL ‘QMHSNDPM’
C PARM MsgId
C PARM MsgFileLib
C PARM MsgData
C PARM MsgDtaLen
C PARM MsgType
C PARM ‘*’ MsgQueue
C PARM 1 MsgQueNbr
C PARM MsgKey
C PARM ErrorDs
c return
P SndErrMsg E
*************** Beginning of data *************************************
HNOMAIN
*
* CRTRPGMOD MODULE(XXXLIB/RMVERRMSG) SRCFILE(XXXLIB/QRPGLESRC)
*
* procedure name: RmvErrMsg
*
* procedure function: Remove program messages wrapper 4 QMHRMVPM API.
* Usually used to clear previously displayed
* error messages.
* RmvErrMsg prototype
d RmvErrMsg PR
d PRmvId 10 const options(*nopass)
P RmvErrMsg B export
d RmvErrMsg PI
d RmvId 10 const options(*nopass)
d DefRmvMsg s 10 inz(‘*ALL ‘)
d CallStack s 10 inz(‘* ‘)
d CallStackC s 9b 0 inz(1)
d RmvKey s 4
d RmvMsg s 10
D ErrorDs DS INZ
D BytesProv 1 4B 0 inz(116)
D BytesAval 5 8B 0
D MessageId 9 15
D Err### 16 16
D MessageDta 17 116
*
c if %parms = 1
c move RmvId RmvMsg
c else
c move DefRmvMsg RmvMsg
c endif
C CALL ‘QMHRMVPM’
C PARM CallStack
C PARM CallStackC
C PARM RmvKey
C PARM RmvMsg
C PARM ErrorDs
c return
P RmvErrMsg E
Figure 2: The SndErrMsg procedure makes it easy for you to display helpful error messages to users.
Figure 3: The RmvErrMsg procedure deletes error messages after the user has seen them.
LATEST COMMENTS
MC Press Online