We all know my fondness for subprocedures, but one thing I like even better is prototyping useful APIs and C runtime functions for use by RPG IV programmers.
How many times while in your RPG program have you wanted to record a piece of information to the job log? You basically had two choices: Call a CL program to post the data or call the QMHSNDPM API and set up and pass all those cryptic parameters. Consequently, many RPG developers will go into debug on a program simply to view the contents of a variable or two. Wouldn't it be great to be able to write the contents of virtually any field to the job log, easily, from within RPG IV?
This article illustrates a rather uncommon (and apparently unknown) API that I've been using for years and have incorporated into the RPG xTools. The API is Qp0zLprintf (Print Formatted Job Log Data). Its purpose is to write a message in the job log. Qp0zLprintf is different from calling the message API or a CL program because it writes the text directly to the job log without the clutter of other methods. In fact, if you press F1 on the job log entry, no message ID is written. It does this by sending an impromptu message to the job log.
There are only three rules for calling Qp0zLprintf.
- The name Qp0zLprintf is case-sensitive and, yes, there is a zero in the middle of the name.
- You may insert substitution variables at runtime to build the job log entry dynamically.
- In order for the entry to be written to the job log, you must terminate the text with a "linefeed" character. As I've mentioned in other articles on CGI programming, a linefeed character in RPG IV is represented by X'25', and, no, Qp0zLprintf has nothing to do with CGI programming.
The API is really provided for non-RPG IV programmers who want to log information where iSeries and AS/400 users can find it. But it does make things a bit easier for RPG IV programmers as well. For example, the following writes the text string 'Hello OS/400' out to the job log from within RPG IV, using the Qp0zLprintf API:
You could use job log messages of this type to verify that milestones in the code were being performed in the correct sequence (or even at all). But what if you want to insert some varying data, such as a customer name or number, into a part of a static message? For example, suppose you want the customer number and name to be recorded to the job log, like this:
CustNo: 3470 The Big Software Company
You can use Qp0zLprintf to build this message dynamically at runtime by coding the following:
C %char(CustNo):%Trimr(CustName))
Note the %s values in the message text. These are substitution variable placeholders. These placeholders are part of the Qp0zLprintf API and are based on the C language's age-old printf function. Effectively, it means that each subsequent parameter (following the first parameter's text literal) is automatically inserted into the first parameter in order. So the first %s value is replaced with the content of Qp0zLprintf's second parameter, the second %s is replaced with the content of the third parameter, and so on.
The %s value means the API is expecting a "string"--or more accurately, a C language-style null-terminated string for the associated parameter. There are other substitution symbols for numeric and single characters, but I find that just using %s is easier and more practical in RPG IV. Besides, with the %CHAR() built-in function, converting numeric to character is more than easy to do.
So in our example, the CUSTNO field is converted from numeric to character using the %CHAR built-in function, and then it's inserted into the location of the first %s substitution symbol in your message text. The content of the CUSTNAME field is inserted in place of the second %s substitution symbol. Note that I remove trailing blanks from CUSTNAME just for good measure.
Writing to the Job Log
Of course, the example would not actually send anything to the job log because it was not terminated with a linefeed character. To make this work, you need to change the example to the following:
C %char(CustNo):%Trimr(CustName))
Note the addition of the X'25' character after the original message text. This causes the API to write the message to the job log. With the X'25', the data will just sit out there until either the X'25' is sent or the total length of all the text sent via Qp0zLprintf reaches 512 characters. At that point, the X'25' is forced.
Qp0zLprintf Prototype
Let's look at a prototype, as it appears in the IBM manuals, for the Qp0zLprintf API:
The API has a parameter named format-string, which is straightforward; it is a C language null-terminated string. But the second parameter is not so straightforward; it is an ellipse (...). An ellipse in C means that the parameter may be repeated as necessary. RPG IV does not have an equivalent of this capability, so we have to make due with what can be simulated in RPG IV.
Qp0zLprintf Parameters
The first parameter, as I mentioned, is a C null-terminated string. Passing a parameter of this data type is extremely easy in RPG IV. We simply specify the OPTIONS(*STRING) keyword on the prototype parameter, and the compiler does the conversion from character to null-terminated for us. So the first parameter would be defined as follows:
This is very similar to coding something like the following:
The difference is, since the API expects a null-terminated string, you would have to call the procedure as follows to make it work:
Effectively, what you're doing is adding X'00' to the end of the text. This is not pretty. So instead, you code the parameter so that the compiler automatically converts your input value into a null-terminated string and then passes that temporary value to the API. The "length" of a null-terminated string is determined by the location of the X'00' in the string. There is no predefined length for null-terminated strings, but they are limited to a length of 64K minus 1. Using these variables is vaguely similar to using VARYING fields in RPG IV.
The next requirement is to define the second through Nth parameters. It's up to you to decide how many of these parameters you would like to permit. Since you don't have to assign a name to parameter, you can define the parameter once and then copy the line of code as many times as you want.
As I mentioned, the API allows you to insert character, numeric, or strings as substitution parameters. If you convert everything to strings, you only need one type of parameter, one that is similar to the first parameter itself, as follows:
In this parameter, the OPTIONS(*STRING) keyword is used, but it also includes a second value, *NOPASS. This means the parameter is optional. This is the key to prototyping an API that has a variable number of parameters via the ellipse; you specify OPTIONS(*NOPASS) in addition to everything else you specify.
So the prototype for Qp0zLprintf ends up looking like this:
D szOutputStg * Value OPTIONS(*STRING)
D * Value OPTIONS(*STRING:*NOPASS)
Of course, just one substitution variable isn't all that compelling. You want more. So how do you specify more than one substitution parameter? By simply copying the second parameter over and over again until you have enough to handle as many substitution values as you believe you'll need. Remember, these parameters are optional, so when you call the API, you only specify the ones you need. Here's an example of the Qp0zLprintf prototype with 10 substitution variable parameters:
D szOutputStg * Value OPTIONS(*STRING)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
Now, when you call Qp0zLprintf, you can specify one parameter or more than one parameter. If you specify one parameter, you imply that you're not doing any substitutions; if you specify more than one parameter, you imply that you are doing substitutions.
Now, you can call Qp0zLprintf as follows:
C %char(CustNo):%Trimr(CustName))
And sitting out in the job log will be something similar to the following:
Qp0zLprintf is great for logging important debug information to the job log, but if performance is an issue, Qp0zLprintf is not a great choice because the overhead is a bit more than you might expect. I recommend using Qp0zLprintf only to log things you need to know of during debug mode. Or you could create a "switch" in your code that you can turn on externally. Then, if that switch is on, call the Qp0zLprintf; otherwise, bypass it.
Finally, if you're a fan of TRACE, there is an equivalent API to write out to the user trace area. That API is Qp0zUprintf. The only difference is the "U" in the name instead of the "L." The prototype for Qp0zUprintf is as follows:
D szOutputStg * Value OPTIONS(*STRING)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
D * Value OPTIONS(*STRING:*NOPASS)
To view the data in the user trace space, use the DSPUSRTRC CL command.
Bob Cozzi is a programmer/consultant, writer/author, and software developer. His popular RPG xTools add-on subprocedure library for RPG IV is fast becoming a standard with RPG developers. His book The Modern RPG Language has been the most widely used RPG programming book for more than a decade. He, along with others, speaks at and produces the highly popular RPG World conference for RPG programmers.
LATEST COMMENTS
MC Press Online