Cobol and Message-Handling APIs

Article ID: 58302

Modern technologies such as laptop computers, cell phones, and wireless handheld devices have created an "always online and connected" culture in the IT community. Some companies tend to abuse their position of power, pressing their IT staff to be available 24x7x365, anytime something goes wrong. Whether this trend is a modern variety of worker exploitation is a matter of opinion. My belief is that IT staff should oppose this trend and defend their right to lead a balanced life. Despite my personal beliefs, the same thing that happened to me could happen to you: You might be asked to resend unanswered inquiry messages from the QSYSOPR message queue to the mobile devices of your personnel. I had no choice; I had to do it. The first thing I did to solve my business problem was look through the powerful APIs IBM provides.

As you might expect, I found the appropriate tool in the enormous mass of information provided by the Information Center: the Message Handling APIs, and more specifically, the List Nonprogram Messages (QMHLSTM) API. But before going further on this subject, let's recap some concepts that I discussed in some of my previous articles.

APIs can be classified according to their operating environment:

  • OPM APIs. These are the callable APIs whose names begin with the letter Q and are limited to 8 uppercase characters (e.g., the Create User Space (QUSCRTUS) API).
  • ILE bindable APIs, which are invoked by static procedure calls. These APIs begin with the letter Q but can be up to 30 characters and are case sensitive (e.g., the QlnSetCobolErrorHandler API).
  • ILE CEE bindable APIs. CEE stands for Common Execution Environment. These APIs are based on the System Application Architecture (SAA) specifications that enable cross-platform compatibility with other IBM systems (e.g., the CEEDATE and CEEDYWK APIs).
  • Unix-type APIs, whose naming conventions and prototypes are determined by industry standards organizations.

APIs that return information in a user space are called List APIs, and those that return information in a receiver variable are called Retrieve APIs. Receiver variables are data structures defined in the WORKING-STORAGE SECTION of a Cobol program, whereas user spaces are permanent objects that exist in the system space. Hence, user spaces can be shared by different Cobol run units. User spaces can be processed in two ways: using the Retrieve User Space (QUSRTVUS) API, or by means of pointers. As long as ILE Cobol supports pointers, we will always use the second way.

The first two categories of APIs have a common error-handling interface known as an error-code parameter, a variable-length data structure available in the QSYSINC include library.

The information returned by the List APIs consists of two parts: a header (or fixed portion) and a list portion. In the fixed portion, there are three important special fields: the offset to the list portion, the number of entries in the list portion, and the size of each entry. In turn, the list portion is made up of a series of entries, where each entry is a data structure that represents a particular instance of the returned information.

As a general rule, List APIs and Retrieve APIs require a parameter called "format name." This parameter determines the format of the information returned from the API to the calling program.

In general, the first parameter for List APIs is a qualified user space name, and this user space must exist before the API is invoked.

Some APIs, including QMHLSTM, have a keyed interface. By means of key values, you can provide directives to the API about what information you are interested in. An example of an API with keyed interface is the Save Object List (QSRSAVO) that I presented in my article "Planning for a Smooth Deployment in Cobol" (January, 15, 2009, article ID 57613). For the QMHLSTM API, you can use key values to tell the API that it must return the inquire messages from QSYSOPR with (1) the replacement data, (2) the message help, and (3) the default reply. This is a great tool, but it makes the interface much more complex! Key values are passed in a varying-length data structure. Therefore, before calling the API, you must provide the number of keys, the offset to the array that contains the key values inside the data structure, and the length of the data structure. The API documentation terms this structure as "message selection information." As long as there are several formats for the message selection information, the API requires a second format name, called "format of message selection information."

The API documentation tells us that QMHLSTM requires six parameters:

  1. The qualified user space name - input, char(20)
  2. The format name - input, char(8)
  3. The message selection information - input, char(varying-length)
  4. The size of the message selection information - input, binary(4)
  5. The format of the message selection information - input, char(8)
  6. The error code - input/output, char(varying-length)

All these parameters must be passed "by reference," because the QMHLSTM API belongs to the first category (i.e., OPM APIs). For each parameter, a pointer with the address of the information being passed is put into the parameter list. You don't have to worry about this issue, because the Cobol compiler generates implicit pointers for each parameter and does the work for you. As long as pass-by-reference is the default for the Cobol language, in my sample program (downloadable at the end of this article), I call the API in this way:

           CALL "QMHLSTM"
                USING user-space-name
                      returned-information-fmt
                      message-selection-info
                      size-message-selection-info
                      message-selection-info-fmt
                      qus-ec
           END-CALL

