While some may think that RPG is dead and periodically call for its burial, an in- depth study of the anatomy of subprocedures reveals not a corpse, but rather a thriving language that is finally getting the attention from IBM that it deserves. However, to keep IBM on a continued path of improvement and enhancement and to ensure that support for the language does not languish in the face of the recent Java enthusiasm, we must push the current features to the limit and keep demanding more from IBM.
This article encourages the exploitation of subprocedures (by example) and discloses the important elements of subprocedures (prototyping, interface definition, and argument options just to name a few) and explains how you can use them to replace functions that you may have previously implemented with standard /COPY source code routines, such as text- or string-centering functions. As such, our function will be named StrCenter, short for String Center.
Whats the Difference Between /Copy Source and Subprocedures?
Before I disclose the similarities between /COPY routines and subprocedures, Ill review an important difference: /COPY routines enable reuse of standard source code routines, but subprocedures enable binary reuse of standard routines or functionsreusability without recompilation. This reuse is made possible with the introduction of ILE modules (object type *MODULE) on the AS/400. Modules are not executable objects, but they can be bound with other modules to create executable programs.
ILE modules are created with the Create xxx Module (CRTxxxMOD) command, where xxx identifies the language of the source code module used with the CRTxxxMOD command. For example, CRTRPGMOD is used to create an ILE RPG (or RPG IV) module, whereas CRTCMOD is used to create an ILE C module. The source type for your subprocedure must be RPGLE in order to be used to create an ILE RPG module. To create
the RPG module, you use option 15 from PDM instead of the typical 14 option, which creates a program.
I chose the text- or string-centering function to demonstrate subprocedures, because it is both simple and usable, and most of us have, at some time or another, written a function or subroutine to center text. However, even if you havent written such a function, you can easily imagine what it would take to accomplish this task and thus intuitively accept what you readand acceptance is half of understanding what is presented. What is left is the material relevant to understanding subprocedures.
Subprocedures give us in RPG what we do not get with subroutines: the capability to pass parameters to and receive values from them. For instance, in the example case, I need to pass to the subprocedure a reference to (more on this later) a string of characters (a text field) and the length of the receiving variable (which may or may not be the same as the passed string variable). I will be receiving the centered text in the named variable for my EVAL assignment statement. (For an example of the StrCenter function, see RPG Building Blocks: Centering a String of Text elsewhere in this issue). This is the algorithm implemented by the StrCenter: The definition of what is passed to a subprocedure (number and type of parameters) is defined by the subprocedure prototype. But what is prototyping?
Prototyping Sign in, Please
In other languages, such as C, the notion of prototypes is a familiar one. However, to the veteran RPG programmer, this can be an alien conceptbut, as you will see, a welcome one, given the benefits of subprocedures.
What is a prototype? A prototype discloses the interface of a subprocedure to the calling program or subprocedure. An interface, in any language, is the mechanism through which we communicate (i.e., pass information to and from functions). The ILE RPG compiler, to provide compile-time type checking of arguments passed to and returned from the subprocedure, uses the prototype as a way to ensure that the proper number and types of arguments are passed, as defined in the procedure interface for the subprocedure. In essence, the signature for the subprocedure is defined by the subprocedure name, number of arguments, return values, and the types of arguments and return values provided by the prototype.
I place all of my prototypes into a file called SYSINC (system includes). However, you may place it in the same source file as your subprocedure (which is likely to be QRPGLESRC). Lets begin with the compiler directives (Label A in Figure 1) specified at the top of the source for my prototype definition. I have defined a compiler condition variable, StrCenterHCopied, that will be created when the first /COPY encountered that includes this definition is read. This avoids the compiler errors that result if another subprocedure or module has already prototyped it for compilation.
The PR statement (or prototype definition statement) shown at Label B identifies and defines any value to be returned by the subprocedure. This must be the maximum length of receiver variable that can be returned by the subprocedure. In this case, I limit the string length to 256 characters. Additionally, I have defined that OPDESC (the operational descriptor) be passed on the call to the subprocedure. This will be used primarily to retrieve the length of the referenced variable specified for argument 1. Immediately following this statement are the parameters that will be passed to the StrCenter function (Labels C and D). These parameters can be given names or not; they are inconsequential to this discussion.
Named Receiver Variable = StrCenter(UncenteredText :
SizeOfNamedReceiverVariable)
What I mean by that is, the names I refer to them as are the local names I define in the subprocedure, not the names defined in the prototype.
The compiler is interested only in the number and type of parameters, not the names. However, if names are not given to the arguments of the prototype, you should at minimum document their usage in a set of formal comments with the /COPY prototype code.
I have defined two parameters: a character variable of variable size up to a maximum of 256 characters (Label C) and a five-digit packed numeric field with zero decimal precision (Label D). I have defined argument 1 with the optional keyword Options(*VARSIZE) to tell the compiler that I will pass character fields of varying lengths, as would be required for a useful text-centering utility. I have defined the second argument as a five-digit packed numeric field so that I may use it with the ILE CL command CALLPRC. Since ILE CL does not currently support integer data types, I cannot use the more-efficiently stored data type for passing the size of the receiver variable, if I intend to call it from ILE CL programs. Otherwise, I recommend using integers to pass numeric values to subprocedures.
Note that the five-digit packed numeric field has been defined as a constant (CONST). This informs the compiler that this field is a read-only field and will not be changed by the subprocedure StrCenter. When creating your own subprocedures, it is useful to know that, by default, when you pass an argument to a subprocedure, you pass a reference to a defined variable, not the value of the variable. To pass the value of a variable, you must define the argument with the VALUE keyword appended.
You can define other argument constraints and preferences in addition to those presented in this article. They can be found under definition-specification keywords in the IBM ILE RPG Reference Guide.
Heart of the Function
Armed with an understanding of the prototype, lets look at the implementation details of the StrCenter function. Figure 2 presents this code in its entirety.
The first statement, a control specification with the NoMain keyword (Label E), informs the compiler not to generate cycle code for this module. RPG is a fixed-cycle language (whether we use the cycle or not). Because using the RPG cycle to call the subprocedure would result in undue overhead, I tell the compiler that no main procedure is required. To that effect, I code a NOMAIN keyword in the H-spec. The next statement (Label F) is a /COPY statement to include the prototype source statements in the module compilation. The subprocedure definition begins with the StrCenter Begin statement, denoted by the B in column 24 of the P-spec. There are two P-specs, one to begin the subprocedure and one to end it. The Export (Label G) keyword definition makes the subprocedure available to other programs and subprocedures; otherwise; it is available only within the current module.
Procedure Interface and Local Variables Definition
Next, I define the procedure interface (Label H). The procedure interface defines parameters passed to the subprocedure and any values returned. These parameters and values must match the prototype defined earlier. The PI statement is also where I give local names to the parameters.
Then, I define any local variables (Label I) needed to perform the string-centering function and return any values. Local variables are accessible only within the subprocedure in which they are defined. So, I can have a variable Title defined in the calling program as a 40-character field and defined in the subprocedure with the same name as a 256-character field. Thus, the StringOut variable could be named Title, and no conflicts would arise.
At Label I, you will also see the variable InLen defined as a 10-digit signed integer for the receiver from a call to the ILE API CEEDOD (Label J). CEEDOD retrieves the operational descriptor information for the first argument passed to this subprocedure so that the subprocedure references only the data passed to it. This facilitates the %LEN(%TRIMR(WorkString)) statement in Label K that trims blanks to the right of significant referenced variable data and then returns the length of the nonblank character string.
Finally, I define the subprocedure logic. In the first part of this logic, I determine the actual number of characters in the string parameter passed. The logic in Label K is coded for V3R7 and later releases of RPG; it can be replaced by the code in Figure 3 for prior releases. The CHECKR op code returns the actual length (number of nonblank characters) of the variable passed as the argument; whereas, the %SIZE built-in function returns the size of the variable passed as an argument, regardless of the data contained within the variable.
The remainder of the code in Figure 2 deals with finding the difference between the midpoint for the receiver variable and the midpoint of the passed string variable for the purpose of positioning the string in the middle of the receiver (return) variable. The last statement of logic, RETURN %SUBSTR(StringOut:1:StrLength), returns the centered text variable (but only the number of characters it can support). The subprocedure ends on the P-spec with E in position 24.
To create the subprocedure module, run the CRTRPGMOD command, as described earlier. In the program that requires the centering function, include a /COPY statement like the one at Label F. Then, create another RPG module for the program. Finally, bind the two modules together in a single program using the Create Program (CRTPGM) command, specifying the program name and related modules that make up that program.
Put on Your Running Shoes the Race Is On!
ILE RPG subprocedures are quite easy to implement, and they make the usually mundane task of writing yet another text-centering function fun! But dont stop there. There are many other functions yet to be written, and IBM still needs to implement many features to make RPG competitive with other languages. For example, both C++ and Java have a construct called a class, which combines data with related methods (file descriptions with subprocedures). IBMs current statement of direction is that the only changes it will make to RPG will be ones that make it cooperate with these other languages, such as C++ and Java. It doesnt sound like IBM has overwhelming enthusiasm for giving RPG the internal support it needs to become RPG++. But if you think for a moment about how you could implement a class in RPG++, it might look something like that presented in Figure 4.
Instead of using the Export statement to designate a public function or data, public could make the class method exportable, while private functions and data could be accessed only by class methods defined as public.
While I am not recommending my example as the guide by which IBM should implement object-oriented RPG, one can see that it is not a stretch to believe IBM could create an object-oriented RPG if the company were sufficiently motivated.
IBM, if youre listening, Java is a good long-term strategic direction for a portable AS/400 language, but dont forget the language that made the AS/400 a successRPG.
References
ILE RPG Programmers Guide (SC09-2074, CD-ROM QBJAQD01) ILE RPG Reference Guide (SC09-2077, CD-ROM QBJAQE01)
** Compiler Directives
*-
BEGIN LABEL A
/If Not Defined( StrCenterHCopied )
/Define StrCenterHCopied
/Else
/Eof
/ EndIf
END LABEL A
** Prototype for StrCenter Function.
*-
BEGIN LABEL B
D StrCenter PR 256A OPDESC
END LABEL B
BEGIN LABEL C
D 256A Options(*VARSIZE)
END LABEL C
BEGIN LABEL D
D 5P 0 CONST
END LABEL D
**===============================================================
* To compile:
*
* CRTRPGMOD MODULE(XXX/STRCENTER) SRCFILE(XXX/QRPGLESRC)
*
*===============================================================
BEGIN LABEL E
H Nomain
END LABEL E
BEGIN LABEL F
D/COPY SysInc,StrCenterH
END LABEL F
*-
BEGIN LABEL G
P StrCenter B Export
END LABEL G
*-
BEGIN LABEL H
D PI 256A OPDESC
D StringIn 256A Options(*VARSIZE)
D StrLength 5P 0 CONST
END LABEL H
** Prototype for CEEDOD (Retrieve Operational Descriptor)
*D CEEDOD PR
D ParmNum 10I 0 CONST
D 10I 0
D 10I 0
D 10I 0
D 10I 0
D 10I 0
D 12A Options(*OMIT)
** Local function variables and return value definition.
*D StringOut S 256A INZ
D WorkString S 256A INZ
D NbrOfChar S 5P 0 INZ
Figure 1: StrCenter compiler directives and prototype
D Adjust S 5P 0 INZ
*
D DescType S 10I 0
D DataType S 10I 0
D DescInfo1 S 10I 0
D DescInfo2 S 10I 0
BEGIN LABEL I
D InLen S 10I 0
END LABEL I
*-
BEGIN LABEL J
C CALLP CEEDOD(1 : DescType : DataType :
C DescInfo1 : DescInfo2: InLen :
C *OMIT)
END LABEL J
C EVAL WorkString %SUBST(StringIn:1:InLen)
BEGIN LABEL K
C EVAL NbrOfChar %LEN(%TRIM(WorkString))
END LABEL K
C IF (NbrOfChar < StrLength )
C EVAL(H) Adjust = ( StrLength - NbrOfChar ) / 2
C IF Adjust > *ZERO
C EVAL %SUBST( StringOut:Adjust:StrLength) =
C % SUBST(%TRIML( WorkString):1:Inlen)
C ENDIF
C ELSE
C EVAL StringOut = %SUBST(%TRIML(WorkString):1:+
C StrLength )
C ENDIF
C RETURN %SUBST(StringOut:1:StrLength)
*PStrCenter E
*C* HiLoEq
C CHECKR StringIn:InLen:NbrofChar 6839
C IF (*IN39 = *ON AND NbrOfChar < StrLength)
PMyClass B CD } CD - Class Definition
D MyClassID 10I 0 Private } Data can only be accessed
D MyClassName 30A Private } by class methods.
P MyClass CM } CM - Class Method
D AClassID 10I 0 } Class ConstructorD AClassName 30A } Defaults to private access
C EVAL MyClassID = AClassID
C EVAL MyClassName = AClassName
C RETURN this } Reference to MyClass being
constructed
P SetName CM Public
D NewClassName 30A 0
C EVAL this.MyClassName = NewClassName
P GetName CM 30A Public
C RETURN this.MyClassName
PMyClass E
LATEST COMMENTS
MC Press Online