29
Your partner in IBM i Education Your partner in IBM i Education Your partner in IBM i Education Your partner in IBM i Education RPG Subprocedures Basics Susan Gantner [email protected] www.Partner400.com SystemiDeveloper.com Susan doesn’t code subroutines any more. In her opinion, subprocedures (aka, procedures) make great subroutine replacements. They can make your code more obvious, making maintenance easier, faster and more reliable. Take the extra step to package your commonly used procedures into ILE Service Programs and you can share them easily and efficiently among many programs. This seminar covers the details of coding subprocedures - both the syntax and the best practices. We'll look at how prototypes are used with subprocedures and cover some valuable prototype keywords to make your coding life easier. In addition, we'll cover the details of ILE Service Programs - what they are, why you should use them, how to package your subprocedures in them, and shortcuts for using them from your other programs. This seminar provides a solid foundation that will prepare you for many of the other Summit sessions. Susan will also provide a suggested roadmap of Summit sessions to encourage you to continue your education on these topics in even greater depth. The author, Susan Gantner, is co-founder of Partner400, a firm specializing in customized education and mentoring services for IBM i (AS/400 and iSeries) developers. After a 15 year career with IBM, including several years at the Rochester and Toronto laboratories, Susan is now devoted to educating developers on techniques and technologies to extend and modernize their applications and development environments. This is done via on-site custom classes for individual companies as well as conferences and user group events. Jon and Susan author regular technical articles for the IBM publication, IBM Systems Magazine, IBM i edition (formerly iSeries Magazine and eServer Magazine, iSeries edition), and the companion electronic newsletter, IBM i EXTRA (formerly iSeries Extra). You may view articles in current and past issues and/or subscribe to the free newsletter or the magazine at: www.ibmsystemsmag.com/IBMi This presentation may contain small code examples that are furnished as simple examples to provide an illustration. These examples have not been thoroughly tested under all conditions. We therefore, cannot guarantee or imply reliability, serviceability, or function of these programs. All code examples contained herein are provided to you "as is". THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED. © Copyright Partner400, 2004-2011. Page 1-2

RPG Subprocedures Basics - gomitec.com Presentations/Susan Gantner...Your partner in IBM i Education RPG Subprocedures Basics Susan Gantner [email protected] System iDeveloper.com

  • Upload
    lamdat

  • View
    223

  • Download
    3

Embed Size (px)

Citation preview

Your partner in IBM i Education Your partner in IBM i Education Your partner in IBM i Education Your partner in IBM i Education

RPG Subprocedures Basics

Susan Gantnersusan.gantner@partner400.comwww.Partner400.comSystemiDeveloper.com

Susan doesn’t code subroutines any more. In her opinion, subprocedures (aka, procedures) make great subroutine replacements. They can make your code more obvious, making maintenance easier, faster and more reliable. Take the extra step to package your commonly used procedures into ILE Service Programs and you can share them easily and efficiently among many programs.

This seminar covers the details of coding subprocedures - both the syntax and the best practices. We'll look at how prototypes are used with subprocedures and cover some valuable prototype keywords to make your coding life easier. In addition, we'll cover the details of ILE Service Programs - what they are, why you should use them, how to package your subprocedures in them, and shortcuts for using them from your other programs. This seminar provides a solid foundation that will prepare you for many of the other Summit sessions. Susan will also provide a suggested roadmap of Summit sessions to encourage you to continue your education on these topics in even greater depth.

The author, Susan Gantner, is co-founder of Partner400, a firm specializing in customized education and mentoring services for IBM i (AS/400 and iSeries) developers. After a 15 year career with IBM, including several years at the Rochester and Toronto laboratories, Susan is now devoted to educating developers on techniques and technologies to extend and modernize their applications and development environments. This is done via on-site custom classes for individual companies as well as conferences and user group events.

Jon and Susan author regular technical articles for the IBM publication, IBM Systems Magazine, IBM i edition (formerly iSeries Magazine and eServer Magazine, iSeries edition), and the companion electronic newsletter, IBM i EXTRA (formerly iSeries Extra). You may view articles in current and past issues and/or subscribe to the free newsletter or the magazine at: www.ibmsystemsmag.com/IBMi

This presentation may contain small code examples that are furnished as simple examples to provide an illustration. These examples have not been thoroughly tested under all conditions. We therefore, cannot guarantee or imply reliability, serviceability, or function of these programs.

All code examples contained herein are provided to you "as is". THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED.

© Copyright Partner400, 2004-2011. Page 1-2

What's a Subprocedure?A cross between a subroutine and a program

Like a subroutineIt may be coded in the same source member where it is usedFollows all other C specs Compiled with its callerPerformance level similar to a subroutine

Like a programContains its own data via D specsAccepts parametersMay be compiled separately from its caller to ease reuse

Note that a subprocedure may be coded in (and compiled with) its caller - more like a subroutine.

However, it may instead be coded and compiled separately from its caller. In this case, you would use ILE static binding facilities (most likely a Service Program) to bind the caller to the called subprocedure. This externalizes the routine and makes it easily shared among many programs without duplicating the code - either at the source level or at the compiled level.

© Copyright Partner400, 2004-2011. Page 3-4

SubproceduresSubprocedures can:

Define their own Local variablesThis provides for "safer" development since only the code associated with the variable can change its content

More on this later

Access Files Defined in the Global sectionBy that we mean the main body of the source

Access Global variablesBe called recursively

In other languages it would be called Function or P rocedureIf it returns a value it is a Function

Much like an RPG Built-In Function (BIF)

If it does not return a value it is a Procedure

It is simply called with a CALLP and "does stuff" for you

C If %EOF(MyFile) = *On

C If ValidCustomer(CustNo)

