• The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
Menu
  • The Four Hundred
  • Subscribe
  • Media Kit
  • Contributors
  • About Us
  • Contact
  • Guru: Getting The Message, Part 1

    August 1, 2018 Paul Tuohy

    Author’s Note: This article was originally published in October 2009. Since then I have worked on many modernization projects with many clients and, in every one of those projects, we have used some form of the contents of this (and the following) article. The content of the article has been updated for free form RPG and some of the coding enhancements that have been introduced, into RPG, since the original publication of this piece.

    When we look at modernizing applications (or writing new applications) one of the basic principles is to tier the application — i.e., separate the interface — the business logic and the database processing, the concept being that any of the components can be changed without affecting the others and, more importantly, you can have multiple interfaces making use of the same business logic and database routines.

    All well and good, but there are a couple of minor hiccups that have to be handled. What happens when a business logic or database routine hits an error? How does it notify the interface that an error has occurred?

    In other words, how do we send messages between the different components when the components have no knowledge of each other?

    In our traditional green screen world messaging was tightly integrated between the process logic and the screen. We may have been using the ERRMSG or ERRMSGID keywords in DDS or making use of program message queues and message subfiles. But will a program message queue technique work with a web request or an SQL subprocedure?

    In this article (and a following article), I will take a look at a technique that allows for handling messages no matter what the interface. A library containing the code used in these articles is available for download at http://www.systemideveloper.com/downloads/messagesV2.zip

    But Before We Begin . . .

    I have always been an enormous fan of message files and I intend to keep using them in this “new” structure. I really like the ability of defining second level message text, severity codes and variable parameters.

    But one of the things I don’t like about message files is when I see the message ID hard coded in an RPG program. Of course you have to use the message ID in the program, but I prefer to define my message IDs as named constants and place them in a copy member which is included in every program. Therefore, every program has a list of all available message IDs. The following piece of code shows an example of some of these message IDs. I use the convention (common in most programming languages) of all upper case for constant names.

    dcl-C ERR_NOTFOUND   'ALL9001';
    dcl-C ERR_CHANGED    'ALL9002';
    dcl-C ERR_DUPLICATE  'ALL9003';
    dcl-C ERR_CONSTRAINT 'ALL9004';
    dcl-C ERR_TRIGGER    'ALL9005';
    dcl-C ERR_UNKNOWN    'ALL9006';
    dcl-C ERR_NOT_NUMBER 'ALL9007';
    dcl-C ERR_NOT_DATE   ‘ALL9008';
    

    Storing Messages

    Bearing in mind that each component of our application cannot have any knowledge of another component, it is not possible to send messages between the components.

    Instead, messages are stored and subprocedures are provided to indicate how many messages are currently stored and corresponding subprocedures to retrieve the messages.

    How do we store the messages? First inclinations might lead us towards a message queue or a database, but none of these are necessary. We can simply store our messages in a data structure array. All of the message subprocedures will be coded in a single module and the message format data structure array will be maintained in the same module.

    This is the format of the message data structure. This data structure is defined in a copy member and included in all programs.

     // Format in which error messages are stored.
    dcl-Ds def_MsgFormat qualified template;
      msgId    char(7);
      msgText  char(80);
      severity int(10);
      help     char(500);
      forField char(25);
    end-Ds; 
    

    The message data structure contains the message ID, the first level message text, the message severity, the second level message text, and the name of the field for which the message was stored.

    The Message Module

    Let’s have a look at the global definitions and each of the message subprocedures.

    The global definitions in the message module (shown below) consist of:

    • An array of message formats and a count of how many messages are currently stored. I think 200 messages are more than enough but feel free to increase them as required.
    • A data structure that identifies the name of the message file that contains our messages.
    • Prototypes for the Retrieve Message from Message File (QMHRTVM) and Receive Message from Message Queue (QMHRCVPM) APIs.
    **free
    /include QCpySrc,StdHSpec
    ctl-Opt NoMain;
    
       // To create the required service program...
       //    Current library set to MESSAGES
       //    CRTRPGMOD MODULE(UTILMSGS)
       //    CRTSRVPGM SRVPGM(UTILITY) MODULE(UTIL*)
       //              SRCFILE(UTILITY) TEXT('Utility Procedures')
    
    /include utility,putilMsgs
    
    dcl-Ds messages LikeDS(Def_MsgFormat) dim(200) inz;
    
    dcl-S  msgCount int(10);
    
     // Message File used for retrieving message
    dcl-Ds msgF;
      msgFile    char(10) Inz('APPMSGF');
      msgFileLib char(10) Inz('MESSAGES');
    end-Ds;
    
    // Prototype for QMHRTVM API
    dcl-Pr retrieve_MessageFromMsgF extPgm('QMHRTVM');
      msgInfo           char(3000) options(*varSize);
      msgInfoLen        int(10) const;
      formatName        char(8) const;
      msgId             char(7) const;
      msgF              char(20) const;
      replacement       char(500) const;
      replacementLength int(10) const;
      replaceSubValues  char(10) const;
      returnFCC         char(10) const;
      usec              char(256);
    end-Pr;
    
    // Prototype for QMHRCVPM API
    dcl-Pr receive_Msg extPgm('QMHRCVPM');
      msgInfo      char(3000) options(*VarSize);
      msgInfoLen   int(10)    const;
      formatName   char(8)    const;
      callStack    char(10)   const;
      callStackCtr int(10)    const;
      msgType      char(10)   const;
      msgKey       char(4)    const;
      waitTime     int(10)    const;
      msgAction    char(10)   const;
      errorForAPI  like(APIError);
    end-Pr; 
    

    Clear Messages

    The u_clear_Messages() subprocedure simply does what it says on the box — it clears the message format data structure array and sets the message count to zero.

     //--------------------------------------------------
     // Clear messages
    dcl-Proc u_clear_Messages export;
      dcl-Pi *n end-Pi;
    
      msgCount = 0;
      clear messages;
    
    end-Proc;  
    

    Add Messages

    The u_add_Message() subprocedure adds the required message to the message format data structure array and increments the message count.

     //--------------------------------------------------
     // Add Message - Add a pre-defined message to the message list
     //             - Field Name is optional and may be omitted
     //             - Message data is optional
    
    dcl-Proc u_add_Message export;
      dcl-Pi *n;
        msgId      char(7)   const;
        forFieldIn char(25)  const
                             options(*Omit:*noPass);
        msgData    char(500) const
                             options(*noPass);
      end-Pi;
    
       // Format RTVM0300 for data returned from QMHRTVM
      dcl-Ds RTVM0300 qualified;
        bytesreturned int(10);
        bytesAvail    int(10);
        severity      int(10);
        alertIndex    int(10);
        alertOption   char(9);
        logIndicator  char(1);
        messageId     char(7);
        *n            char(3);
        noSubVarFmts  int(10);
        CCSIDIndText  int(10);
        CCSIDIndRep   int(10);
        CCSIDTextRet  int(10);
        dftRpyOffset  int(10);
        dftRpyLenRet  int(10);
        dftRpyLenAvl  int(10);
        messageOffset int(10);
        messageLenRet int(10);
        messageLenAvl int(10);
        helpOffset    int(10);
        helpLenRet    int(10);
        helpLenAvl    int(10);
        SVFOffset     int(10);
        SVFLenRet     int(10);
        SVFLenAvl     int(10);
      end-Ds;
      //** reserved
      //** defaultReply
      //** message
      //** messageHelp
    
      // Based variable used to retrieve text from RTVM0300
      dcl-S textPtr pointer;
      dcl-S text    char(500) Based(textPtr);
    
      dcl-S repData  char(500);
      dcl-S forField like(forFieldIn);
    
      if %parms() > 2;
        repData = msgData;
      endIf;
    
      if %parms() > 1;
        if %addr(forFieldIn) <> *null;
          forField = forFieldIn;
        endIf;
      endIf;
    
      retrieve_MessageFromMsgF(RTVM0300
                              :%Len(RTVM0300)+350
                              :'RTVM0300'
                              :MsgId
                              :MsgF
                              :RepData
                              :%Len(%Trim(RepData))
                              :'*YES'
                              :'*YES'
                              :APIError);
    
      msgCount += 1;
      messages(msgCount).msgId = msgId;
      messages(msgCount).forField = forField;
      if (APIError.bytesAvail = 0);
         messages(msgCount).severity = RTVM0300.severity;
         if (RTVM0300.messageLenRet > 0);
            textPtr = %Addr(RTVM0300) + RTVM0300.messageOffset;
            messages(msgCount).msgText = %SubSt(text: 1:
                                                RTVM0300.messageLenRet);
         endIf;
         if (RTVM0300.helpLenRet > 0);
            textPtr = %Addr(RTVM0300) + RTVM0300.helpOffset;
            messages(msgCount).help = %SubSt(Text: 1:
                                             RTVM0300.helpLenRet);
         endIf;
      else;
         messages(msgCount).severity = 99;
         messages(msgCount).msgText = '*** Expected Message Not Found ***';
      endIf;
    end-Proc;  
    

    The subprocedure accepts three parameters but only the first (message ID) is required. The second parameter identifies the name of the field to which the message relates and the third parameter contains any variable data for the message.

    The subprocedure uses the QMHRTVM API with the RTVM0300 format to retrieve the indicated message from the message file (identified in the msgF data structure in the global definitions). If message data was supplied on the call to addMessage() then the message data is automatically inserted during the retrieve. As you can see, a little bit of pointer manipulation is required to retrieve the message text and the second level text.

    Of course, the routine checks to ensure that the requested message exists (not that anyone would ever request a non-existent message ID).

    Get Message Count

    The u_message_Count() subprocedure simply returns the number of currently stored messages.

     //--------------------------------------------------
     // Message Count - returns the number of stored messages
    
    dcl-Proc u_message_Count export;
      dcl-Pi *n int(10) end-Pi;
    
      return msgCount;
    end-Proc;  
    
    

    Get A Stored Message

    The u_get_Message() subprocedure retrieves the required message indicted by the first parameter. The data returned is a message format data structure. The subprocedure checks that a valid stored message is being requested.

     //--------------------------------------------------
     // Clear messages
     //--------------------------------------------------
     // Get Message - Get the Message identified by the No.
    
    dcl-Proc u_get_Message export;
      dcl-Pi *n;
        forMessage int(10) const;
        msgFormat  likeDs(def_MsgFormat);
      end-Pi;
    
    
      if forMessage > 0 and forMessage <= msgCount;
        msgFormat = messages(forMessage);
      else;
        clear msgFormat;
        msgFormat.msgText = '*** Message Not Found ***';
      endIf;
      return;
    end-Proc; 
    

    To Be Continued

    In my next article we will see how these message subprocedures may be used in an application and we will also look at a couple of other message subprocedures that may be useful.

    Paul Tuohy, IBM Champion and author of Re-engineering RPG Legacy Applications, is a prominent consultant and trainer for application modernization and development technologies on the IBM Midrange. He is currently CEO of ComCon, a consultancy firm in Dublin, Ireland, and partner at System i Developer. He hosts the RPG & DB2 Summit twice per year with partners Susan Gantner and Jon Paris.

    RELATED STORY

    Getting The Message, Part 1

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    Tags: Tags: 400guruclassic, DDS, FHGC, Four Hundred Guru Classic, IBM i, RPG, SQL

    Sponsored by
    WorksRight Software

    Do you need area code information?
    Do you need ZIP Code information?
    Do you need ZIP+4 information?
    Do you need city name information?
    Do you need county information?
    Do you need a nearest dealer locator system?

    We can HELP! We have affordable AS/400 software and data to do all of the above. Whether you need a simple city name retrieval system or a sophisticated CASS postal coding system, we have it for you!

    The ZIP/CITY system is based on 5-digit ZIP Codes. You can retrieve city names, state names, county names, area codes, time zones, latitude, longitude, and more just by knowing the ZIP Code. We supply information on all the latest area code changes. A nearest dealer locator function is also included. ZIP/CITY includes software, data, monthly updates, and unlimited support. The cost is $495 per year.

    PER/ZIP4 is a sophisticated CASS certified postal coding system for assigning ZIP Codes, ZIP+4, carrier route, and delivery point codes. PER/ZIP4 also provides county names and FIPS codes. PER/ZIP4 can be used interactively, in batch, and with callable programs. PER/ZIP4 includes software, data, monthly updates, and unlimited support. The cost is $3,900 for the first year, and $1,950 for renewal.

    Just call us and we’ll arrange for 30 days FREE use of either ZIP/CITY or PER/ZIP4.

    WorksRight Software, Inc.
    Phone: 601-856-8337
    Fax: 601-856-9432
    Email: software@worksright.com
    Website: www.worksright.com

    Share this:

    • Reddit
    • Facebook
    • LinkedIn
    • Twitter
    • Email

    IBM Readies Big Iron With “Cumulus” Power9 Chips Guru: An Introduction to RPG’s XML-INTO, Part 1

    Leave a Reply Cancel reply

