ILE Message Handling
You can produce reusable components that enhance your applications and simplify programming by applying object-oriented (OO) concepts to common programming tasks and taking advantage of the ILE environment. Applications created from integrated components are easier to create and maintain.
The message-handling components described in this article are part of a toolkit of RPG IV subprocedures that send, receive, forward, and define messages. This article describes components that support sending messages, receiving messages, traversing the call stack, and interpreting an API error.
The procedures of the message toolkit can save you many hours when you code message routines because the complex interface to the message APIs is hidden behind the toolkits simpler interface. These procedures are designed to be flexible and easy to use.
Before using the message toolkit, you should evaluate your environment and decide where the toolkit fits in. You may want to look at the toolkit procedures and change the way that default values are derived to better fit your environment. If you are not using message subfiles in your applications, you may want to consider using them. You may want to set up environment variables in your initial program(s) that supply default values. You may also find it useful to add your own procedures to the message toolkit. If you are using, or have considered using, messages in your RPG IV programs, you will find that implementing the toolkit procedures is a good starting point.
The message toolkit procedures are interdependent. When a message procedure needs to access the function of another message procedure, it always uses the interface provided by the toolbox rather than calling the underlying function directly. Because toolkit components can call each other and themselves, recursive calls are possible. These procedures use dynamic and automatic storage to preserve the values of variables if they are called recursively.
I define one prototype per module. I also give the prototype the same name as the module. To prevent name collisions, I define prototypes in a source file named QPROTOSRC. Preceding each prototype is a description of the parameters defined by the prototype. Constant parameters are defined under the heading Input; return variables are defined under the heading Return. Any other types of parameters are defined under the heading Input/Output or Output. The parameter descriptions include all of the information needed to use the prototypes procedure. (The code for this utility can be found at MCs Web site at http:/ /www .midrangecomputing.com/mc/98/11. Editors Note: Click the icon on this page to download related article code.)
Several of the parameters received by the toolkit procedures use the keyword *VARSIZE. In order to determine the actual length of the parameter passed, a call is made to the Display Operational Descriptors (CEEDOD) API. Operational descriptors describe the length and attributes of parameters being passed. Several restrictions apply when using operational descriptors: The parameter passed cannot be an array, a data structure, or a table. For omissible parameters, a similar call is made to the Test for Omitted Arguments (CEETSTA) API. This API returns a 0 if the parameter was omitted and a 1 if the parameter was passed.
When you use an API, you should define the parameters passed to the API based on definitions brought in using /COPY from the source in library QSYSINC. This step ensures that the attributes of the parameters match, and that the procedure will operate correctly on future releases. QSYSINC is an optionally installable library and must be installed on your machine before you can compile the message toolkit procedures. When installing, choose use GO LICPGM. Select Install Licensed Programs, and then install OS/400System Openness Includes.
While testing the Message Toolkit procedures, I discovered I could not send messages from one V4R2 system. Comparing the Cum level and PTFs on this system with those on a system that worked, I discovered that PTF SF47432 was missing. This PTF corrects parameter-handling errors for constant parameters. After loading SF47432, the Message Toolkit was able to send messages.
The SndMsg Procedure
According to the book Design Patterns: Elements of Reusable Object-Oriented Software, a Facade defines a higher-level interface that makes using the subsystem easier to use. SndMsg, the first message toolkit component, provides a Facade for the Send Message APIs. This Facade simplifies the sending of messages by providing defaults for any parameters not passed. You can modify the default values specified to match your own environment.
When sending messages in the ILE environment, it is helpful to understand what a call queue is and how it relates to a program queue. Call queues are ILE job-stack entries that consist of procedures, modules, and programs with name lengths up to 256 bytes. Program queues are Original Program Model (OPM) job-stack entries that consist of a 10- byte program name. If you have procedure names that are used in more than one module or program, you may need to qualify the procedure name. The PgmQQ parameter of the SndMsg procedure allows you to do this.
One of the specifications that you may need to customize is the location to which to send a message if no queue is specified. In the environment Im describing, the name of the default queue is retrieved from two data areas created in a jobs initial program and stored in QTEMP. The first data area contains the name of the procedure or program; the second data area contains the queue qualifier. The default for interactive jobs is to send two messages: The first message is sent to the initial programs call message queue; the second message is returned to the caller for diagnostic purposes. This default helps to locate the procedure that sent a message, which can be very difficult in a system built from many reusable procedures. The second message is never removed, which allows support staff to see a history of messages sent for the job. The default for batch jobs is to send a single message to the caller.
While struggling to come up with an easy way to determine where application messages should be sent, I had a revelation: Why not just use a single queue for all messages? Using a single queue (a program queue for OPM; a call queue for ILE) for all user interactions in interactive jobs has several advantages. The main advantage is that it simplifies message processing. Message queues do not have to be passed between component procedures (a stack counter is not needed, either). Using a single queue also facilitates sending messages to programs that are not yet active. This works very well when a common process is used to set up work areas prior to calling a program that displays the results. To display messages from the common queue, display programs set the message subfiles SFLPGMQ value to this queue. They then write the message subfile control format to display any pending messages. After messages are displayed from the common queue, the queue is cleared.
In the ILE environment, messages can be sent to a Call Message queue. Call Message queues correspond to the programs or procedures that have been called. You can display the Call Message queues for a job by displaying the jobs stack using the WRKJOB OPTION(*PGMSTK) command. To see a Call Message queue name in its entirety, you need to press F11 twice. Each entry on this display represents a Call Message queue. If an entry is a procedure, a module and program qualifier will be shown. Programs or procedures that have been called more than one time will have multiple Call Message queues. The first procedure called for a program represents a program boundary, and the first procedure called in an ILE activation group represents a control boundary. To send a message to one of these queues (via either this utility or a CL command), use the special values *PGMBDY and *CTLBDY.
To display messages from a program call queue, the subfile program queue needs to be defined with a length of 276. This allows a procedure name of up to 256 characters with a module and program qualifier. The DDS example shows how to define a long program queue name. Remember that program messages sent to a program boundary will go to the programs entry point procedure. For CL programs, this is _CL_PEP; for RPG IV programs, it is _QRNP_PEP_ followed by the module name. You can determine a programs entry-point procedure using DSPPGM to get the program entry procedure module. Then get the entry-point procedure for that module using DSPMOD specifying DETAIL(*PROCLIST).
Another specification that you may want to customize is how the default message file name is derived. The SndMsg procedure assumes that user messages will be found in QUSRMSG and that user messages will not start with CPF, MCH, CPA, SQL, CEE or RPG. If you use any of these prefixes in your messages, you may want to make the message file parameter required for all but user messages.
It is not very useful in an ILE environment to send messages by level. Program entry-point procedures are inserted by the ILE compilers. Application programs have no way of knowing where entry-point procedures exist on the job stack. The Ignore Program Entry Points parameter allows you to bypass program entry-point procedures when sending a message by level. When a message is sent by level up the stack, and this parameter is passed as *ON, the SndMsg procedure adds 1 to the message level for each program entry point found between the current level and the targeted level. Program entry point procedures are identified by the underscore ( _ ), which can be found in the first position of their names.
The RmvMsg Procedure
The Remove Message (RmvMsg) procedure provides a Facade for the Remove Program Messages (QMHRMVPM) API. This procedure is usually called in interactive programs to remove messages after they have been displayed in a message subfile. The program queue parameter can be used to indicate that messages are to be removed from a named program queue, the external queue, or a relative queue. The special value *ALLINACT can be passed in the program queue parameter, which causes all messages from inactive programs to be removed.
In addition to specifying where messages are removed, parameters can be passed to RmvMsg that allow a single message to be removed by key, or a group of messages to be removed by type. The groups of messages that can be removed are request messages, new unreceived messages, old received messages, and scope messages. Another parameter determines whether unhandled exception messages are removed. If no parameters are passed to the RmvMsg procedure, all messages in the default program queue are removed.
The RcvMsg Procedure
The Receive Message (RcvMsg) procedure allows messages to be retrieved from a call queue or from the external queue. The format of the returned data is determined by the format passed to RcvMsg. Because the format of the returned data can change from release to release, you should use the /COPY directive to retrieve member QMHRCVPM from file QSYSINC/QRPGLESRC. You wont have to recompile this procedure every time you install a new release, but using the /COPY directive will ensure that you always have the latest definition of this structure when you do recompile.
Data should be accessed using the offsets provided in the QMHRCVPM definition. The toolkit procedure RtvStkEnt provides an example of using the returned offsets. The position of the program queue and program queue qualifier is determined by using the length of the data passed from format QMHM0300 to locate the start of the sender information. The format of the sender information is defined in QMHRS. The structure of QMHM0300 and QMHRS is defined in the QMHRCVPM copy member. RcvMsg allows a message type and key to be specified. These two parameters work together to determine what message will be returned. A message key can be specified along with a message type of *TOP, *NEXT, or *PRV. If *NEXT or *PRV are passed, you can specify *LOVAL for the message key on the first receive, and the returned key on subsequent receives. To receive a message from a program that is not currently active on the job stack, specify * for the queue and 0 for the level, along with a message key.
This procedure can be used to receive a reply to an inquiry message sent using SndMsg. To receive a reply, you need the message key for the message that was sent. The message key is the value returned from the SndMsg procedure. After an inquiry message is sent, pass the returned message key to the RcvMsg procedure and specify type *RPY. You can adjust the time allowed to enter a reply with the MsgWat parameter that is passed to SndMsg.
The SndAPIErr Procedure
The Send API Error (SndAPIErr) procedure uses the standard error information returned by most APIs to determine whether an APIs function completed successfully. If error data is found, the return value is set to *ON, and the message in the return data is formatted and sent as a message to the caller of the API.
Call SndAPIErr immediately after calling an API. Pass the error information structure used in the call to the API to SndAPIErr. To use this procedure, pass the error structure APIErr to the API.
The format of the APIErr error structure is defined in the /COPY member APIErrDef. One nice feature of RPG IV is its ability to nest /COPY statements. APIErrDef uses /COPY to bring in the standard error definition structure from member QUSEC, which is located in QRPGLESRC in library QSYSINC. APIErrDef renames the QUSEC variables and supplies a 240-byte field for message replacement data. The message information and replacement data are passed to SndMsg to send a program message if error information is available after a call to an API.
The RtvStkEnt Procedure
A call stack is the list of active programs and procedures for a job, with its last entry representing the most recently called program or procedure. The Retrieve Stack Entry (RtvStkEnt) procedure allows you to retrieve the names for entries on the call stack. For an ILE program on the call stack, the procedure, module, and program names are returned. For an OPM program call stack entry, the program name is returned. This procedure is used by the SndMsg procedure to identify program entry-point procedures when messages are sent by level and the Ignore Program Entry Point Procedures parameter is *ON.
The Last Message
Using the message APIs directly can be challenging, but once you have built a Facade that hides their complexity, the message APIs become easy to use. The message toolkit will never be finished; there will always be new components to add. These new components may address new areas or simply combine existing functions to refine the toolkit.
Design Patterns: Elements of Reusable Object-Oriented Software. Gamma, Erich, Richard Helm, Ralph Johnson, and John M. Vlissides. Reading, Massachusetts: Addison- Wesley Computer and Engineering Publishing Group, 1995
OS/400 Message Handling APIs V4R2 (SC41-5862-01, CD-ROM QB3AMN01) System API Programming V4R1 (SC41-5800-00, CD-ROM QB3AVC00)