Support for subprocedures was added to the RPG IV language in releases V3R2 and V3R6.User written subprocedures in RPG IV allow for recursion (i.e., the ability for the same procedure to be called more than once within a job stream). They also allow for true local variable support (i.e., the ability to define fields within a subprocedure that are only seen by and affected by logic within the bounds of the subprocedure.) RPG IV subprocedures use prototypes, which are a way of defining the interface to a called program or procedure. In this session, we will concentrate on writing and using RPG IV subprocedures, but you will find that many of the same prototype-writing skills can be applied to access system APIs and C functions.

The code sample here is intended to show the similarities between calling an RPG built-in function and one of your own functions that is written as a subprocedure.

Note that although a CALLP is used to invoke subprocedures that do not return a value, you should not be mislead into thinking that CALLP means CALL Procedure. It does not - it actually stands for CALL with Prototype.

Most of the time your subprocedures will return a value, but sometimes you'll just want to have it "do stuff". For example a subprocedure named WriteErrorLog might be used to record errors detected by the program. There's not a lot of point in having it return a value to say it did it - after all what are you going to do if it couldn't? Call it again to write another error message? <grin>

© Copyright Partner400, 2004-2011. Page 5-6

Major Features of SubproceduresRPG source members can contain multiple subprocedur es

Subprocedures can use other SubproceduresBut they cannot be nested

When they return a value they are used as functionsJust as if they were built into the language like IBM's BIFs They have a data type and size

You can use them anywhere in the freeform calcs where a variable of the same type can be used

e.g. In an IF, EVAL, DOW, etc. etc.

A Subprocedure DayOfWeek could be used as shown bel owWe will be developing this routine later

C If DayOfWeek(ADateFld) > 5 C Eval WeekDay = DayOfWeek(Today)

When we talk about the "Source Member" we really mean all of the RPG IV source that is processed by the RPG compiler in any one compilation. This would include any source members that were /COPY'd into the main source. You may wonder why we used the term "Source Member" rather than "Program". Traditionally we have tended to equate a source member to a program since the normal RPG/400 approach meant that one source was compiled and this resulted in a program (*PGM) object. With RPG IV each source is compiled into a module (*MODULE), and a number of modules may be combined into a single program.

Subprocedures which return a value are used very much like RPG IV built-in functions, as shown in the examples on this chart. In the example, "DayOfWeek" is the name of an RPG IV subprocedure. In fact we will be building this exact procedure shortly.

It requires a single date field as the input parameter (which in the first example is passed via the field named "ADateFld" and in the second example via the field "Today").

It returns a value, which is a number representing the day of the week (1 through 7). The returned value effectively replaces the function call in the statement. In the first example, that value will be compared with the literal 5. In the second example, the returned value will be placed in the field "WeekDay".

© Copyright Partner400, 2004-2011. Page 7-8

DayOfWeek Subroutine

* Variables used by DayOfWeek subroutine

D AnySunday C D'04/02/1995 ' D WorkNum S 7 0 D WorkDay S 1S 0 D WorkDate S D

* Additional program logic omitted

C Eval WorkDate = InputDate C ExSr DayOfWeek C Eval DayNumber = WorkDay

* Additional program logic omitted

***** Calculate day of week (Monday = 1, Tuesday = 2, etc.)

C DayOfWeek BegSr

C Eval WorkNum = %Diff( Wor kDate : AnySunday: *D ) C Eval WorkDay = %Rem( Work Num : 7 ) C If WorkDay < 1 C Eval WorkDay = WorkDay + 7 C EndIf C EndSr

In this presentation, we will take the subroutine in this program and turn it into a subprocedure. Existing subroutines in programs often make good candidates for subprocedures.

Here we see the traditional use of a standard subroutine, along with all its inherent problems.WorkDate and WorkDay are "standard" field names within the subroutine that we have to use. In effect they are acting as parameters because the subroutine is used from many places in this program using different fields for the calculations (note that we have omitted all except the relevant code for this example).

We must move fields to/from these "standard" fields to in order to use the subroutine. Once we have turned this into a subprocedure, these additional steps will not be necessary.

The use of common subroutines also forces us to use naming standards to ensure that work fields within the subroutine do not get misused. This can certainly hinder attempts at producing meaningful field names - particularly with RPG III's six character limit!

© Copyright Partner400, 2004-2011. Page 9-10

D DayOfWeek PR 1S 0 D ADate D : C Eval DayNumber = DayofWee k(InputDate) : : : P DayOfWeek B

D DayOfWeek PI 1S 0 D WorkDate D

D WorkNum S 7 0 D WorkDay S 1S 0 D AnySunday C D'04/02/1995 '

C Eval WorkNum = %Diff( Wor kDate : AnySunday: *D ) C Eval WorkDay = %Rem( Work Num : 7 )

C If WorkDay < 1 C Eval WorkDay = WorkDay + 7 C EndIf

C Return WorkDay

P DayOfWeek E

1

2

3

4

Basic Subprocedure

5

This is the code for our completed subprocedure.

Notice the basic sequence of the specifications. The prototype(s) appear at the beginning of the source, along with any other D specs. The P specification that begins the subprocedure appears after the regular C specs. We will look in more detail at the sequence of specifications in this "new style" of RPG program later,

In this example, the stand-alone and constant fields are local variables available only to the logic in this particular subprocedure. More on what we mean by "local" later.

In case you haven't seen the use of the %REM built-in before, it returns the remainder of the result of dividing the first parameter by the second. It replaces a DIV followed by a MVR.

© Copyright Partner400, 2004-2011. Page 11-12

Invoking the SubprocedureConverting to a subprocedure allows us to use DayOf Week as if it were a built-in function of RPG

It just doesn't have a % sign in front of the name!

