Programming / RPG
Message descriptions are one of the unsung heroes of IBM i application development, and this article shows you how to use RDPi to manage them with ease.
The message description is unique to the IBM i. The concept is simple: a seven-character key provides a link to a fully featured message, which supports both a short version suitable for presentation on a user display and a long message with enough information to provide full diagnostic and help capabilities. Such a basic idea, yet message descriptions provide a wide range of capabilities. For example, messages are contained in a message file,m and internationalization is as simple as supplying a different message file for the locale. The i had i18n before i18n was cool!
Using Message Fields Today
Messages are incredibly feature-rich. You can design a message with replacement variables and also design a data record that is passed to the message at run time. This message data is then parsed when the message is displayed, and the variable data is inserted into the message. Here's a simple example in which the customer number and name is displayed as an informational message:
Customer &1, name &2.
The message is further defined to identify the size of the two variables, &1 and &2, and then when the application program sends the message to the user, it passes a data structure with the appropriate values and the message is displayed with the data in place. Given the power of messages, it may seem surprising that they're not universally used. But I think that message files are a little bit like externally described printer files: the pain of the standard tooling is just enough to prevent the adoption of the tool. The good news is that, like printer files, the Rational tooling provides easier-to-use capabilities to get you over the hump and using messages in your everyday programming. Not only that, but the message file capabilities are present all the way back to WDSC, so even folks who haven't made the move to the newer Rational tool sets can still use these techniques (that's unfortunately definitely not the case with printer files).
Figure 1: The CHGMSGD command is not the most user-friendly command in the i toolkit. (Click images to enlarge.)
The problem with message files lies in their maintenance. Take a look at Figure 1. The CHGMSGD command is almost useless for maintenance. The keyword values throughout the command are not filled in with the current definitions from the message; they instead only support the dreaded *SAME keyword, which means you have to key in the whole new value. This is not only annoying for things like text, but also almost impossible to use when your message data has many variables; you effectively have to key in all the variables every time you make a change. And I'm not just complaining to hear myself talk: check out the CHGUSRPRF command. Execute CHGUSRPRF on a user profile to which you have authority (such as your own) and hit F4; you'll see that most of the values are pre-filled. CHGMSGD just doesn't work that way.
Figure 2: WRKMSGF has a little more functionality.
The WRKMSGF command gives you a little better interface. It's still rather labor-intensive, as you can see in Figure 2; you have to first call up the message file, then position to the message description, and then finally maintain the message. It does pre-fill the values for the keywords based on the current message definition, a feature that is absolutely essential when maintaining messages with many message data fields. But it's far from an easy experience, and making any sort of wholesale changes is out of the question.
The other problem is that a message file is like a number of other objects in the system, particularly non-program objects such as output queues and subsystem descriptions, in that it does not have a source member that is compiled to create the object. You create these objects by executing commands from the command line. Since command-line changes are ad hoc, you always have the chance of somebody making a change that wipes out yours—or even deleting the object—and then your changes are lost. This argues for a practice I try to implement at all my client sites: I always have a source file that contains all the commands used in defining or modifying system objects.
RDPi and Message Files
So how does RDPi make message file management easier? Well, at first glance, you might think that Remote Systems Explorer (RSE) has your back, but unfortunately that turns out not to be quite true.
Figure 3: RSE has some nice inquiry capabilities for message files.
Figure 4: But the maintenance capabilities are the same as the CHGMSGD, which is to say not very good.
Looking at Figure 3, you can see that the Remote System Explorer (RSE) will show you the message file and the messages in it. You can even initiate a change using the right-click context menu. However, the change isn't particularly user-friendly. RSE uses its built-in ability to execute any IBM i command. Unfortunately, that ability is limited by the features of the command being executed, which, in the case of the CHGMSGD, are somewhat underwhelming, as we've already seen. But even if the RSE change feature was more powerful, I still wouldn't recommend it, because of the ad hoc nature of the change. Instead, I recommend a source-file approach. In my library, I created source file QSRC1009 with a CLP member MYMSGF. Here's the source for that member:
DCL &MSGF *CHAR 10 VALUE('MYMSGF')
DCL &MSGFLIB *CHAR 10 VALUE('MCP')
RMVMSGD MSGID(CUS0001) MSGF(&MSGFLIB/&MSGF)
ADDMSGD MSGID(CUS0001) MSGF(&MSGFLIB/&MSGF) +
MSG('Customer &1, name &2.') +
FMT((*DEC 6 0) (*CHAR 30))
RMVMSGD MSGID(CUS9001) MSGF(&MSGFLIB/&MSGF)
ADDMSGD MSGID(CUS9001) MSGF(&MSGFLIB/&MSGF) +
MSG('No customer number passed.')
RMVMSGD MSGID(CUS9002) MSGF(&MSGFLIB/&MSGF)
ADDMSGD MSGID(CUS9002) MSGF(&MSGFLIB/&MSGF) +
RMVMSGD MSGID(CUS9999) MSGF(&MSGFLIB/&MSGF)
ADDMSGD MSGID(CUS9999) MSGF(&MSGFLIB/&MSGF) +
MSG('Feature not yet implemented.')
This is the standard template I use for all "message file compile" programs. I put the name of the message file and library into variables at the top of the program; that way, I don't have to change every line of the source if the message file name or library needs to change. I do a RMVMSGD and an ADDMSGD; this works whether the message exists or not. The MONMSG CPF2419 ignores any errors if I execute a RMVMSGD on a message that doesn't exist. Use RDPi to maintain the file; it's very easy to copy and paste an existing message description definition to create a new one.
You'll note that I did just that: I added a new message that doesn't appear in Figure 3, message ID CUS9999. Next I have to compile the program. However, this program is a little different than others; it's a setup program rather than an application program. Unlike application programs, which you only need to compile, you have to compile a setup program and then run it. The compile part is easy in RDPi; options include pressing Ctrl+Shift+C when you're in the editor and right-clicking on the member in the RSE view and selecting Compile. But running a program from RDPi is not always the easiest. You can run any command by right-clicking on the Objects subsystem and selecting Run Command or by using the command field in the Commands Log tab, and these are both options I'll address a little more another day. Unfortunately, they both suffer from the same flaw: you have to key in the entire command, such as CALL MYMSGF. This is tedious and error-prone, being subject to the whims of both typographical errors and library list malfunctions.
To close this article, I would like to show you one extra feature.
You can run the program in one of two ways. The first is straightforward: expand your library and look for the program. When you find it, right-click on it and select Run As > IBM i application in RSE Job, as shown in Figure 5. It's easy, but it does require looking for the program. I have one other little trick. If your source file is in the same library as your setup program (not an unreasonable notion, especially for these sorts of programs), you can set up a User Action. Figure 6 shows the definition of the action, while Figure 7 shows how to use the action.
Figure 5: The simplest way to run a program is to right-click on the program object and select Run As.
Figure 6: A more convenient way, though, is to use a User Action, like the one shown here.
Figure 7: Then you can right-click on the source member and invoke the User Action to call the program.
You might wonder why I create the user action when the Run As option is perfectly adequate. I do it because it allows me to keep my focus on the source file. Libraries tend to get cluttered with objects, and having to search for the appropriate program to run can require enough scrolling to make things cumbersome; in Figure 5 I had to scroll all the way to the top of the library to find the program. Instead, with the user action, I do everything from the source file: I double-click on the source member to edit it, make my changes, hit Ctrl+Shift+C to save and compile the member, and then right-click on the same source member to run the resulting program. It's simple and quick.
In fact, I hope the technique is quick enough that those of you not using message files might be willing to give it another shot. And at the same time, I hope the message file mavens among us have learned a little something from this article as well. I may devote another article to the fine art of defining and naming messages and using them in conjunction with another IBM i exclusive: the message subfile.
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7,