Like most people, I'm short on time, and my replies in the Cobol Forum at SystemiNetwork.com are generally concise -- often just a code snippet and a "try this . . . ." As such, in a recent thread, a member asked how to use the Print Formatted Job Log Data (Qp0zLprintf) API in a Cobol program, and my reply was deplorably brief. In this article, I try to repair that fault by explaining in detail how to read the API prototype and describing the fundamentals of calling it from a Cobol program.
The Qp0zLprintf() API's purpose is to record user information in the job log. I find it useful in the testing phase of my programs. For example, if I must select between two different sort algorithms, I can test performance by recording in the job log the starting and ending timestamps of each algorithm. Before we go any further, let me offer a quick word of warning: The third character in the API name is a zero (don't ask me why), not the uppercase letter O.
The Qp0zLprintf() API falls in the category of Problem Determination APIs, a subcategory of the Unix-type APIs. Perhaps because Unix programming is predominantly done in C, the Unix-type APIs are documented with a bent towards C programmers. Despite this, you can call a Unix-type API from any ILE language. No special binding directory or binding instructions are required for these APIs. However, Unix-type APIs use the ILE C/C++ "errno" method of reporting errors, which is part of the ILE C/C++ runtime library. Therefore, if you want to be able to check for errors returned by the Qp0zLprintf() API, you need to include BNDDIR(QC2LE) on the CRTBNDCBL or CRTPGM command. Specifying BNDDIR(QC2LE) lets your Cobol program call APIs that are part of the ILE C/C++ runtime environment.
int Qp0zLprintf(char *format-string, ...) ;
This line of the API's documentation is actually the ILE C prototype for the API. Because IBM provides documentation intended for C programmers, we need to take the information in this prototype and use it to create an equivalent CALL PROCEDURE statement in Cobol. If you're unfamiliar with prototypes, just know that they are a way to provide instructions for how the API must be called, what parameters must be passed, and so forth. The input parameters are enclosed within parentheses, and the word "int" at the start specifies the return value for the API. The first parameter is defined as a pointer (note the asterisk) to a character string named "format-string," which represents the format of the data to be recorded in the job log. According to the C language rules, character strings are null-terminated. That is, the end of a string is indicated by the x'00' character. But, as with many stream I/O APIs, this one also requires the new-line (or linefeed) character to indicate the end of the line to be printed into the job log. In a typical C environment, the API saves your message into a buffer, and it isn't added to the job log until a subsequent call specifies the new-line character. In fact, the new-line and the null characters must occupy the last two positions of the string. In the C documentation, the new-line character is represented as "\n" and the null character as "\0". In Cobol, the new-line character must be defined with the value x'25' and the null character with the value x'00', as in the following example:
01 line-feed PIC x(01) VALUE x"25". 01 null-character PIC X(01) VALUE x"00".
In the prototype I showed earlier, the three dots (...), or ellipsis, represent an optional list of arguments that contain substitution variables to be formatted into the format-string. At runtime, each format specification -- which begins with the percent sign (%) -- into the format-string is replaced by a substitution variable. You should define the same number of substitution variables as the number of format specifications declared in the format-string. A format specification can contain (besides the percent sign) a flag for justification and printing of signs, the width, the precision, and the data type. Each substitution variable gets formatted according to its corresponding format specification. The Qp0zLprintf API documentation in the i5/OS Information Center (link is provided at the end of this article) contains the complete list of format specification forms. The sample program in this article uses the easiest of these: the percent sign and the data type (%s), where "s" stands for "string." So in my sample program, the substitution variables are null-terminated strings.
The int (for integer) expression at the beginning of the prototype means that the API returns a value in a four-byte binary variable. If the API successfully ends, this returned value represents the number of characters recorded. If the API fails, a negative value is returned.
Suppose that I need to record the starting local time of a subprocedure called SORT-ARRAY, and I already obtained it (in Gregorian format) from the CEELOCTC API. I would build the format-string like this:
STRING "The SORT-ARRAY subprocedure start at: "
DELIMITED BY SIZE
"%s"
DELIMITED BY SIZE
line-feed
DELIMITED BY SIZE
null-character
DELIMITED BY SIZE
INTO ws-format-string
END-STRING.
My format-string has declared one format specification, so I must call the Qp0zLprintf() API, passing to it two parameters: the format-string and a substitution variable containing the current local time plus the null character.
Generally, Unix-type APIs expect parameters to be passed by value. In this way, the calling program copies the parameters to temporary variables, and the addresses of these temporary fields are passed to the called procedure. So I would call the Qp0zLprint() API like this:
CALL PROCEDURE "Qp0zLprintf"
USING BY VALUE ADDRESS OF ws-format-string
BY VALUE ADDRESS OF ws-current-local-time
RETURNING return-value
END-CALL.
But there is an easy way to do this call. If I pass the parameters by reference or by content, the Cobol compiler implicitly passes to the API the addresses of these variables. So, I would call the API like this:
CALL PROCEDURE "Qp0zLprintf"
USING BY REFERENCE ws-format-string
BY REFERENCE ws-current-local-time
RETURNING return-value
END-CALL.
As long as the call by reference is the default, I can simply code:
CALL PROCEDURE "Qp0zLprintf"
USING ws-format-string
ws-current-local-time
RETURNING return-value
END-CALL.
And in any case, I should always check out the return value and, if it is negative, obtain the errno global variable as in the following example:
IF return-value = -1
CALL PROCEDURE "__errno"
RETURNING errno-ptr
END-CALL
SET ADDRESS OF errno TO errno-ptr
DISPLAY "Errno: " errno
END-IF.
As I said earlier, I use this API in the testing phase of my programs. Occasionally, I also use it in the "tuning" phase -- when a program already is in production and I want to track its behavior in a real environment. But, as soon as possible, I remove the API from the main program, because of performance considerations. As the i5/OS API documentation says, "An application should not use the tracing APIs in performance-critical code. These APIs are intended for debugging exceptions or error conditions."
A clear description about the similarities and differences between "call by value" and "call by reference" is in Bruce Vining's article, "API Parameters and Data Types":
http://systeminetwork.com/article/api-parameters-and-data-types
i5/OS Information Center documentation for the Qp0zLprintf() API:
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=/apis/p0zlog.htm
iSeries ILE C/C++ Run-Time Library Functions:
http://publib.boulder.ibm.com/infocenter/iseries/v5r4/topic/books/sc415607.pdf
You can download a sample Cobol program that uses this API at:
http://www.pentontech.com/IBMContent/Documents/article/56785_621_jobtrace.zip