Retrieve textual information from message files.
In the December 2010 "API Corner" article, "Still Using Compile-Time Arrays?", we looked at one approach to removing compile-time arrays containing textual information from a program. The text removed was being used to display the status of an order (order canceled, order shipped, etc.) to the user of an inquiry program. In this article, we will review one way to access the text that is to be displayed by the use of a message file (*MSGF) and a procedure being exported by a message support service program (*SRVPGM).
Before doing that, though, several alternative approaches were proposed by readers of the first article. In the previous article, I had simply replaced references to the compile-time array with messaged IDs. That is, where the original program had coded…
Status = Txt(1);
…I replaced the line with…
RtvMsgTxt(Status :%size(Status) :'DSP0100');
…where RtvMsgTxt is a procedure that returns the first %size(Status) bytes of the first-level text for message ID DSP0100 to the variable Status. Rory H., in a posting located here, proposed two alternative approaches that, while maintaining the RtvMsgTxt implementation we'll be looking at shortly, could simplify the changes to the program. The first alternative is to not remove the compile-time array containing the order status text, but to instead change the contents of the array so that the message IDs are now being accessed. That is, use an approach such as this:
…
dTxt s 7 dim(8) ctdata
…
select;
// Provide text description of order status
when OrdSts = 'P';
RtvMsgTxt(Status :%size(Status) :Txt(1));
…
**CTDATA Txt
DSP0100
…
The second proposal is to use an alternating array as shown below, replacing the select group with a lookup operation.
…
dOrdStsVal s 1 dim(7) ctdata
dOrdStsMsg s 7 dim(7) alt(OrdStsVal)
dX s 10i 0
…
X = %lookup(OrdSts :OrdStsVal);
if X > 0;
RtvMsgTxt(Status :%size(Status) :OrdStsMsg(X));
else;
RtvMsgTxt(Status :%size(Status) :'DSP0107');
endif;
…
**CTDATA OrdStsVal
PDSP0100
…
Depending on the application, you might choose to use any of the three approaches, as each has its pros and cons, with all three enabling you to remove textual information from the source of the application program.
Going a step further are proposals from CJeffries and JVoris, using a separate program or a SQL user-defined function, respectively, to determine the order status text. That is, using approaches such as these:
RtvOrdStsTxt(Status :%size(Status) :OrdSts);
or
Exec SQL Select … OrdStsTxt(ord.OrdSts),…
into … :Status,…
from … OrdMstr ord…
Or possibly, in order to isolate order status information entirely from the application, simply passing an order number as in the following:
RtvOrdStsTxt(Status :%size(Status) :OrderNbr);
or
Exec SQL Select … OrdStsTxt(ord.OrderNbr),…
into … :Status,…
from … OrdMstr ord…
These last proposals, based on RtvOrdStsTxt and OrdStsTxt, would require us to write additional programs/procedures/functions that are not provided in the current article, but these functions would not be difficult to implement and may be just the ticket for some of your application programs.
I would be remiss if I didn't also point out that JVoris, in the posting found here, had concerns about using a *MSGF to store the order status text. While the concerns may be quite valid for some companies, this article will continue to use *MSGFs and the APIs related to their use. This is, after all, the API Corner!
Having seen how we might use the RtvMsgTxt procedure in the first few examples, let's now look at the procedure itself. The heart of the procedure is the Retrieve Message (QMHRTVM) API, which is documented here.
The QMHRTVM API defines 13 parameters, the last three of which are optional.
Required Parameter Group:
1 |
Message information |
Output |
Char(*) |
2 |
Length of message information |
Input |
Binary(4) |
3 |
Format name |
Input |
Char(8) |
4 |
Message identifier |
Input |
Char(7) |
5 |
Qualified message file name |
Input |
Char(20) |
6 |
Replacement data |
Input |
Char(*) |
7 |
Length of replacement data |
Input |
Binary(4) |
8 |
Replace substitution values |
Input |
Char(10) |
9 |
Return format control characters |
Input |
Char(10) |
10 |
Error code |
I/O |
Char(*) |
Optional Parameter Group:
11 |
Retrieve option |
Input |
Char(10) |
12 |
CCSID to convert to |
Input |
Binary(4) |
13 |
CCSID of replacement data |
Input |
Binary(4) |
If you've been following my columns for long, you should readily recognize the purpose of the first several parameters. The Message information parameter is the receiver variable where the API will return message description information, and the Length of message information parameter is the allocated size of the Message information parameter.
Format name is the format of the information to be returned. The QMHRTVM API supports four formats, RTVM0100 through RTVM0400, and we'll be using the RTVM0100 format. This format returns everything we need for the RtvMsgTxt function introduced in the previous article. And, generally, the lower the number of the format, the faster the API runs.
The Message identifier and Qualified message file parameters, respectively, specify the message ID to be returned and the message file containing the message ID.
The next three parameters can be used to replace substitution variables numbers (that is, variables such as &1 and &2 that are found in the retrieved message text) with replacement data specified by the program (for instance, the name of the department holding the order). We will not be using this capability in today's example, but the ability to directly merge program data with message text can greatly simplify the display of textual information in some situations (such as text translated to various national languages, where sentence structure can differ significantly across spoken languages).
The ninth parameter, Return format control characters, allows you to control whether or not formatting controls found in the message text, such as &N for new line, should be returned. In our current use of the QMHRTVM API, we will have the API remove these controls from the text returned to the application program.
The tenth parameter is the standard API error code.
The first optional parameter, Retrieve option, will not be used by our sample program but will show up in a future article. The Retrieve option parameter allows you, through special values, to access message text without knowing the message ID in advance. There are special values to return the *FIRST message ID found in a message file (in which case the Message identifier parameter is ignored) and also the *NEXT message ID found following the message identified by the Message identifier parameter.
Using the QMHRTVM API, prototyped below, we can write the following procedure. The module is named MSGSPT, for Message Support, and our plan is to use this module to create a *SRVPGM (also named MSGSPT).
h nomain
dRtvMsgTxt pr
dMsgRcv 65535a options(*varsize)
dLenMsgRcv 10i 0 const
dMsgID 7a const
dRtvMsgAPI pr extpgm('QMHRTVM')
d MsgInfo 65535a options(*varsize)
d LenMsgInfo 10i 0 const
d Format 8a const
d MsgID 7a const
d QualMsgF 20a const
d RplDta 65535a const
d LenRplDta 10i 0 const
d RplOpt 10a const
d RtnFmtOpt 10a const
d ErrCde likeds(QUSEC)
d RtvOpt 10a const options(*nopass)
d ToCCSID 10i 0 const options(*nopass)
d RplDtaCCSID 10i 0 const options(*nopass)
/copy qsysinc/qrpglesrc,qmhrtvm
/copy qsysinc/qrpglesrc,qusec
pRtvMsgTxt b export
d pi
dMsgRcv 65535a options(*varsize)
dLenMsgRcv 10i 0 const
dMsgID 7a const
dRcvVar ds qualified
d API likeds(QMHM010004)
d MsgDta 4096a
dMsgF s 20a
/free
QUSBPrv = 0;
// Get the message file that corresponds to the MessageID prefix
select;
when ((%subst(MsgID :1 :3) = 'CPF') or
(%subst(MsgID :1 :3) = 'MCH'));
MsgF = 'QCPFMSG *LIBL';
when %subst(MsgID :1 :3) = 'DSP';
MsgF = 'TEXTMSGS *LIBL ';
other;
MsgF = 'DEFAULT *LIBL ';
endsl;
// Get the message text
RtvMsgAPI(RcvVar :%size(RcvVar) :'RTVM0100'
:MsgID :MsgF
:' ' :0 :'*NO' :'*NO' :QUSEC);
// Return the first level text
%subst(MsgRcv :1 :LenMsgRcv) =
%subst(RcvVar.MsgDta :1 :RcvVar.API.QMHLMRTN02);
/end-free
p e
The module uses /copy to access the QSYSINC RPGLE include members for QMHRTVM and the API error code structure QUSEC. The QMHRTVM member provides a data structure definition for format RTVM0100, named QMHM010004. This structure defines the fixed fields Bytes returned through Length of message help available that are found in the RTVM0100 format. This structure is then used (with LIKEDS) in defining our receiver variable RcvVar. As the message text starts immediately after the Length of message help available field, the sample program also defines the subfield MsgDta following the LIKEDS reference to QMHM010004. The size of MsgDta, 4096 bytes, is quite arbitrary; it just needs to be large enough to hold the largest message text that might be returned (assuming that we want access to all of the message text).
I will point out that this approach—defining the MsgDta subfield immediately after the base definition of the format—is generally not the way that APIs work when returning variable-length data (and message text is certainly variable length). Most APIs provide an offset to variable-length data, and any program accessing the variable-length data must use the provided offset value if you want to avoid rude surprises after installing a new release or even applying a PTF. For instance, QMHRTVM's format RTVM0300 provides the field Offset of message at decimal offset 64 of the format. Anytime an API provides an offset value, you should use it. In the case of format RTVM0100, a rather old format dating back to V2R1.1, there is no offset, so we can safely assume that the message text will always be returned immediately following Length of message help available. This assumption, however, is not valid in the majority of cases.
The actual processing done by the program is quite simple. After setting the API error code structure Bytes provided field to return escape messages in the case of a failure, and determining the correct message file to use when locating the message (based on the message ID prefix in the sample program), the program calls the API to retrieve the message text associated with message ID MsgID. The program then substrings the returned value—for up to the length returned (field QMHLMRTN02 of the RcvVar data structure), into the first parameter passed to the RtvMsgTxt procedure—for up to the length of the first parameter (LenMsgRcv), and returns to its caller. Whichever value is smaller, QMHLMRTN02 or LenMsgRcv, will determine the actual number of bytes returned to the caller of RtvMsgTxt (and in the case of QMHLMRTN02 being less than LenMsgRcv, blank pad MsgRcv to LenMsgRcv). The use of these two length fields is critical when moving data from the QMHRTVM receiver variable, RcvVar, to the caller's receiver variable (Status in the sample program calling RtvMsgTxt). Both of these parameters are prototyped as being 65535 bytes in length with options(*varsize). You do not want the procedure attempting to copy more data than is actually being returned and/or allocated.
To create the MSGSPT module and service program you can use the following commands:
CRTRPGMOD MODULE(MSGSPT)
CRTSRVPGM SRVPGM(MSGSPT) EXPORT(*ALL)
To create a binding directory, MSGSPTBD, and an entry for the MSGSPT *SRVPGM, you can use these:
CRTBNDDIR BNDDIR(MSGSPTBD)
ADDBNDDIRE BNDDIR(MSGSPTBD) OBJ((MSGSPT))
Compiling the program provided in the last article, and included again here, with the following command…
CRTBNDRPG PGM(RTVMSGD)
h dftactgrp(*no) bnddir('MSGSPTBD')
dRtvMsgTxt pr
dMsgRcv 65535a options(*varsize)
dLenMsgRcv 10i 0 const
dMsgID 7a const
dOrdSts s 1
dStatus s 25
/free
// Read a record containing order status (OrdSts)
select;
// Provide text description of order status
when OrdSts = 'P';
RtvMsgTxt(Status :%size(Status) :'DSP0100');
when OrdSts = 'O';
RtvMsgTxt(Status :%size(Status) :'DSP0101');
when OrdSts = 'C';
RtvMsgTxt(Status :%size(Status) :'DSP0102');
when OrdSts = 'H';
RtvMsgTxt(Status :%size(Status) :'DSP0103');
when OrdSts = 'I';
RtvMsgTxt(Status :%size(Status) :'DSP0104');
when OrdSts = 'R';
RtvMsgTxt(Status :%size(Status) :'DSP0105');
when OrdSts = 'S';
RtvMsgTxt(Status :%size(Status) :'DSP0106');
other;
RtvMsgTxt(Status :%size(Status) :'DSP0107');
endsl;
// Do processing, including output of
// order status in textual form
*inlr = *on;
return;
/end-free
…you have now externalized the human-readable text of the application program from the program itself. If tomorrow the users want a change to the text of an order status, you can simply change the message description and you're done. No searching for programs to change, no program source changes to make and test, and no compiles.
Next month, we'll look at a few other ways to use the QMHRTVM API. In the meantime, if you have any API questions send them to me at
as/400, os/400, iseries, system i, i5/os, ibm i, power systems, 6.1, 7.1, V7,
LATEST COMMENTS
MC Press Online