The date to be processed (WorkDate) is passed as a parmNo need to 'fake' parameters as in the original

More on parameter definition in a moment

C Eval DayNumber = DayofWe ek(InputDate)

C Eval WorkDate = InputDate C ExSr DayOfWeek C Eval DayNumber = WorkDay

1

We call the subprocedure in much the same way as we use a built-in function in RPG. Since it returns a value, we can call it in an expression. The returned day number will be placed in the field called DayNumber. If our subprocedure did not return a value we would invoke it with a CALLP.

© Copyright Partner400, 2004-2011. Page 13-14

P-specs & the Procedure InterfaceProcedures are bounded by P-specs

A type B(egin) names the procedureA type E(nd) is required to complete it

A Procedure Interface (PI) is also requiredThe PI line defines the Data type and Size of the procedure

The procedure can be used anywhere that a field of the same type and size can be used

Subsequent lines define any parametersi.e. The PI acts as the procedure's *ENTRY PLIST

P DayOfWeek B

D DayOfWeek PI 1S 0 D WorkDate D : : : P DayOfWeek E

The procedure name is optional

on both the PI and the E(nd) P-Spec

2

Subprocedures begin and end with P specs. In this example, we see the beginning P spec. In a later chart, we will see the ending P spec as well. The beginning P spec contains a B in the position that would contain, for example, a DS on a D spec. The P spec has a very similar layout to the D spec.

The next thing we need is a Procedure Interface, or PI. The procedure interface defines the interface to the procedure -- most significantly, the parameters passed to and/or from the subprocedure. The PI is defined on the D specs, typically as the first D specs in the subprocedure. The PI replaces the need for a *ENTRY PLIST.

The data item defined on the same line as the PI is the return value. (Note: It is possible to have a subprocedure that returns NO value. A subprocedure can return, at most, one value.)

The data item(s) that follow the PI with nothing specified in the column where the PI (in this example, the WorkDate field) is are the input parameters into the subprocedure.

The data items following WorkDate are all local fields to the subprocedure. You can tell the end of the procedure interface and by the appearance of something (in this example, an "S") in the column where PI was specified.

© Copyright Partner400, 2004-2011. Page 15-16

3DayOfWeek SubprocedureSubprocedures allow us to define local variables

They can ONLY be referenced within the subprocedure

Much safer than the traditional RPG approachThe writer of the procedure can protect all of the variables

Only those that should be changed can be changed !

D DayOfWeek PI 1S 0 D WorkDate D

D WorkNum S 7 0 D WorkDay S 1S 0 D AnySunday C D'04/02/1995'

Local data is very important for reusable procedure logic. It is also important to make maintenance tasks more reliable, as there is more independence within the subprocedures and, therefore, less chance that maintenance tasks will inadvertently affect mainline logic. In this example, the fields AnySunday, WorkNum and WorkDay are local fields to this subprocedure. They can only be referenced from the subprocedure.

We'll take a closer look at Local data later in the presentation.

© Copyright Partner400, 2004-2011. Page 17-18

4Returning the resultRETURN is used to send the result back to the calle r

It can simply return a value as in our original version

Or it can return an expression as shown below

Often a procedure will consist simply of a RETURN o p-codeFor example, the entire logic for a procedure called CalcItemTotal which took the parameters Price, Discount and Quantity might be:

Return ( Price * ( Discount / 100) ) * Quantity

C If WorkDay < 1 C Return WorkDay + 7 C Else C Return WorkDay C EndIf

C Return WorkDay

Here we specify the return value for this subprocedure. We use the RETURN operation code and specify the return value in factor 2. Notice that the RETURN operation code is now a freeform operation.

The returned value can be either a single field or an expression, as in the second example. In fact, since a subprocedure can be used anywhere a variable of its type can be used, the returned value itself could be the result of a subprocedure invocation. But we're getting a little deep a little too quickly here ....

© Copyright Partner400, 2004-2011. Page 19-20

Defining the PRototypeEach procedure requires a Prototype

Notice that it's almost identical to the PIIt must be present when compiling the procedure

This allows it to be validated against the Procedure Interface

It is also required in each program that wants to use the procedure

