Most AS/400 programmers are familiar with message descriptions and message files, which provide a centralized repository to store messages used by programs to communicate with users. In many AS/400 shops, message descriptions also store text presented on screens and reports. The use of message descriptions, rather than hard-coded text, in programs facilitates the use of common text, phrases, and terms, which helps ensure a more consistent user interface.
Message descriptions can be used in several ways. The most common use for message descriptions is to store messages used to communicate errors and status. Application programs send these messages using CL commands or message APIs. The best way to display messages is in a message subfile. (For more information on sending messages and using message subfiles, see RPG Building Blocks: ILE Message Handling in the November 1998 issue of Midrange Computing.) Another way to use message descriptions is to store constant text used on screens, printer files, and command prompts. To use message descriptions to store constant screen or printer text, you use the DDS MSGCON keyword, supplying a message ID. When defining a command, you can supply a message ID in place of hard-coded prompt text.
Many international companies have to support multiple languages. One good way to separate language-specific information from applications is to use message descriptions. Message files are set up for each language supported. A translator familiar with the applications then duplicates and translates messages into language-specific message files. Each languages message file contains messages sent to users as well as messages containing constant screen and printer text. You can use the message description support procedures provided with this article to extend multilanguage support to applications that do not use DDS to define their user interface.
Even though messages and message descriptions are a central part of the AS/400, the tools provided to maintain message descriptions lack one important feature: the ability to copy message descriptions. In organizations that use message descriptions, the ability to copy messages can save time and help ensure consistent messages. If your company uses message descriptions to provide support for multiple languages, the ability to copy a message can be especially useful, particularly when message descriptions contain replacement values. I originally wrote the message description and message file support toolkit that accompany this article to provide better support for message descriptions in
applications. Soon after, I used the toolkit to write the Copy Message Description (CPYMSGD) command presented here. Both the toolkit and command are now an integral part of my development toolset.
A Couple of New Tools
The Message Description Toolkit (MSGDTKT) procedures make it easier to incorporate message description support into your applications. This toolkit contains two procedures. The first is the Retrieve Message Description (RTVMSGD) subprocedure, which the CPYMSGD command uses. The second is the Retrieve Message File Attributes (RTVMSGFA) subprocedure. These procedures are easier to use than a direct call to the system APIs because they hide some of the underlying APIs complexity by providing default values, parsing return values, and handling errors.
The RTVMSGD subprocedure returns information about a message description using the Retrieve Message (QMHRTVM) API. There are two ways that this subprocedure is used. The first way retrieves information about a particular message description. You can also use RTVMSGD to retrieve information for multiple message descriptions by specifying *FIRST or *NEXT for the message ID to retrieve. The QMHRTVM API returns message description information in a single return value. Length and offset values at the beginning of the return value describe several variable-length values returned by the API. RTVMSGD uses these lengths and offsets to extract variable-length values returned by the API.
The other toolkit subprocedure, RTVMSGFA, returns message file information. Some of the message file attributes are message file text, the current file size, and the coded character set ID (CCSID). RTVMSGFA calls the Retrieve Message File Attributes (QMHRMFAT) API to retrieve this information.
Copying Message Descriptions
The CPYMSGD command combines API calls with a command interface to provide functionality missing from the AS/400. This command prompts for a copy-from message ID and message file as well as a target message ID and message file. When the CPYMSGD command runs, a validity check program first ensures that the parameters are valid. Next, the CPYMSGD command processing program (CPP) retrieves information about the message being copied and uses this information to prompt the Add Message Description (ADDMSGD) command.
The first thing I do when I create a new command is enter the command definition source and then create the command. Figure 1 (page 112) shows the source for the CPYMSGD command definition. Next, I prompt the command and create the panel group help text to better understand how the command will work. The panel group help text included with this command has the look and feel of a native AS/400 command. You can download the source for the CPYMSGD command, including the panel group help text, from www.midrangecomputing.com.
Once the command interface and help text is complete, the next step is to write the CPP. Figure 2 (page 113) shows the source for the CPYMSGD CPP, which is a CL module. The CPYMSGD CPP first calls the RTVMSGD subprocedure from the MSGDTKT to retrieve the copy-from messages description. This call illustrates a problem with one-character return variables when CL modules call RPG IV subprocedures. To get around this problem, the CL program passes a two-character return variable and uses the %SST built-in to extract the first character.
The next step that the CPYMSGD CPP does is build an ADDMSGD command string using the copy-from message descriptions information and selective prompt characters. To prompt the command string, the Check Command Syntax (QCMDCHK) API is used. This API returns the ADDMSGD command string along with any changes made during prompting. During the call to QCMDCHK, a monitor message for CPF6801
allows the program to determine if prompting was cancelled with F3 or F12. If the user did not cancel, the returned command string runs using a call to the Execute Command (QCMDEXC) API.
Validity check programs check command prompt values for errors before a commands CPP is called. Figure 3 shows the validity check program CPYMSGDV for the CPYMSGD command. This program checks to see that the copy-from message ID exists, the message files exist, and the copy-from and copy-to values differ. If an error is found, an error flag is set and a diagnostic-type message is sent to the validity check program. After checking all of the parameters for errors, the validity check program receives and returns any diagnostic messages to the calling program using the message ID CPD0006. Next, the validity check program sends CPF0002 as an escape message to indicate that a problem has occurred.
Why Didnt IBM Do That?
Rather than lament the fact that IBM did not anticipate the needs of every user, you can use APIs to create support for features that IBM did not predict. The AS/400 provides hundreds of APIs that you can use to build support for these missing features. Access to these APIs is easier than ever from ILE programs written in RPG. If you use a command interface for this support and follow system conventions, your enhancements will integrate seamlessly with the operating system. To me, the comment When did IBM add this command to the operating system? is the highest form of compliment.
Storing the text and messages used in your applications in message descriptions can help you create a more consistent interface for your applications. Message descriptions are also a good way to support multiple languages in AS/400 applications. The procedures found in the message description toolkit use encapsulation to hide the underlying complexity of the supported APIs. These toolkit procedures make it easier to use message descriptions in applications that do not use DDS.
REFERENCES AND RELATED MATERIALS
AS/400 Information Center Overview 5 (SC41-5315-01, QA3AIC01)
OS/400 CL Programming 4R5 (SC41-5721-03, CD-ROM QB3AUO03)
CMD PROMPT(Copy Message Description)
PARM KWD(FROMMSGID) TYPE(*NAME) LEN(7) MIN(1) +
FULL(*YES) EXPR(*YES) PROMPT(Message +
identifier)
PARM KWD(FROMMSGF) TYPE(QMSGF) MIN(1) +
CHOICE(*NONE) PROMPT(Message file)
PARM KWD(TOMSGID) TYPE(*NAME) LEN(7) +
DFT(*FROMMSGID) SPCVAL((*FROMMSGID +
*FROMMS)) FULL(*YES) EXPR(*YES) +
PROMPT(To message identifier)
PARM KWD(TOMSGF) TYPE(QTOMSGF) DFT(*FROMMSGF) +
SNGVAL((*FROMMSGF)) CHOICE(*NONE) +
PROMPT(To message file)
QMSGF: QUAL TYPE(*NAME) LEN(10) MIN(1) EXPR(*YES)
QUAL TYPE(*NAME) LEN(10) DFT(*LIBL) +
SPCVAL((*LIBL) (*CURLIB)) EXPR(*YES) +
PROMPT(Library)
QTOMSGF: QUAL TYPE(*NAME) LEN(10) EXPR(*YES)
QUAL TYPE(*NAME) LEN(10) DFT(*LIBL) +
SPCVAL((*LIBL) (*CURLIB)) EXPR(*YES) +
PROMPT(Library)
PGM (&FromMsgID &FromMsgF &ToMsgID &ToMsgF)
Figure 1: To create the new command, first enter the Copy Message Description command definition source.
DCL &FromMsgID *CHAR LEN(7) /* Message ID */
DCL &FromMsgF *CHAR LEN(20) /* Message File */
DCL &ToMsgID *CHAR LEN(7) /* To message ID */
DCL &ToMsgF *CHAR LEN(20) /* To Message File */
DCL &FromMsgFN *CHAR LEN(10) /* Message File Name */
DCL &FromMsgFL *CHAR LEN(10) /* Message File Library */
DCL &ToMsgFN *CHAR LEN(10) /* Message File Name */
DCL &ToMsgFL *CHAR LEN(10) /* Message File Library */
DCL &RtnMsgID *CHAR LEN(7) /* Returned Message ID */
DCL &MsgTxt *CHAR LEN(134) /* Variable Message text */
DCL &MsgTxtLen *DEC LEN(5) /* Message text length */
DCL &SecLvl *CHAR LEN(3002) /* Variable Second level text */
DCL &SecLvlLen *DEC LEN(5) /* Second level text length */
DCL &Fmt *CHAR LEN(792) /* Variable format */
DCL &Len *CHAR LEN(198) /* Variable length */
DCL &Dec *CHAR LEN(198) /* Variable decimal pos */
DCL &Sev *DEC LEN(2) /* Message severity */
DCL &Ele *DEC LEN(5) /* Element */
DCL &StrLen *DEC LEN(5) /* String length */
DCL &Off *DEC LEN(5) /* Offset */
DCL &Off2 *DEC LEN(5) /* Offset */
DCL &Tst *CHAR LEN(2) /* Test character */
DCL &DecVal5 *DEC LEN(5) /* Decimal value */
DCL &DecAlp5 *CHAR LEN(5) /* Alpha decimal value */
DCL &DecVal2 *DEC LEN(2) /* Decimal value */
DCL &DecAlp2 *CHAR LEN(2) /* Alpha decimal value */
DCL &Cmd *CHAR LEN(4000) /* Command to execute */
DCL &ErrFlg *CHAR LEN(2) /* Return error flag */
DCL &Msgid *CHAR LEN(7) /* Status message */
DCL &MsgF *CHAR LEN(10) /* Status message */
DCL &MsgFLib *CHAR LEN(10) /* Status message */
DCL &MsgDta *CHAR LEN(60) /* Status message */
DCL &Qte *LGL /* Quote flag */
DCL &EscFlg *LGL /* Escape flag */
MONMSG CPF0000 EXEC(GOTO Error)
CHGVAR &FromMsgFN %SST(&FromMsgF 1 10)
CHGVAR &FromMsgFL %SST(&FromMsgF 11 10)
IF (&ToMsgID = '*FROMMS') THEN(DO)
CHGVAR &ToMsgID &FromMsgID
ENDDO
IF (&ToMsgF = '*FROMMSGF') THEN(DO)
CHGVAR &ToMsgFN &FromMsgFN
CHGVAR &ToMsgFL &FromMsgFL
ENDDO
ELSE DO
CHGVAR &ToMsgFN %SST(&ToMsgF 1 10)
CHGVAR &ToMsgFL %SST(&ToMsgF 11 10)
ENDDO
CALLPRC PRC(RtvMsgD) PARM(&FromMsgID &FromMsgFN &FromMsgFL *NULL *YES +
&RtnMsgID &MsgTxt &SecLvl &Fmt &Len &Dec &Sev)+
RTNVAL(&ErrFlg)
IF (%SST(&ErrFlg 1 1) = '1') THEN(GOTO Error)
CHGVAR &MsgTxtLen %BIN(&MsgTxt 1 2) /* Set length of varying parameter */
CHGVAR &SecLvlLen %BIN(&SecLvl 1 2)
CHGVAR &DecAlp2 &Sev /* Convert to alpha */
/* Build required part of add message command */
CHGVAR &Cmd ('ADDMSGD +
?-MSGF(' *CAT &ToMsgFL *TCAT '/' *CAT &ToMsgFN *TCAT ') +
MSGID(' *CAT &ToMsgID *TCAT ') +
MSG(''' *CAT %SST(&MsgTxt 3 &MsgTxtLen) *TCAT ''') +
SEV(' *CAT &DecAlp2 *CAT ')')
/* Add second level text if any is found */
IF (&SecLvlLen *NE 0) THEN(DO)
CHGVAR &Cmd (&Cmd *BCAT +
'SECLVL(''' *CAT %SST(&SecLvl 3 &SecLvlLen) *TCAT ''')')
ENDDO
/* Add message data fields if any are found */
IF (%SST(&Fmt 1 8) *NE ' ') THEN(DO)
CHGVAR &Ele 1 /* Inz element for format variables */
CHGVAR &Cmd (&Cmd *BCAT 'FMT(')
NextFmt:
CHGVAR &Off (&Ele * 8 - 7) /* Set off to format variable */
IF ((&Off < 792) *AND (%SST(&Fmt &Off 8) *NE ' ')) THEN(DO)
CHGVAR &Cmd (&Cmd *TCAT '(' *CAT %SST(&Fmt &Off 8))
CHGVAR &Off (&Ele * 2 - 1) /* Set off to length variable */
CHGVAR &DecVal5 %BIN(&Len &Off 2)
CHGVAR &DecAlp5 &DecVal5
CHGVAR &Cmd (&Cmd *BCAT &DecAlp5)
CHGVAR &Off (&Ele * 2 - 1) /* Set off to decimal variable */
CHGVAR &DecVal2 %BIN(&Dec &Off 2)
CHGVAR &DecAlp2 &DecVal2
CHGVAR &Cmd (&Cmd *BCAT &DecAlp2 *TCAT ')')
CHGVAR &Ele (&Ele + 1) /* Next format variable */
GOTO NextFmt
ENDDO
CHGVAR &Cmd (&Cmd *TCAT ')')
ENDDO
GOTO RepApost /* Replace apostrophe */
Retry:
CHGVAR &CMD ('?' *CAT &CMD)
CALL PGM(QCMDCHK) PARM(&CMD 4000)
MONMSG CPF6801 EXEC(GOTO Resend) /* F3/12 pressed */
CALL PGM(QCMDEXC) PARM(&CMD 4000)
MONMSG CPF0000 EXEC(DO)
RCVMSG MSGTYPE(*LAST) MSGDTA(&MsgDta) +
MSGID(&MsgID) MSGF(&MsgF) +
MSGFLIB(&MsgFLib) RMV(*YES)
SNDPGMMSG MSGID(&MsgID) MSGF(&MsgFLib/&MsgF) +
MSGDTA(&MsgDta) TOPGMQ(*EXT)
RCVMSG PGMQ(*EXT) MSGTYPE(*LAST) RMV(*YES)
GOTO Retry
ENDDO
/* No errors end program. */
GOTO CMDLBL(Resend)
/* Replace single with double apostrophe. */
RepApost:
CHGVAR &Off 1
NextPos:
CHGVAR &Tst %SST(&Cmd &Off 2)
/* Set flag if in quoted string */
IF (&Tst = '(''') THEN(DO)
CHGVAR &Off (&OFF + 2) /* Skip this quote */
CHGVAR &Qte '1'
ENDDO
ELSE DO
IF (&Tst = ''')' *AND &Qte) THEN(CHGVAR &Qte '0')
ENDDO
/* Set flag at end of quoted string */
/* Replace apostrophe with two appostrophies */
IF (&Qte *AND %SST(&Cmd &Off 1) = '''') THEN(DO)
CHGVAR &Off (&Off + 1)
CHGVAR &Off2 (&Off + 1)
CHGVAR &StrLen (4000 - &Off)
CHGVAR %SST(&Cmd &Off2 &StrLen) (%SST(&Cmd &Off &StrLen))
CHGVAR %SST(&Cmd &Off 1) ''''
ENDDO
/* Next position */
CHGVAR &Off (&Off + 1)
/* Not end of command */
IF (&Off < 4000) THEN(GOTO NextPos)
GOTO Retry
/* Errors, resend messages and end program. */
Error:
IF &EscFlg THEN(GOTO Escape)
CHGVAR &EscFlg '1' /* Severe errors */
Resend:
CALLPRC PRC(MovMsg)
Escape:
IF &EscFlg DO
CALLPRC PRC(SndEscMsg) PARM('CPF0001' 'CPYMSG')
MONMSG CPF0000 /* To prevent endless loop */
ENDDO /* &EscFlg */
ENDPGM
PGM (&FromMsgID &FromMsgF &ToMsgID &ToMsgF)
DCL &FromMsgID *CHAR LEN(7) /* Message ID */
DCL &FromMsgF *CHAR LEN(20) /* Message File */
DCL &ToMsgID *CHAR LEN(7) /* To message ID */
DCL &ToMsgF *CHAR LEN(20) /* To Message File */
DCL &EscFlg *LGL /* Escape flag */
DCL &FromMsgFN *CHAR LEN(10) /* Message File Name */
DCL &FromMsgFL *CHAR LEN(10) /* Message File Library */
DCL &ToMsgFN *CHAR LEN(10) /* Message File Name */
DCL &ToMsgFL *CHAR LEN(10) /* Message File Library */
DCL &RtnMsgID *CHAR LEN(7) /* Message ID */
Figure 2: The Copy Message Description command processing program is a CL module.
DCL &MsgIDErr *CHAR LEN(2) /* Return error flag */
DCL &ErrFlg *LGL /* Error found */
DCL &EscFlg *LGL /* Escape processing */
DCL &Msg *CHAR LEN(132) /* Error message */
DCL &RtnType *CHAR LEN(2) /* Message return type */
DCL &KeyVar *CHAR LEN(4) /* Message key */
CHGVAR &FromMsgFN %SST(&FromMsgF 1 10)
CHGVAR &FromMsgFL %SST(&FromMsgF 11 10)
IF (&ToMsgID = '*FROMMS') THEN(DO)
CHGVAR &ToMsgID &FromMsgID
ENDDO
IF (&ToMsgF = '*FROMMSGF') THEN(DO)
CHGVAR &ToMsgFN &FromMsgFN
CHGVAR &ToMsgFL &FromMsgFL
ENDDO
ELSE DO
CHGVAR &ToMsgFN %SST(&ToMsgF 1 10)
CHGVAR &ToMsgFL %SST(&ToMsgF 11 10)
ENDDO
CHKOBJ OBJ(&FromMsgFL/&FromMsgFN) OBJTYPE(*MSGF)
MONMSG CPF0000 EXEC(DO)
CHGVAR &ErrFlg '1'
ENDDO
IF (*NOT &ErrFlg) THEN(DO) /* Message file OK, check the ID */
CALLPRC PRC(RtvMsgD) PARM(&FromMsgID &FromMsgFN &FromMsgFL *NULL *YES +
&RtnMsgID) RTNVAL(&MsgIDErr)
IF (%SST(&MsgIDErr 1 1) = '1' *OR &RtnMsgID *NE &FromMsgID) THEN(DO)
CHGVAR &ErrFlg '1'
ENDDO
ENDDO
CHKOBJ OBJ(&ToMsgFL/&ToMsgFN) OBJTYPE(*MSGF)
MONMSG CPF0000 EXEC(DO)
CHGVAR &ErrFlg '1'
ENDDO
IF ((&FromMsgFL *EQ &ToMsgFL) *AND (&FromMsgFN *EQ &ToMsgFN)) +
THEN(DO)
CHGVAR &ErrFlg '1'
SNDPGMMSG MSGID(CPF9897) MSGF(QCPFMSG) TOPGMQ(*SAME) +
MSGDTA('From and to message file and library must be +
different.') MSGTYPE(*DIAG)
ENDDO
/* No errors end program. */
IF (*NOT &ErrFlg) THEN(GOTO EndPgm)
/* Errors, resend messages and end program. */
Error:
IF &EscFlg THEN(GOTO Escape)
CHGVAR &EscFlg '1' /* Severe errors */
CHGVAR &KEYVAR '*TOP'
Resend:
RCVMSG MSGTYPE(*NEXT) MSGKEY(&KeyVar) KEYVAR(&KeyVar) MSG(&Msg) +
RTNTYPE(&RtnType)
IF (&RtnType *NE ' ') DO
IF ((&RtnType *EQ '02') *OR +
(&RtnType *EQ '04') *OR +
(&RtnType *EQ '15')) THEN(DO) /* *DIAG,*INFO,*ESCAPE */
SNDPGMMSG MSGID(CPD0006) MSGF(QCPFMSG) MSGDTA('0000' *CAT &Msg) +
MSGTYPE(*DIAG)
ENDDO
GOTO Resend /* Get next message */
ENDDO
Escape:
IF &EscFlg DO
CALLPRC PRC(SndEscMsg) PARM('CPF0002')
MONMSG CPF0000 /* To prevent endless loop */
ENDDO /* &EscFlg */
EndPgm: ENDPGM
LATEST COMMENTS
MC Press Online