Program messages are a fundamental part of the operating system, and the more you know about them the better.
RPG has come a long way from its original, standalone report-generation days. We can integrate features from many other languages and from the operating system itself via simple calls. That additional functionality, though, leads to additional complexity. Many times, errors occur under the covers of our RPG programs, and while often it's enough just to know an error occurred (say on a file open), sometimes it's important to know the details of the error. The IBM i has a powerful messaging system that allows you to get that information, but you need to access it. This is the first in a series of tips that will help you do just that.
Getting the Last Error Message
In this tip, we'll extract the information from the most recent error message. This technique can be used whenever an error occurs. First, be sure to capture the error and prevent it from causing a hard halt. You can do this with the (e) extender on the opcode or by wrapping the operation in a monitor block. You can even go old-school in RPG III and use an error indicator on the operation.
When an error occurs, the error message is logged in the program message queue. You see the contents of that queue whenever you display the job log for a running job, or in the joblog spooled file that is created when the job ends. While those somewhat indirect methods are the usual ways we see program messages, we can also access the messages in the queue directly using standard IBM i commands, and this article shows you how to do it.
Before I get to the code, though, we need to take a closer look at the IBM i's messaging system and specifically at how messages are defined and presented to the user. One way to think of a message is as a form letter. Each message has a key (a seven-character message ID) and then it has message text. Most messages also have substitution variables, which act as holding areas in the message text for dynamic data to be passed in when the message is sent. At presentation time, the variable data is merged into the static message text to create the final message. For example, error message CPF0001 has the text "Error found on &1 command" along with a single substitution variable. The variable is a character field with a length of 10, which is perfect for holding a command name.
Go to an IBM i command line and type the command JOEPLUTA. Assuming you don't have a command named JOEPLUTA on your system, you will see two messages:
Command JOEPLUTA in library *LIBL not found.
Error found on JOEPLUTA command.
The first is message CPD0030, while the second is the CPF0001 message I already mentioned. When the operating system sees that the JOEPLUTA command is not found, it sends out both messages. When it sends out the CPF0001 message, it includes the command name as part of the message data. The command line processor then merges the text and presents it to you.
That merged text is for human consumption. But the underlying architecture of using message data makes it very easy for programs to access the original information and interact with those messages. Imagine if we had to find the program name from the actual test displayed. It's not particularly hard, but this is a simple message with only one parameter. And even this one can get difficult; imagine if you're running on a Spanish-language machine. Any hardcoding you did for "Error found on" now goes out the window.
So, the idea is to be able to extract the message ID and the unformatted message data. Today's tip goes that first step by giving you a simple CL program that retrieves the ID and message data for the most recent message. You might be thinking to yourself that we've gone a long way into the tip without seeing the code, but that's because the code is so very simple:
GETPGMMSG.CL
GETPGMMSG: PGM (&MSGID &MSGDTA)
DCL &MSGID *CHAR 7
DCL &MSGDTA *CHAR 500
RCVMSG PGMQ(*PRV) MSGQ(*PGMQ) MSGTYPE(*LAST) RMV(*NO) +
MSGDTA(&MSGDTA) MSGID(&MSGID)
ENDPGM
That's it. You call this program with two parameters, a seven-character message ID and a 500-character message data parameter. The length of the message data parameter is arbitrary; you can use whatever best suits your environment. Then, whenever an error occurs, just call the program. Here's a rather contrived example:
d x s 3u 0
d y s 3u 0
d GETPGMMSG pr extpgm('GETPGMMSG')
d oMsgid 7a
d oMsgdta 500a
d msgid s 7a
d msgdta s 500a
/free
x = 0;
monitor;
y = (1 / x);
on-error;
GETPGMMSG( msgid: msgdta);
endmon;
*inlr = *on;
/end-free
Run this program in debug and step through. Since we're dividing by zero, you'll find yourself executing the on-error clause, which in turn calls the GETPGMMSG utility. This is a very simple case; the error received (MCH1211) is one of those rare messages that has no parameters at all. It basically just says that divide by zero occurred, and that's all you need to know. So if you step through the call you'll see that afterward the variable msgid contains the value MCH1211. And that shows that the utility works as designed!
It's kind of funny, actually, that the test program has more lines than the utility itself, but that's sometimes the case and especially in this case where I've shown you only a very limited use of the program message APIs; in subsequent tips, we'll extend this utility quite a bit.
Enough for now. Start playing!
LATEST COMMENTS
MC Press Online