If the procedure is externalized (we'll get to this later)The preferred approach is to code it as a /Copy member Place Prototypes for groups of related functions in a single member

Possibly one member per Service Program

Prototypes simply provide information to the compil erThey don't result in any data definition

This is what our prototype currently looks like

D DayOfWeek PR 1S 0 D ADate D

5

RPG 7.1 Update: Prototypes are optional in some circumstances

The next step is to define the prototype. The parameters in the prototype must match the Procedure Interface (PI) because it also defines the interface to the procedure. The prototype will be used by the procedures that call this subprocedure.

The prototype is also required to be placed into the module where the procedure is defined. This is so the compiler can check the validity of the prototype -- that is, that the parameters specified match the Procedure Interface in terms of number and type. Note that beginning with 7.1, the rules for placement of prototypes have been relaxed. The compiler no longer requires prototypes for internal subprocedures, like the one we have here. Prior to 7.1, they are required and are probably a good idea to have, generally speaking.

At this point in the development of the subprocedure, we are hard coding the prototype in the main line portion of the program. Later when we decide to externalize the subprocedure, its prototype will be moved to a /COPY source member. This is a common (and encouraged) practice. The prototypes for subprocedures are grouped in a separate source member that is copied in (via the /COPY directive). This is especially important if the subprocedure is placed in a separate module (source member) because it is critical that the prototype in the calling procedure match the one in the defining procedure, since it is the once in the module containing the subprocedure that the compiler verified for you.

In the event that the subprocedure is placed in the same source member as the caller (as in our basic example), then only a single copy of the prototype is required (until 7.1, when the prototype becomes optional), because the compiler will be able to check the prototype and procedure interface in a single compile step. If the subprocedure were to be placed in a separate source member, then a copy of the prototype would be required in both the member containing the subprocedure and in the main procedure (or calling procedure). as well as in any other main or subprocedures calling this subprocedure.

© Copyright Partner400, 2004-2011. Page 21-22

Improving Our PrototypesIf the date routines are to be used against multipl e dates

What if the formats for the date parameters are of different formats? We should specify the format to make sure the subprocedure always gets the format it wants

Especially important in the next session when we externalize these subprocedures

So ALWAYS specify a formatWhen using dates as parms OR return values

* Prototype for DayOfWeek procedure

D DayOfWeek PR 1 0

D InputDate D DATF MT(*USA) * Prototype for DayName procedure

D DayName PR 9

D WorkDate D DATF MT(*USA)

The DATFMT keyword should always be specified for a date field returned by a subprocedure, or passed as a parameter. In other words any date field defined within a Procedure Interface (PI) or Prototype (PR) should have the DATFMT keyword.

Why? Well without it, the format of the date is determined at the time of compilation. Without the DATFMT keyword, the date will be classified as the default type for the compilation. While this is normally *ISO, it is subject to change at the whim of an H-spec DATFMT entry.

Suppose that our prototype does not specify the date format. Later, after the externalize these subprocedures, then when we /COPY it into a program with no H-spec, the compiler will interpret it as an *ISO date. If the program then passes an *ISO date to the subprocedure, the compiler will accept this as valid.

Now suppose that in the source member that contains our subprocedure, we have an H-spec that specifies the default format as being *USA. The same /COPY member will now have it's date fields interpreted as being *USA dates.

The result would be that within the subprocedure the date is viewed as *USA while in the calling program it is defaulting to *ISO. The prototype that should protect us from such problems is allowing a date of the wrong format to be passed. Not a recipe for success!!

Note that although we have emphasized this as being an issue with dates, the same holds true for Times. It is not and issue with Timestamps since they only have one format.

© Copyright Partner400, 2004-2011. Page 23-24

CONST (Constant) ParametersThere's one problem with specifying DATFMT

The routines now only work with parameters in that formatThe compiler will reject any attempt use any other format

The CONST keyword can help us hereIt allows the compiler to accept a date in any format

The compiler generates a temporary field in the correct format and moves the parameter into it before passing the copy

Remember: Both the prototype & the procedure interface must change

This is not only helpful with dates - what about packed vs. zoned? What if an expression or hard-coded constant value is to be passed?

* Prototype for DayOfWeek procedure

D DayOfWeek PR 1 0 D InputDate D CONST DATFMT(*USA)

* Prototype for DayName procedure

D DayName PR 9 D WorkDate D CONST DATFMT(*USA)

Best Practice Tip Always specify CONST

for read-only parms

In this example, by combining CONST and the DATFMT keywords, the compiler can generate a temporary (hidden) date field in the calling program or procedure, if necessary, in order to convert the date format used by the caller to the format (in this case, *USA) used in the called subprocedure.

In general, the use of the CONST keyword allows the compiler to accommodate mismatches in definitions of parameters between the calling and called programs or procedures. When you use this keyword, you are specifying that it is acceptable that the compiler to accommodate such mismatches by making a copy of the parameter (if necessary) prior to passing it.

Note that CONST indicates that the parameter is Read-only i.e. the called subprocedure cannot change it's value. The compiler will in fact defend against this and will error out any attempts to change the value of parms passed as CONST.

There is no equivalent to CONST needed on the definition of the returned value. If the compiler notes that (for example) and *ISO date is being returned and it being assigned to a *USA date field, it will automatically convert the date just as it would in a simple assignment of one date type to another.

It is important to realize that CONST is not only helpful for date parameters. All parameters that are not going to be changed by the called subprocedure can and should be passed as constants (i.e., specifying CONST). That way, the caller can pass constants, expressions, other numeric data types (packed vs zoned), etc. It makes the subprocedure far more useful in different circumstances.

Granted, this detail is perhaps more important after the next session when we make our subprocedure available externally. But it is still a good habit to get into using even with internal subprocedures.

© Copyright Partner400, 2004-2011. Page 25-26

"Local" and "Global" Variables

D Count S 5P 0 Inz D Temp S 20A

C Eval Count = Count + 1

C Eval LocalValue = 0

C Eval Temp = 'Temp in Mai n'

* Procedure1

D LocalValue S 7P 2 Inz D Temp S 7P 2 Inz

C Eval Count = Count + 1

C Eval LocalValue = 0

C Eval Temp = LocalValue

* Procedure2

D Temp S 40A

C Eval LocalValue = 0

C Eval Temp = 'Temp in Pro cedure2'

Any and all subprocedures coded within the source member will automatically have access to global data items defined in the main procedure. They can also define their own local data fields, which will be accessible only within the subprocedure where they are defined.

As a rule of thumb, use of global data within a subprocedure should be avoided whenever possible. Ideally, subprocedures should act upon the data passed in through parameters and affect the data back in the calling code only by returning a result. This avoids the possibility of side-effects where (by accident or design) a subprocedure unexpectedly changes the content of a field back in the calling code.

In addition to returning values, subprocedures can also modify parameters passed to them, just as a parameter on a conventional program call can be modified. However, this is not the preferred approach, for the same basic reasons that we stated for global data items.

In some circumstances accessing global data cannot be avoided. For example, although files can be used within a subprocedure, until V6R1, they can only be defined at the global level. Therefore in order to access the file data we must reference those global items.

Beginning with V6R1, F specs can also be located inside a subprocedure and in that case, the file is local to the subprocedure. When using a local file definition in a subprocedure, the I/O to that file must be via a local Data Structure, most likely defined using the LIKEREC keyword.

© Copyright Partner400, 2004-2011. Page 27-28

What About /Free?Prefer /Free logic, like me?

Remember that only logic can be free formSubprocedures use P specs & D specs before any logic, therefore:

All subprocedure logic must be surrounded by /Free and /End-FreeIt gets a tad cumbersome, but ...

Example of DayOfWeek in /Free format on next chart

I'm a fan of /Free logic. The reason I teach topics such as subprocedures with more traditional C specs is that I want to make sure that even those RPGers who have not embraced /Free can easily comprehend the concepts and syntax of writing them.

© Copyright Partner400, 2004-2011. Page 29-30

D DayOfWeek PR 1S 0

D ADate D Const D atFmt(*USA)

/FREE

DayNumber = DayofWeek(InputDate);

/END-FREE

P DayOfWeek B

D DayOfWeek PI 1S 0

D WorkDate D DatFmt(* USA) Const

D WorkNum S 7 0

D WorkDay S 1S 0

D AnySunday C D'04/02/ 1995'

/FREE

WorkNum = %Diff( WorkDate : AnySunday: *D );

WorkDay = %Rem( WorkNum : 7 );

If WorkDay < 1;

WorkDay = WorkDay + 7;

EndIf;

Return WorkDay;

/END-FREE

P DayOfWeek E

/Free Form DayOfWeek Subprocedure

Since subprocedures are usually quite small pieces of code, it makes the necessity of using /Free and /End-Free all the time seem even more cumbersome than usual.

I still find that /Free is a better way to write any RPG logic. Hopefully someday we will be freed from the requirement to include the /Free, etc. directives.

© Copyright Partner400, 2004-2011. Page 31-32

Static vs. Automatic StorageLocal data fields use automatic storage by default

Can be overridden with the STATIC keyword on the D specIn other words, local data can be either static or automatic

What's automatic storage?Storage that exists only for the duration of the procedure callGoes away (is de-allocated) when procedure returns to its caller

Static storage is the only type for global fields i n RPG LR indicator controls when global fields are reinitialized

Local fields -- even if declared STATIC -- are not reinitialized after LR

Why use automatic storage?Automatic "clean up" of fields without the overhead of LRMore efficient - storage not used unless / until the procedure is calledIt allows for recursive calls

Data stored in automatic storage goes away when the procedure returns to its caller. Upon subsequent calls to the procedure, automatic fields "lose" their values. Data stored in static storage remains between calls to the procedure until the Activation Group where the procedure is activated is reclaimed.

Recursion (the ability for a subprocedure to be called multiple times in the same invocation stack -- in fact, for a subprocedure to call itself!) is made possible because of automatic storage. Each call to the subprocedure gets a "fresh" set of local automatic storage fields. This is why subprocedures may be called recursively, but main RPG procedures cannot.

Automatic storage can be considerably more efficient than static storage since it is only allocated if and when the subprocedure is called. Think about static binding and the fact that many procedures are often activated at once in a job! Remember that all the procedures in all the Service Programs referenced (directly or indirectly) by an ILE program are allocated immediately on the first call in the job to that ILE program. If that represents a lot of procedures (either main procedures or subprocedures) all static fields will be allocated and initialized by the system right away. In many cases, many of those procedures may never be called in this particular job. However, their static storage must always be allocated and remain allocated until the program's Activation Group is reclaimed. Use of automatic storage reduces this potential "overuse" of memory by allocating only what is needed and only for the duration that it is needed. In "memory-heavy" applications, this could have a noticeable impact on application performance and system efficiency.

© Copyright Partner400, 2004-2011. Page 33-34

H DftActGrp(*NO) ActGrp('QILE')

D ProcAuto PR D GlobalCount S 3 0 Inz D X S

/FREE For x = 1 to 5;

ProcAuto();

EndFor; *INLR = *On;

/END-FREE

P ProcAuto B

D PI D CountStat S 3 0 Static Inz D CountAuto S 3 0 Inz

/FREE CountStat = CountStat + 1; CountAuto = CountAuto + 1; GlobalCount = GlobalCount + 1; Dsply 'CountStat=' ' ' CountStat ; Dsply 'CountAuto=' ' ' CountAuto; Dsply 'GlobalCount=' ' ' GlobalCount;

/END-FREE P E

What are the values displayed

each time?

Example: Static & Automatic Storage

To test if you really understand automatic and static storage, can you predict what values will be displayed when the main procedure in this sample code is called?

The values of CountStat and GlobalCount increase by 1 each time they are displayed.

The value of CountAuto never increases. It is displayed as 1 every time!

Now, for the tougher question:

Assume this main procedure was immediately called a second time in the same job. What value would be displayed for CountStat and GlobalCount on the first call to the ProcAuto subprocedure ??

© Copyright Partner400, 2004-2011. Page 35-36

H Keyword NOMAIN required if no main line calc specsF File Specifications - Global D PR Prototypes for all procedures used and/or defined in the

source (Often present in the form of a /COPY)D Data definitions - GLOBALI GLOBALC Main calculations (Any subroutines are local)O GLOBAL

P ProcName1 B Start of first procedureF Local File Specifications - only allowed V6R1+D PI Procedure Interface

D Data definitions - LOCALC Procedure calcs (Any subroutines are local)P ProcName1 E End of first procedure

P Proc..... B Start of next procedure........ Procedure interface, D-specs, C-Specs, etc.P Proc..... E End of procedure** Compile time data

RPG IV Specification Sequence

This chart illustrates the layout of a complete RPG program containing one or more subprocedures.

Note that the NOMAIN keyword on the H specification is optional and indicates that there is no mainline logic in this module, i.e., no C specs outside the subprocedure logic. Note also that until V6R1, any F specs always go at the top of the member, just after the H spec, for any files that will be accessed, either by the mainline code (if any) or by the subprocedures. This is true regardless of whether there is any mainline logic or not.

A significant V6R1 enhancement allows files to be defined inside a subprocedure, in which case, the file becomes local to the subprocedure.

The first D specs are the PR(ototypes) for any subprocedures that are going to be used or defined in this source member. It is not compulsory to have them first, but since you will not need to reference or change them very often it is a good idea to have them near the top. Of course any prototypes for subprocedures that you are used in multiple programs should be in a /COPY member and not cloned from program to program. The D and I specs that follow are for data items in the mainline, which are global, i.e., they can be accessed from both mainline logic and any subprocedures in this module.

Following the O specs for the mainline code is the beginning P spec for the first subprocedure. It is followed by the PI (procedure interface) for that subprocedure. Note that as of V6R1, local file definitions may exist in subprocedures. In that case, the F spec(s) fall between the P spec and the PI definition. D and C specs for this subprocedure are next, followed by the ending P spec.

Any other subprocedures would follow this, each with its own pair of beginning and ending P specs.

© Copyright Partner400, 2004-2011. Page 37-38

Procedures using ProceduresHow about a procedure to provide the day name

e.g., "Monday", "Tuesday", etc. for a specified date ? This procedure will use DayOfWeek to obtain the day number

P DayName B D PI 9 D InpDate D

D DayData DS D 9 Inz('Monday') D 9 Inz('Tuesday') D 9 Inz('Wednesday ') D 9 Inz('Thursday' ) D 9 Inz('Friday') D 9 Inz('Saturday' ) D 9 Inz('Sunday')

D DayArray 9 Overlay(DayDat a) Dim(7)

D WorkDay S 1 0 Inz /Free WorkDay = DayOfWeek(InpDate); Return DayArray(WorkDay); /End-Free P DayName E

Prototype for DayName is

shown on notes page below

This chart illustrates how subprocedures can use other subprocedures.

Note the technique to put the 7 values for the names of the days of the week into the array. We're using the ability to overlay the array on the Data Structure to "fill" the array with values. This is a better way than using compile time data to fill the array because it doesn't require us to look elsewhere to find the data going into the array. Also, compile time arrays and tables cannot be defined in subprocedures, so we cannot define our array that way if we want it to be local to the subprocedure.

The important point to notice here is that we have NOT cloned the logic of the DayOfWeek procedure. Instead we are truly re-using the logic of DayOfWeek by _calling_ DayOfWeek.

The new "DayName" procedure will call the "DayofWeek" procedure to get the number of the day of the week. The "DayName" procedure then translates the number into a day name.

Note that we must include 2 prototypes: one for DayName and one for DayofWeek, since DayName calls DayofWeek. It is important to note that ALL prototypes go up in the main part of the program's D specs - never inside the subprocedure itself. Even though in this case, DayName calls DayOfWeek, both the DayOfWeek and DayName prototypes are placed together at the top of the source member with the global D specs.

* Prototype for DayOfWeek procedure

D DayOfWeek PR 1 0 D InputDate D

* Prototype for DayName procedure

D DayName PR 9 D WorkDate D

© Copyright Partner400, 2004-2011. Page 39-40

An alternative approach There's nothing "wrong" with that approach but ....

Earlier we said that a subprocedure can be used anywhere that a variable of its type can be used So we can replace these three lines of code:

With this more streamlined versionNotice that we avoid the need to declare the 'WorkDay' variable !

If you find this hard to understand - do not attempt to learn Java!

Return DayArray(DayOfWeek(InpDate));

D WorkDay S 1 0 Inz

WorkDay = DayOfWeek(InpDate); Return DayArray(WorkDay);

Since subprocedures which return a value can be used anywhere a field name or constant of that type (i.e., alphanumeric or numeric) and size can be used, the second example you see here could be used to incorporate a "cleaner" programming style. After all, why create a field to hold the day number simply to use it as a subscript and then throw it away!

© Copyright Partner400, 2004-2011. Page 41-42

Compiling with SubproceduresWe have looked (so far) at creating internal subpro cedures

To compile these, you may use CRTBNDRPG (or 14 in P DM)But, you must specify Default Activation Group *No

DFTACTGRP(*NO)

This means you are creating a "real" ILE programThe default (unless it has been changed on your system) is DFTACTGRP(*YES), which means create a *PGM that behaves like a non-ILE program

You can do it at compile time, but it's much better to include this keyword on your H spec

That way, you won't forget it next time!

Note that you could also compile using CRTRPGMODIn that case, you must NOT include the DFTACTGRP keyword

H Option(*SrcStmt : *NoDebugIO) DftActGrp(*No)

When you first try to create subprocedures you will probably meet the compiler message "RNF3788 - Keyword EXTPGM must be specified when DFTACTGRP(*YES) is specified on the CRTBNDRPG command". This occurs because the default values on the CRTBNDRPG command assume that the compilation is for an OPM style program (sometimes known as compatibility mode). Subprocedures are an integral part of the ILE environment and, if you are using them you are therefore using features of ILE, so the default value will not suffice.

To avoid this message, you must remember to compile with the option DFTACTGRP(*NO). Better still - embed it on an H-spec in the source so you can't forget.

You may also want to include other H spec keywords such as the ones shown here to make your runtime statement numbers match your source sequence numbers and to make debugging easier by creating only one "step" option for evey I/O statement, such as CHAIN or READ.

Note that if you need to or prefer to compile your code using the CRTRPGMOD command (Create RPG Module) then you must not include the DFTACTGRP keyword since it is not valid for CRTRPGMOD.

© Copyright Partner400, 2004-2011. Page 43-44

Pause for ThoughtSo far we've just been using procedures in the prog ram

As sort of "muscled up" subroutines

What if we want to reuse them easily ?The answer is Service Programs !!

But there are some changes neededWe must separate the components:

The prototypesThe main program logicAnd the subprocedures

And add some additional keywords to the subprocedures

Since these two subprocedures might be very useful in many other programs that use dates, it would be a good idea to put this type of subprocedure into an ILE Service Program. That way, many programs can access one copy of these subprocedures.

On the following chart, we will see how the source needs to be "cut up" into the individual building blocks that will be used.

© Copyright Partner400, 2004-2011. Page 45-46

D DayOfWeek PR 1S 0 D ADate D DatFmt(*USA ) Const

D DayName PR 9 D ADate D DatFmt(*USA ) Const

: : : DayNumber = DayofWeek(InputDate); DayChar = DayName(InputDate); : : : P DayOfWeek B

D DayOfWeek PI 1S 0 D WorkDate D DatFmt(*USA ) Const : : : Return WorkDay;

P DayOfWeek E

P DayName B

D PI 9 D InpDate D DatFmt(*USA ) Const : : :

Separating the Components

Main program Member

Subprocedures Member

Prototypes Member

This chart represents the source file as we now have it.

The prototypes for the two subprocedures are at the top, they are followed by the main processing logic that invokes the subprocedures. Last but not least is the logic for the subprocedures themselves.

Start by "cutting" the prototypes from the source and place them in a separate source member. Later on we may add other prototypes for new date subprocedures (or indeed any new subprocedure). Do not make the mistake of including H specs in the prototype! Remember the prototype type source will never be compiled on its own. It is compiled by being /COPY'd into the source of the subprocedures and any other programs that wish to use the subprocedures.

Note that whenever we use the term "/COPY" we mean the compiler directive /COPY and NOT copy as in CC in SEU!!!

Next "cut out" the source for the subprocedures and place them in their own source member as we will be compiling them separately to create the service program. Don't forget that we will also need to add a statement to /COPY the prototypes into this source member as well. On the next chart we will look at the other changes that we will need to make to the subprocedures.

What we are left with will be the source for the main (using) program. We will of course need to add a /COPY directive to bring the prototypes into the source. Without those prototypes, the compiler will not know how to interpret the calls to DayName and DayOfWeek.

© Copyright Partner400, 2004-2011. Page 47-48

Changes to Subprocedures MbrCopy the subprocedure logic into a new source memberAdd an H-spec with the NOMAIN keyword

This will make the resulting module "cycle-less" Do not include a DFTACTGRP keyword on this H spec

Add the /COPY directive that will bring in the prototypesBefore the first P spec

Add the keyword EXPORT to the P-specsThis makes the procedures "visible" outside of the module

H NOMAIN /Copy DateProtos

P DayOfWeek B EXPORT D DayOfWeek PI 1S 0 D WorkDate D // Subprocedure's logic is here P DayOfWeek E

P DayName B EXPORT D DayName PI 9 D InpDate D // Subprocedure's logic is here P DayName E

The steps identified on this chart should be performed for any subprocedures that will be placed into a separate module from the program or procedure that calls them. The most common time to use this is when the subprocedures are to be placed in an ILE Service Program.

The NOMAIN keyword will provide much faster access to these subprocedures from other modules. It also tells the compiler NOT to include any RPG cycle logic in this module. The NOMAIN keyword is only allowed if/when there are no main line calculations in the source member PRIOR TO the first subprocedure. In other words, NOMAIN can only be used if there is no main procedure logic coded in this module.

EXPORT makes the name of the procedure "visible" outside of the Module. If it was not made visible in this way it could not be called by anyone outside of the module. For example, if DayOfWeek did not have the EXPORT keyword, it could still be called by DayName but not by any code outside of the module.

To compile the subprocedure source:

We will compile using the CRTRPGMOD command, this will create a *MODULE object that we can then either build into a Service Program, or bind directly to the main module.

If you are using PDM, option 15 will run the CRTRPGMOD command for you. Don't forget that if you want to use the debugger on the resulting objects you must request that now. We recommend that you use the *LIST (or *ALL) options.

If you choose to create a Service Program (and after all that is what we are discussing here) you will next use the CRTSRVPGM command. You can give the Service Program its' own name, or name it after the module. For simplicity you should also specify the option *ALL for the "Export" parameter. Without this, the command will be looking for Binder Language. If you are not using Binder Language, the CRTSRVPGM command will fail.

© Copyright Partner400, 2004-2011. Page 49-50

Changes to Main Program Mbr Delete the prototype entries in the source member

Recommended: Add an H spec keyword for your Binding DirectoryIn some cases, you may need to remove DFTACTGRP(*No)

Add a /COPY directive in their place This will be identical to the /COPY we placed in the subprocedure source

Fully qualified syntax (no spaces allowed): /Copy Library/SourceFile,Member

Next delete all of the subprocedure logicWe will be "binding" this program to the logic in the Service Program

Note that you now have three source membersThe main line programThe subprocedure sourceAnd the prototype source

All that remains is to compile and bind the pieces

H BNDDIR('UTILITY') DFTACTGRP(*NO) /Copy DateProtos : : : DayNumber = DayofWeek(InputDate); DayChar = DayName(InputDate); : : :

Just a reminder that you don't need to compile the prototypes by themselves (in fact they won't compile!). They will be processed by the compiler when it encounters the /COPY directives in the other sources. Also remember not to include H specs in your prototype source member - they will almost certainly result in an "out of sequence" error when incorporated into the main line or subprocedure logic. (The exception to this rule is when you are using conditional compiler directives to control what gets copied - but that topic is outside the scope of this session)

