Communicate with your system and other users.
A letter, a telegram, a postcard, a scribbled note in a memo pad, a spoken sentence--they're all messages in one form or another. A message is a form of one-way communication between two entities. Even the command "Sit!" you give your dog is a message.
Messages don't always involve words. When you touch the handle of a hot kettle, the skin in your fingertips sends a top-priority signal to your spine, which fires back a command to remove your hand immediately. Your brain becomes aware of it only after the fact.
Computer systems use messages to send information between their different modules, and the AS/400 is no exception; as a matter of fact, it uses messages much more extensively than other midrange computer systems.
This is the first of two articles in which we'll study AS/400 messages in detail. In Part 1, I'll define the terms and explain the basic concepts, laying the foundation for Part 2, where I'll explain how two or more programs can communicate using messages and give a summary of the commands that manipulate messages.
The Importance of Messages
The AS/400 uses messages for many purposes; consider the job log, for instance. If you execute the Display Job Log (DSPJOBLOG) command, you'll see that the entire history of your job is recorded as messages. Even the commands you typed at the command line are messages.
Messages let the system communicate with you in your own language. These messages are not expressed in binary or hexadecimal or mnemonic representations of machine instructions--they're in plain English. It's true that some messages are rather obscure, but you can't blame the computer for that; it's the designers of OS/400 (or CPF) who worded the messages your computer sends to you.
Messages can be used to let two users communicate through the computer. Using this feature, one user can leave a message to another user to say that a requested report is waiting in the Inventory Control office, or that program compiler listings are accumulating in the Computer Room and please drag yourself this way to pick them up before we throw them away.
In most cases the recipient of the message is not ready to read the message the moment it's sent. The recipient may be busy doing something else, or may not even be active on the system. To solve this problem, messages are always sent to some sort of message queue. Message queues come in two flavors: permanent and job-related.
Permanent message queues are the ones you can create, change and delete with the CRTMSGQ, CHGMSGQ and DLTMSGQ commands. They are objects (type *MSGQ) in the system.
There are a number of permanent message queues already created when the system is shipped to you. QSYSOPR (the system operator's message queue) is probably the best known. The system also creates a permanent message queue for each display, printer and user profile. By default the user has the same name as the user profile.
Job-related message queues are more difficult to explain. Each time a job begins, the system creates an external message queue (usually referred to as *EXT) which is the primary means of communication between the job and the outside world. In addition, the system can create a separate message queue for each program in the invocation stack if a program message is sent to them- -these are called program message queues.
For a job that runs interactively, *EXT is the display station of the user running the job. If the job needs to report an abnormal situation to you, it sends a message to *EXT. If you need to take some action or issue another command to the job, your keyboard writes the message to *EXT.
If the job runs in batch, *EXT is redirected to QSYSOPR's message queue. In this case, however, you're not allowed to issue additional commands or take any special actions unless the message sent from the job to you specifically asks for a reply.
Types of Messages
All messages are not created equal. Messages have different purposes, and each purpose requires a different message type. When you send messages (or design a program that sends a message), it is of the utmost importance that you use the correct type of message. Let's see what these message types are:
Informational (*INFO): The message is meant to give a piece of information only. An informational message is of little or no consequence. It doesn't require the receiver to take an immediate action or send a reply, or even to acknowledge receipt of the message. Send informational messages when circumstances don't require you to send any other type of message.
Inquiry (*INQ): An inquiry message requires a reply. The sender of the message (either a user or one of your programs) may need to make a decision beyond its capabilities. Consider the printer writer, for instance. It's a program. Your printer has been printing standard forms all day long, but suddenly a spool file shows up with special forms PURCHORDER. The printer writer is faced with a decision it can't make. Should it continue printing on the same form, or should it stop? It has no way of knowing if the form PURCHORDER is installed on the printer or if the standard paper is still there. Consequently, it sends an inquiry message to ask for instructions. Your reply to an inquiry message is also a message (type *RPY).
Reply (*RPY): When a person or program sends an inquiry (*INQ) message to you and you reply to that message, the system is in reality sending a reply message (*RPY) linked to the original inquiry message. Reply messages cannot be sent independently.
Status (*STATUS): Status messages report the progress of an ongoing task. Status messages are customarily sent to *EXT, but they can be sent to another program message queue just as well. When status messages are sent to *EXT for an interactive job, the messages appear at the bottom line of the display, in high intensity.
Diagnostic (*DIAG): Diagnostic messages are used to inform the requester that a minor problem has been found--a problem that the program has been able to solve by itself, but that nonetheless should be recognized. For example, your CL program may attempt to delete file WORK in QTEMP, but it shouldn't stop if the file isn't there. When the program attempts to delete the file but can't find it, it doesn't have to come to a screeching halt--it can send the minor problem type message (*DIAG) to let the requester know that the file wasn't there but that this didn't stop the program.
If you send an *INFO or *DIAG message to *EXT, the message appears in the Program Messages panel and the program stops running until you press Enter. *STATUS messages, on the other hand, do not prevent the program from continuing.
Escape (*ESCAPE): If the problem is serious enough, the program should abend (abend is short for abnormal end). If your CL program abends, it must send an escape message to its caller (the program that called it, or you yourself, if you initiated the program manually). When the caller gets the escape message it will know for sure that there was something seriously wrong, since that's the purpose of the escape message.
Escape messages are usually preceded by a series of diagnostic messages which actually tell the caller what went wrong, but this is not mandatory. The escape message itself can contain that information in its text.
The program that sends the escape message ends immediately. The program that receives the escape message can monitor for it (with the MONMSG command, explained later). If it doesn't monitor for the escape message, the program that receives the message receives a function check message (CPF9999).
Completion (*COMP): A completion message is the opposite of an escape message; it tells the caller that the task completed successfully. Completion messages are not required at the end of each successful job, and it is probably a bad practice to force your programs to always send completion messages when they run okay. Send completion messages only to confirm that an important (critical) task is done.
Notify (*NOTIFY): Notify messages are similar to escape messages because they too inform the caller that there's been a serious problem. The difference is that escape messages describe an unrecoverable error, while notify messages usually describe an error that can be corrected.
The program sending the notify message ends immediately. The program receiving the notify message can monitor for the notify message and thus take corrective action. If no monitoring is taking place, the message is handled automatically by the system and processing continues.
Request (*RQS): Request messages consist of complete command strings, such as CRTCLPGM PGM(QGPL/SAMPLE) SRCFILE(QGPL/QCLSRC) USRPRF(*OWNER) that can be interpreted by the system to start some kind of activity.
Request messages can only be used by CL programs that specifically receive them to execute a command. Because their usefulness is limited, you should not have to use request messages in your everyday CL programming projects.
There's another way to categorize messages besides by type. There are impromptu messages and predefined messages.
An impromptu message is a free-format message you compose the moment you need to send it. Impromptu messages provide complete flexibility since you can select whatever wording you desire. They're good for sending conversational messages to other users or to report successful completion of a job. In other words, you can use impromptu messages to send *INFO or *COMP messages.
A predefined message is not free-format. As the name implies, it has been defined previously and its wording is thereby fixed. If you define a message that reads "File not found," that message will always say "File not found." Predefined messages are great for most types of messages, such as *DIAG and *ESCAPE. Because their wording is fixed, the user always sees the same message. The message is also much easier to maintain since it is defined only once.
This consistency makes the system friendlier to use. If you always use impromptu messages in your programs, your programs may be issuing messages saying "File not found," "File does not exist," "File could not be found," "File/library name mismatch," and "File not contained in the library list" to name just a few.
Predefined messages exist in an object called a message file (type *MSGF) which you can create with the Create Message File (CRTMSGF) command, change with CHGMSGF or delete with DLTMSGF.
Each predefined message is stored in the message file as a message description. You can write new message descriptions with the Add Message Description (ADDMSGD) command, change them with CHGMSGD or remove them from the message file with RMVMSGD. You can also perform all these functions using the Work with Message Descriptions (WRKMSGD) panel.
Although the CRTMSGF command contains a parameter to specify what size it should have, don't bother changing the default values. Using the defaults, your message file will expand as more space is needed so you don't have to worry about allocating initial space to it. Be aware that if you specify a maximum size for your message file and that size is reached, you cannot use CHGMSGF to allocate more space to it. Avoid this problem at all costs.
Anatomy of a Message Description
Each predefined message needs a separate message description. Message descriptions can contain variable data, which means that you don't have to add a separate message description for every conceivable idea your messages must convey. For example, you do not have to define two message descriptions to say "File CUSTOMER not found" and "File VENDOR not found." One message description can take care of both of them. You'll see how this is accomplished a little later.
A message description is comprised of several parts. When you add a message description with the ADDMSGD command, these parts become parameters. I'm including the name of the parameter in parentheses.
Message ID (MSGID): A unique identification for the message description within the message file. Message IDs must have seven characters; the first three must be letters and are usually used to identify the product or application, such as INV for inventory. The last four must be a hexadecimal value between 0000 and FFFF, but you can use decimal values if that suits you better (0000 to 9999).
Message IDs don't have to be unique throughout your system. You can have two (or more) predefined messages with the same message ID, provided that they're in different message files. This, however, is not recommended.
Avoid using the letters CPF at the beginning of the message ID, since these are used for system messages. Other letters you should not use are CPA to CPZ, MCH, KBD, RPG, etc. Also avoid defining messages that end in 00 or 0000. Message IDs ending in 00 or 0000 have special meanings to the MONMSG command.
Message File (MSGF): When you run ADDMSGD, you assign the new message description to a specific message file by entering the message file's name in this parameter. The name can be qualified to a library name.
Message Text (MSG): All predefined messages must contain some text. This text can have a maximum of 132 characters and can include substitution variables, which are coded as an ampersand (&) followed by a number between 1 and 99.
For instance, the message text could be "Program &1 not found in the library list." &1 is a substitution variable which must be defined in the FMT parameter described later.
Second Level Text (SECLVL): You can enter an optional second-level text for the message, which can have a maximum length of 3000 characters. This text appears if the user presses the Help key while the message is displayed. It also appears in the job log if your job is logging second-level text. The second-level text can also contain substitution variables, which can in fact be the same as those presented in the message text.
Further, second-level text can take advantage of special formatting options which you select by entering &N, &P or &B in the second-level text (followed by a blank space).
&N forces the text that follows to a new line, beginning on column 2. If the text that follows doesn't fit in one line, the next lines are indented to column 4.
&P works like &N, except that the new line begins on column 6, and following lines are indented to column 4.
&B also works like &N, but the new line begins on column 4 and the next lines are indented to column 6.
Severity Code (SEV): Assign a severity code to the message by entering a two- digit number between 00 and 99. The higher the number, the more severe the message. IBM uses the severity codes listed in 1 on page 90.
Severity Code (SEV): Assign a severity code to the message by entering a two- digit number between 00 and 99. The higher the number, the more severe the message. IBM uses the severity codes listed in Figure 1 on page 90.
Substitution Variable Format (FMT): Each numbered substitution variable (&1, &2 and so on) must have an entry in the FMT parameter to define the variable. FMT is a list-within-list parameter that accepts one element per substitution variable. Each element is, in turn, another list containing the type of data and length.
For example, suppose you want to add a message description to say the following:
Member &1 in file &2 contains &3 records.
Variable &1 can contain a name or special value *FIRST, so we have to assign it type *CHAR. Variable &2 can only contain a name, so it's safe to assign it type *NAME. Both &1 and &2 are 10 bytes long. Variable &3 can only contain numeric data, so we assign it *DEC, with a length of (10 0). This makes the FMT parameter look like this:
FMT((*CHAR 10) (*NAME 10) (*DEC 10 0))
The ADDMSGD command parameters I have presented are the most important ones. Other parameters are TYPE, LEN, VALUES, SPCVAL, RANGE, REL and DFT, which let you define the replies that the predefined message should expect if sent as an *INQ (inquiry) message.
There are even more parameters, which are used even less. For information about these parameters, see the Programming: Control Language Reference, SC41-0030.
Monitoring For Messages
Messages are of different types and can be issued anytime. Some messages, such as an *ESCAPE message, cause trouble if the circumstance that causes them is not foreseen.
If you have reasons to believe that your CL program is going to be issued an escape message, you can use the Monitor Message (MONMSG) command to inform the system that you're expecting the message, that it's okay, and that the CL program should perform a certain action should the message arrive. To use the MONMSG command you must include it in your CL program as one of its statements.
There are two ways to use the MONMSG command. The first one requires you to code the MONMSG command immediately after the command you expect will cause the problem. For example, suppose that at some point your program must create a data area in QTEMP. If the data area already exists in QTEMP, the CRTDTAARA command in your CL program would cause an *ESCAPE message because it cannot carry out its function. This message is CPF1023. If the data area already exists, you want the CL program to clear it to blanks with the CHGDTAARA command. 2 shows how you can code this.
There are two ways to use the MONMSG command. The first one requires you to code the MONMSG command immediately after the command you expect will cause the problem. For example, suppose that at some point your program must create a data area in QTEMP. If the data area already exists in QTEMP, the CRTDTAARA command in your CL program would cause an *ESCAPE message because it cannot carry out its function. This message is CPF1023. If the data area already exists, you want the CL program to clear it to blanks with the CHGDTAARA command. Figure 2 shows how you can code this.
The MONMSG command has an EXEC parameter where you code the command you want to execute if the message(s) listed in the MSGID parameter are received. I coded a DO to start a group of statements (ended with ENDDO) just for clarity, but I could have coded the CHGDTAARA command directly into the EXEC parameter.
I'll call this the command-level MONMSG, since it applies only to one command. There's another way to code MONMSG: at the program level. You code the MONMSG immediately after the last DCL or DCLF command (if any are present). The program-level MONMSG (as I choose to call it) applies to the entire program. If any command within the program produces the messages monitored by the program-level MONMSG, the message is covered and the MONMSG command takes over.
MONMSG has a quirk, however. If you specify a message ID (in the MSGID parameter) that ends in 00 or 0000, MONMSG covers all messages that end in 00 through FF or in 0000 through FFFF. For example, monitoring for CPF1200 means that all messages between CPF1200 and CPF12FF are monitored. Similarly, monitoring for CPF0000 monitors for all CPF messages.
Program-level MONMSG can only have a GOTO in the EXEC parameter; you cannot code anything else. This creates a restriction and, for that reason, the program-level MONMSG should be used only to trap serious errors that can cause the CL program to abend. Most CL programmers use program-level MONMSG to monitor for CPF0000, which would trap any error message beginning in CPF that was not specifically monitored at the command level. See 3.
Program-level MONMSG can only have a GOTO in the EXEC parameter; you cannot code anything else. This creates a restriction and, for that reason, the program-level MONMSG should be used only to trap serious errors that can cause the CL program to abend. Most CL programmers use program-level MONMSG to monitor for CPF0000, which would trap any error message beginning in CPF that was not specifically monitored at the command level. See Figure 3.
Note the placement of the MONMSG command. It's right after the DCLs and before the real meat and potatoes of the CL program. It monitors for CPF0000 (any CPF message) and if any such message arrives, it transfers control to tag ERROR.
At tag ERROR, the RCVMSG command receives a message of type *EXCP (exception, meaning either *DIAG or *ESCAPE) and places the message data in &MSGDTA, the message ID in &MSGID, and the message file name and library in &MSGF and &MSGFLIB. The SNDPGMMSG command that follows sends that same message back to the caller. This process is called forwarding messages and it must be part of any CL program that uses a program-level MONMSG for CPF0000. Next month's article, "AS/400 Messages: Part II" will provide a utility command to simplify this tedious coding.
To obtain a list of the messages the MONMSG command can monitor, refer to the CL Reference Guide, SC21-8104, Volume 1, Appendix E (for machines running V1R3 or prior), or the Programming: Reference Summary, SX41-0028, Chapter 7 (for V2R1 or later).
Finally, you should also know that having a program-level MONMSG does not prevent you from using command-level MONMSGs elsewhere in the same program. The command-level MONMSG is evaluated first. If it doesn't cover the message that was issued, the program-level MONMSG is examined.
The System Reply List
OS/400 provides a function called the System Reply List. Even though it's probably an object somewhere in QSYS, it cannot be treated as one because it can't be created, changed or deleted, and the DSPOBJD command has no object type code for it (it should have been *SYSRPYL or *RPYL if existent).
The system reply list is a list of entries you can maintain. For each entry there's a message ID, optional comparison data and the default reply for the message. For example, the Initialize Tape (INZTAP) command issues message CPA4278 when CHECK(*YES) is specified if the tape contains active files. CPA4278 is an inquiry message and it offers options C (cancel) and I (ignore).
If you specify CHECK(*YES) and always want INZTAP to end if there are any active files, you can add an entry to the system reply list with the Add Reply List Entry (ADDRPYLE) command, as follows:
ADDRPYLE SEQNBR(100) + MSGID(CPA4278) + RPY(C)
Each entry has a unique sequence number (SEQNBR parameter). When adding an entry you must make sure that the number you assign hasn't been used.
Similarly, you can remove an entry from the system reply list with the Remove Reply List Entry (RMVRPYLE) command, or work with all entries with the Work with Reply List Entries (WRKRPYLE) command.
Now you must activate the system reply list for your job, since the system reply list is not used unless you ask the system to use it. To activate the system reply list, you must run the Change Job (CHGJOB) command as follows:
This change can also be performed in a job description, running the Change Job Description (CHGJOBD) command and again specifying INQMSGRPY(*SYSRPYL).
The system reply list is a great feature, but it isn't without risks. To see why, remember that there is only one system reply list. You cannot customize it to your job's specific needs, but it must at all times contain entries for all the messages that everyone on your system wants to be covered. Consequently, when you activate the system reply list, you may be activating automatic message replies for more messages than you intended. Always keep this in mind.
You are perhaps better off using the system reply list sparingly and using MONMSG whenever possible. MONMSG is predictable and applies only to your job. The system reply list can be unpredictable and affects the entire system.
Next month's issue will include the conclusion of this series, where I'll explain the mechanics of inter-program communications using messages. Two utilities will offer a hands-on approach to messaging. The first describes the usage of message subfiles in application programs. The second presents a utility command to forward program messages more easily. Stay tuned!
AS/400 Messages: Part I
Figure 1 Severity codes for message descriptions
Figure 1: Severity Codes for Message Descriptions Code Description 00 Information. No error detected, no reply needed. 10 Warning. There's something that could be interpreted as an error. 20 Error. There has been an error condition, but the system was capable of performing automatic recovery. 30 Severe error. There has been an error condition but the system could not perform any automatic recovery. 40 Program or function abended. 50 Job abended. 60 System status or warning issued to QSYSOPR. 70 Device integrity warning. 80 System alert. There's a situation that could prevent the system from functioning. 90 System integrity. There's a situation that has rendered a subsystem or the system inoperative. 99 Manual action required, such as changing forms on a printer or tapes on a tape drive.
AS/400 Messages: Part I
Figure 2 Command-level MONMSGFigure 2: Command-level MONMSG CRTDTAARA DTAARA(QTEMP/X) TYPE(*CHAR) LEN(10) MONMSG MSGID(CPF1023) EXEC(DO) CHGDTAARA DTAARA(QTEMP/X *ALL) VALUE(' ') ENDDO
AS/400 Messages: Part I
Figure 3 Program-level MONMSGFigure 3: Program-level MONMSG PROGRAM: PGM PARM(&A &B &C) DCL VAR(&A) TYPE(*CHAR) LEN(10) DCL VAR(&B) TYPE(*DEC) LEN(5 0) DCL VAR(&C) TYPE(*LGL) DCL VAR(&MSGDTA) TYPE(*CHAR) LEN(132) DCL VAR(&MSGF) TYPE(*CHAR) LEN(10) DCL VAR(&MSGFLIB) TYPE(*CHAR) LEN(10) DCL VAR(&MSGID) TYPE(*CHAR) LEN(7) MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR)) /* Program begins */ . . . /* Program ends */ RETURN ERROR: RCVMSG MSGTYPE(*EXCP) MSGDTA(&MSGDTA) MSGID(&MSGID) + MSGF(&MSGF) MSGFLIB(&MSGFLIB) SNDPGMMSG MSGID(&MSGID) MSGF(&MSGFLIB/&MSGF) + MSGDTA(&MSGDTA) MSGTYPE(*ESCAPE) ENDPGM