Now I explain in more detail each of these parameters:

  • The first parameter is the qualified name of the user space on which the requested information will be recorded. The user space must exist before calling the API.
  • The second parameter—the format name—indicates the format to use the API to return the requested information. For our API, there's only one available format: the LSTM0100 format. It's detailed in the API documentation, and it's translated to the Cobol language in QSYSINC/QCBLLESRC(QMHLSTM).
  • The third parameter—message selection information—is a varying-length data structure that contains directives passed from the Cobol program to the API about the content of the required information (selection criteria, severity criteria, etc.).
  • As long as the message selection information is a varying-length structure, the fourth parameter contains the length of that structure.
  • The fifth parameter—format of the message selection information—tells the API what format the Cobol program has selected to pass the message selection directives. There are two available formats: MSLT0100 and MSLT0200. Again, these are available to Cobol programmers in the QSYSINC/QCBLLESRC(QMHLSTM) include source member.
  • The sixth parameter is the common error-handling data structure for the OPM and ILE bindable APIs.
  • For us Cobol programmers, the real difficulty is to manipulate the varying-length data structures. Therefore, let's examine more closely how the message selection information parameter is defined in our sample program. First, let's look at how the people of IBM have defined it in the QSYSINC/QCBLLESRC(QMHLSTM) include source member:

    *****************************************************************
    *Type Definition for the input parameter section of the user-
    *space.
    ****                                                          ***
    *NOTE: The following type definition only defines the fixed
    *      portion of the format. The array of identifiers of
    *      fields to return specified, and call message queue
    *      specified are each varying length and immediately follow
    *      what is defined here.
    *****************************************************************
     01  QMH-LSTM-INPUT.
         05  USERSPACE-NAME                    PIC  X(00010).
         05  USERSPACE-LIBRARY                 PIC  X(00010).
         05  FORMAT-NAME                       PIC  X(00008).
         05  MESSAGE-SELECTION                 PIC  X(00008).
         05  SIZE-MESSAGE-SELECTION            PIC S9(00009) BINARY.
         05  MAX-MESSAGES-REQUESTED            PIC S9(00009) BINARY.
         05  LIST-DIRECTION                    PIC  X(00010).
         05  SELECTION-CRITERIA                PIC  X(00010).
         05  SEVERITY-CRITERIA                 PIC S9(00009) BINARY.
         05  MAX-MESSAGE-LENGTH                PIC S9(00009) BINARY.
         05  MAX-MESSAGE-HELP-LENGTH           PIC S9(00009) BINARY.
         05  OFFSET-MESSAGE-NAME               PIC S9(00009) BINARY.
         05  OFFSET-MESSAGE-KEY                PIC S9(00009) BINARY.
         05  NUMBER-MESSAGE-QUEUE              PIC S9(00009) BINARY.
         05  OFFSET-FIELD-RETURN               PIC S9(00009) BINARY.
         05  NUMBER-FIELD-RETURN               PIC S9(00009) BINARY.
         05  CODED-CHARACTER-SET-ID            PIC S9(00009) BINARY.
         05  DATE-TIME-CRITERIA                PIC  X(00013).
        *                                                        @A1A
        *05  RESERVED                          PIC  X(00001).
        *
        *                                Varying length
        *05  MESSAGE-QUEUE-NAME                PIC  X(00001).
        *
        *                                Varying length
        *05  START-MESSAGE-KEY                 PIC  X(00004).
        *
        *                                Varying length
        *05  ID-FIELD-RETURN                   PIC S9(00009) BINARY
        *                                          OCCURS 00001 TIMES.
        *
        *                                Varying length
        *****************************************************************

    Pay special attention to this sentence at the commentary note: "The following type definition only defines the fixed portion of the format." This means that it's our responsibility to define the varying portion of the format. We can't include this piece of code using the COPY directive in our program without some changes. In my sample program, I've adapted the above code in this way:

    01  parameter-fields.
        03 user-space-name.
           05 userspace-name            PIC x(10)VALUE "TMPSPACE".
           05 userspace-library         PIC x(10)VALUE "QTEMP".
    
        03 returned-information-fmt     PIC x(8)VALUE "LSTM0100".
    
        03 message-selection-info.
           05 fixed-portion.
              07 max-messages-requested   PIC s9(9) BINARY VALUE -1.
              07 list-direction           PIC x(10) VALUE "*PRV".
              07 selection-criteria       PIC x(10) VALUE "*MNR".
              07 severity-criteria        PIC s9(9) BINARY VALUE 0.
              07 max-message-length       PIC s9(9) BINARY VALUE -1.
              07 max-message-help-length  PIC s9(9) BINARY VALUE -1.
              07 offset-message-name      PIC s9(9) BINARY.
              07 offset-message-key       PIC s9(9) BINARY.
              07 number-message-queue     PIC s9(9) BINARY.
              07 offset-field-return      PIC s9(9) BINARY.
              07 number-field-return      PIC s9(9) BINARY.
    
          05 varying-portion.
              07 qualified-message-queue-name.
                 10 message-queue-name    PIC x(10) VALUE "QSYSOPR".
                 10 message-queue-library PIC x(10) VALUE "*LIBL".
              07 starting-message-key     PIC x(4)  VALUE x"FFFFFFFF".
              07 identifier-field-return-table.
                 10 id-field-return-201   PIC s9(9) BINARY VALUE 201.
                 10 id-field-return-301   PIC s9(9) BINARY VALUE 301.
                 10 id-field-return-401   PIC s9(9) BINARY VALUE 401. 
                 10 id-field-return-601   PIC s9(9) BINARY VALUE 601.
                 10 id-field-return-1001  PIC s9(9) BINARY VALUE 1001.
              07 id-field-return-table REDEFINES
                          identifier-field-return-table.
                 10 id-field-return
                            OCCURS 5 TIMES PIC s9(9) BINARY.
    
        03 size-message-selection-info     PIC s9(9) BINARY.
    
        03 message-selection-info-fmt      PIC x(8) VALUE "MSLT0100".

    In the preceding code, I've used the VALUE clause to specify the initial content for several fields into the message selection information parameter. These values are taken from the API documentation. For example, the value -1 in the MAX-MESSAGES-REQUESTED field indicates a "*NOMAX" condition. That is, the API must return all the messages that fit with the conditions specified in the other fields. Note that I haven't used the VALUE clause to initialize the offset fields. Offset fields must contain the offset values to several varying-length data structures into the varying portion of the format. To avoid "magic numbers" for the offset fields, I've initialized it in the PROCEDURE DIVISION by using the LENGTH OF intrinsic function.

    MOVE LENGTH OF fixed-portion TO offset-message-name
    COMPUTE offset-message-key =
            LENGTH OF fixed-portion +
            LENGTH OF qualified-message-queue-name
    END-COMPUTE
    COMPUTE offset-field-return =
            LENGTH OF fixed-portion +
            LENGTH OF qualified-message-queue-name +
            LENGTH OF starting-message-key
    END-COMPUTE
    MOVE LENGTH OF message-selection-info TO
                   size-message-selection-info
    

    The concept of offsets can be a source of trouble for Cobol programmers because Cobol is a "base 1" language, whereas System APIs receive and send information using a base of 0. Therefore, for List APIs, an offset value is the relative position from the first byte of the user space, which has an offset value of 0.

    Notice that in the varying portion, I've defined a five-element array, which contains the list of identifiers of fields to return. Each of these identifiers represents some type of message information that the API must return in the user space. For example, identifier 201 represents "replacement data," 401 represents "message help," and so forth. I could have defined a two-element, a three-element, or even a 31-element array.

    The next challenge is to manipulate the returned information in the user space, through the use of pointers. This is achieved by

    • getting a pointer to the first byte (i.e., offset value of 0) of the user space. For this purpose, my program uses the Retrieve Pointer to User Space (QUSPTRUS) API. Next, the program uses this pointer to set the address of the header portion.
    • retrieving from the header portion of the offset to the list portion, the number of entries, and the size of each entry.
    • adding the offset value to the space pointer value and setting the address of the list portion with this pointer. Next, the program processes the first list entry. In a loop, the program adds the size of each entry to the pointer value to access the next list entry, until all the entries have been accessed.

    Finally we have access to all the unanswered inquiry messages in the QSYSOPR message queue! The last challenge is how to send out these critical message alerts to the mobile devices of the support staff. This requires more than knowledge of Cobol; this requires a team of experts in network communications, network security, and so forth. But within that team, you as a Cobol programmer will play an important role!

    More Information

    The IBM i Information Center provides documentation about QMHLSTM API.

    You can download the sample program that illustrates the concepts presented here.

ProVIP Sponsors

ProVIP Sponsors