Creating the Main Program and linking to the Servic e Program:

Depending on whether you want to do your binding by compiling the program entry procedure, you may need to remove the DFTACTGRP parameter we had in there earlier. If you want to compile this with CRTRPGMOD (PDM option 15) and then use a separate CRTPGM step, you must remove it. Otherwise, leave it in and do the bind and compile at the same time with CRTBNDRPG (PDM option 14).

Once the module has been created, we need to link it to our Service Program to create the *PGM object. To do this use the CRTPGM command. For simplicity specify the name of your module as the program name, and the name of the Service Program you just created as the "Bind Service Program" entry. Note that if you do not see this parameter, press F10 to list all parameters.

If you don't want to create a Service Program, then you can simply compile both the main line and the subprocedures using CRTRPGMOD (PDM option 15). Then when both modules have been compiled, use CRTPGM specifying the names of both modules (the main program should be specified first) to "bind" them together. This will create a program that is "Bound by copy". Again this is fine for experimenting with subprocedures, but often isn't optimal for production code.

For more details on how and why to use Service Programs vs. modules and binding by copy, refer to the session on the Integrated Language Environment (ILE) and static binding.

© Copyright Partner400, 2004-2011. Page 51-52

Putting the Pieces Together with ILECompile the source member with the subprocedure cod e