TFH Volume: 28 Issue: 51

This Issue Sponsored By

  • RPG & DB2 Summit
  • RPG & DB2 Summit
  • RPG & DB2 Summit

Table of Contents

  • Guru: A Bevy of BIFs – %Dec to the Rescue
  • Guru: An Introduction to RPG’s XML-INTO, Part 1
  • Guru: Getting The Message, Part 1

Content archive

  • The Four Hundred
  • Four Hundred Stuff
  • Four Hundred Guru

Recent Posts

  • POWERUp 2025 –Your Source For IBM i 7.6 Information
  • Maxava Consulting Services Does More Than HA/DR Project Management – A Lot More
  • Guru: Creating An SQL Stored Procedure That Returns A Result Set
  • As I See It: At Any Cost
  • IBM i PTF Guide, Volume 27, Number 19
  • IBM Unveils Manzan, A New Open Source Event Monitor For IBM i
  • Say Goodbye To Downtime: Update Your Database Without Taking Your Business Offline
  • i-Rays Brings Observability To IBM i Performance Problems
  • Another Non-TR “Technology Refresh” Happens With IBM i TR6
  • IBM i PTF Guide, Volume 27, Number 18

Subscribe

To get news from IT Jungle sent to your inbox every week, subscribe to our newsletter.

Pages

  • About Us
  • Contact
  • Contributors
  • Four Hundred Monitor
  • IBM i PTF Guide
  • Media Kit
  • Subscribe

Search

Copyright © 2025 IT Jungle