CRTRPGMOD DATEPROCS

Create a Service Program for the subproceduresCRTSRVPGM DATESTUFF Module(DATEPROCS) Export(*ALL)Alternatively, add the new module to an existing Service Program

Put Service Program name into a Binding DirectoryCreate Binding Directory (if it doesn't already exist)

CRTBNDDIR MYLIB/UTILITY (if necessary)

ADDBNDDIRE UTILITY OBJ((DATESTUFF *SRVPGM))

Compile and bind main program code - the caller(s) MYPGM into a module (CRTRPGMOD)

Then bind it by copy using CRTPGM MYPGM MODULE(MYPGM)Your binding directory specified on the H spec will bind to the Service Program

Alternatively, CRTBNDRPG MYPGMSpecify DFTACTGRP(*No) on H spec along with binding directory

To compile the subprocedure source and create the S ervice Program:

We will compile using the CRTRPGMOD command, this will create a *MODULE object that we can then either build into a Service Program, or bind directly to the main module. If you are using PDM, option 15 will run the CRTRPGMOD command for you.

If you choose to create a Service Program (and after all that is what we are discussing here) you will next use the CRTSRVPGM command. You can give the Service Program its own name in case you want to put more procedures into this Service Program later or you may want to name it after the module if you will have only one module in the Service Program. You should also specify the option *ALL for the "Export" parameter since we're not using Binder Language. We'll also add the name of our Service Program to a Binding Directory to make it easier to bind it to many programs.

If you choose to add the module to an existing Service Program, you will need to take some action to manage the Service Program's signature. We'll be covering the management of signatures shortly.

Creating the Main Program and linking to the Servic e Program:

You could use CRTBNDRPG (option 14) to compile the main program and bind at the same time. Because of the Binding Directory name that we included in the H spec, the binder will find the Service Program and bind the program to the Service Program by reference.

We could create a *MODULE object using CRTRPGMOD (PDM option 15) and then use CRTPGM to bind the program and service program. Again, the binding directory we included on the H spec tells the binder where to find the procedures needed. If we were to compile using CRTRPGMOD, we would need to remove the DFTACTGRP(*NO) keyword from the H spec.

© Copyright Partner400, 2004-2011. Page 53-54

D DayOfWeek PR

: D DayName PR

:

/COPY DATEPROTO

P DayOfWeek B D DayOfWeek PI : P DayName B D DayName PI

:

/COPY DATEPROTO : Eval PrintName = DayName(Today) :

/COPY DATEPROTO : Eval PrintName = DayName(Today) :

/COPY DATEPROTO : Eval PrintName = DayName(Today) :

"User" Programs

"Defining" Source Member

Prototypes

or any program or procedure that uses it

Reminder on Prototype Usage

The prototype must be present:When compiling the procedure

RPG 7.1 Update: Prototypes are optional in some circumstances

One of the most common mistakes made when programmers first begin using subprocedures and prototypes is to fail to include prototypes in all the places where they are needed.

This chart tries to make the rules more clear. These three blocks represent the three components that we just split our program into. i.e. The prototypes, main program, and subprocedures. The term "Defining Source Member" on this chart means the module (source member) where the logic of the subprocedure is coded. The "User Program(s)" are any modules (source members) that will call or refer to these subprocedures.

The prototypes must be present in the source member where the RPG IV subprocedures are DEFINED and also in every source member where the subprocedures will be USED. This is so that the compiler can check the prototype against the Procedure Interface (PI). This rule may seem silly to you, but the compiler enforces it so .........

The fact that these prototypes are required in multiple places is the reason we strongly recommend that the /COPY directive be used to bring in the prototype code. This way, you can ensure the correct prototype is always used.

© Copyright Partner400, 2004-2011. Page 55-56

Subprocedures and Modular CodeSubprocedures are the best way to write modular RPG logic

Internal subprocedures make for great subroutine replacementsLogic and data flow is more obviousLocal data makes maintenance faster, easier and safer

External subprocedures make for efficient code re-useEspecially when placed in Service ProgramsEasily shared among multiple programs at "subroutine-like" speed

Get into the habit of utilizing subprocedures Build up your own library of callable "non-built-in functions"

Writing modular RPG logic using subprocedures ultimately makes your life easier. Like any new coding practice, it takes some time and effort to become comfortable working with these new techniques.

© Copyright Partner400, 2004-2011. Page 57-58