Prototypes vs Entry

Embed Size (px)

Citation preview

  • 8/3/2019 Prototypes vs Entry

    1/9

    Replacing *ENTRY PLISTA prototype is used to call a program, procedure, or Java method. Another device, a procedure interface(PI), is used to receive parameters.

    If you've worked with subprocedures, you know that you can use a PI statement to declare parameters into asubprocedure. Did you realize that you can also use a PI statement to get input into a program?

    Before the PR and PI were introduced, you used *ENTRY PLIST to receive parameters into a program.Here's what that looked like:

    D Amount s 9S 2D Tax s 9S 4

    C *ENTRY PLISTC PARM AmountC PARM Tax

    You can do the same thing with a procedure interface. If you code a PI statement before any P-specs, thecompiler knows that it's for parameters coming into your program. When you code a PI statement after a P-spec, the compiler knows that it's for a subprocedure.

    In the following example, the PI is at the top of the program, before any P-specs. Therefore, it's areplacement for *ENTRY PLIST because it describes the parameters that come into the program and not asubprocedure:

    /copy CALCTAX_H

    D CALCTAX PID Amount 9S 2D Tax 9S 4

    C eval(h) Tax = Amount * 0.05C return

    When you use a PI statement instead of an *ENTRY PLIST, the compiler requires a prototype that matches

    the PI. The /copy member for the CALCTAX program looks like this:

    D CALCTAX PR ExtPgm('CALCTAX')D Amount 9S 2D Tax 9S 4

    Why did I put this in a /copy member? Because I want to be sure that when other programs call this one,they provide the correct parameters. The compiler makes sure that the PR matches the PI, so I know thatthe parameters in my /copy member match the ones in my main program. I can then use the /copy directiveto bring this prototype into the code of any programs that want to call the CALCTAX program. When theyuse this prototype, they're forced to pass the same size and length parameters declared on the prototype.Because the compiler made sure the prototype matched the PI, I also know that the caller's parametersmatch the PI.

    You don't have to use a /copy member if you don't want to, of course. When you have no control over thecode that calls your program, it makes little sense to put the prototype in a /copy member. Similarly, if yourprogram will never be called from another RPG program, a /copy member makes no sense.

    In cases where a /copy member doesn't make sense, you can code the prototype directly above the PI. Forexample:

    D CALCTAX PR ExtPgm('CALCTAX')D Amount 9S 2D Tax 9S 4

  • 8/3/2019 Prototypes vs Entry

    2/9

    D CALCTAX PID Amount 9S 2D Tax 9S 4

    C eval(h) Tax = Amount * 0.05C return

    Under the covers, the PI statement works the same way that *ENTRY PLIST does, so your older programscan still call the preceding code using CALL/PARM, or with the CALL command from CL, or using any othermethod that you could use to call an *ENTRY PLIST program.

    Using a PI instead of *ENTRY PLIST offers several advantages:

    It verifies that the parameters match what the program is expecting (as described earlier).

    *ENTRY PLIST doesn't exist in free-format RPG.

    You can use D-spec keywords to get additional functionality. (I describe this next.)

    The EXTPGM KeywordA prototype can be used to call subprocedures, programs, and Java methods. The EXTPGM keyword

    specifies that a prototype is used to call a program. In the preceding example, it calls a program namedCALCTAX.

    One nice feature of EXTPGM is that it can "rename" a program within your code. If you're stuck with an uglynaming convention, you can give a more meaningful name to your program call. For example, some shopsrequire a three-character abbreviation followed by a number, followed by a code that identifies theprogramming language. A program that centers a string might therefore be called CTR001R4. You can useEXTPGM to give it a meaningful name while still adhering to the naming convention. For example:

    D Center PR ExtPgm('CTR001R4')D String 65535A options(*varsize)D Length 15P 5 const

    I put CTR001R4 in the EXTPGM, so the actual program name called is CTR001R4. However, when I call it

    using this prototype, I can simply refer to it as Center.

    ErrMsg = 'Customer number not found';callp Center(ErrMsg: %size(ErrMsg));

    The CONST KeywordThe CONST keyword tells the compiler that your program won't change a parameter. In other words, whenyou code CONST, you're telling the system that the parameter is for input only.

    The CALCTAX program has two parameters. One of them, the Amount field, is used for input only. You can,therefore, code CONST on that parameter as follows:

    D CALCTAX PR ExtPgm('CALCTAX')D Amount 9S 2 const

    D Tax 9S 4

    D CALCTAX PID Amount 9S 2 constD Tax 9S 4

    C eval(h) Tax = Amount * 0.05C return

    Using CONST this way provides some advantages:

  • 8/3/2019 Prototypes vs Entry

    3/9

    It's self-documenting. You can determine which parameters are for input and which are for output.

    The compiler verifies that the code doesn't change the parameter. This happens at compile-time;the compiler checks your code to make sure that nothing changes the parameter.

    Calling the program is easier because the compiler knows that the parameter is input-only. (Readon.)

    Because the Amount parameter is defined as CONST, the compiler can do some work for the caller. Forexample, if you wanted to pass a literal to the program, and you didn't use CONST, you'd have to first assignthe literal to a temporary variable, and then pass that. With CONST, you can just pass the literal. Thecompiler converts it to the right size and data type for you.

    D TempAmount s 9S 2D Tax s 9S 4/free

    // Without CONST:TempAmount = 123.45;CalcTax( TempAmount: Tax);

    // With CONSTCalcTax(123.45: Tax);

    What actually happens is that the compiler creates a hidden temporary variable for you. It assigns the literalto the temporary variable and passes it to the CALCTAX program. This saves you from having to do thatmanually.

    The same is true when you have a variable that's a different data type or length. The compiler automaticallycreates a temporary variable, copies the value into it, and passes the temporary field. Consider the followingexample:

    D Subtotal s 8P 2D TempAmount s 9S 2D Tax s 9S 4

    // Without CONST:TempAmount = Subtotal;CalcTax(TempAmount: Tax);

    // With CONSTCalcTax(Subtotal: Tax);

    As you can see, because of CONST, reusing the CALCTAX program is easier. The caller doesn't have to dospecial conversions before calling it.

    In fact, this same behavior makes passing an expression for the input parameter possible. Here's anexample of that:

    // Without CONST:TempAmount = Subtotal + Charges - Discounts;CalcTax(TempAmount: Tax);

    // With CONSTCalcTax(SubTotal + Charges - Discounts: Tax);

    The fact that CONST was defined for the input parameter makes calling the CALCTAX program easier.That's important because I write my CALCTAX program only once, but I call it from many places.

    OPTIONS(*NOPASS)Prototypes enforce your program's rules. They make sure that the parameters passed to your program havethe right data types and lengths. They also make sure that the caller passes all the parameters that yourprogram requires. They won't let the caller leave anything off. But what if you wanted to leave something off?

  • 8/3/2019 Prototypes vs Entry

    4/9

    Sometimes passing fewer parameters makes program maintenance easier. Consider the following programthat calculates the price of an item:

    FPRICELIST IF E K DISK

    /copy prototypes,getPrice

    D GetPrice PID ItemNo 5P 0 constD Zone 1A constD Price 9P 2

    /freechain (ItemNo:Zone) PRICELIST;

    if %found;Price = plPrice;

    else;Price = -1;

    endif;

    return;

    /end-free

    Now let's say a need comes up to sell in different countries. You want to call a program called EXCHRATEto convert from U.S. dollars to that country's currency.

    You don't want to change the existing programs that call GETPRICE; they're working fine. When they call it,you want it to assume the price is for the United States. However, future programs might pass an extraparameter to tell it the country code that the price is for. With *ENTRY PLIST, you could do this:

    C *ENTRY PLISTC PARM ItemNoC PARM ZoneC PARM PriceC PARM pCountry

    c if %parms >= 4c eval Country = pCountryc elsec eval Country = 'USA'c endif

    /freechain (ItemNo:Zone) PRICELIST;

    if not %found;Price = -1;return;

    endif;

    callp EXCHRATE('USA': Country: plPrice: Price);

    return;/end-free

    To do that with a prototype, you can code OPTIONS(*NOPASS) on the parameters. It works the same way when you specify OPTIONS(*NOPASS), you tell the compiler that some of the parameters at the end ofthe list can be left off. You can then use the %PARMS built-in function (BIF) to check how many parameterswere passed.

    FPRICELIST IF E K DISK

  • 8/3/2019 Prototypes vs Entry

    5/9

    /copy prototypes,getPrice

    D GetPrice PID ItemNo 5P 0 constD Zone 1A constD Price 9P 2D pCountry 3A const options(*nopass)

    D Country S 3A inz('USA')/free

    if %parms >= 4;Country = pCountry;

    endif;

    chain (ItemNo:Zone) PRICELIST;

    if not %found;Price = -1;return;

    endif;

    callp EXCHRATE('USA': Country: plPrice: Price);

    return;/end-free

    Because this technique works by looking at the number of parameters passed, you can use it only to leaveparameters off the end. After one parameter is declared with OPTIONS(*NOPASS), everything after it mustalso be declared with OPTIONS(*NOPASS).

    OPTIONS(*OMIT)What about omitting parameters in the middle of the parameter list? Sometimes you have an optionalparameter, but it isn't at the end of the list. In that case, you can use OPTIONS(*OMIT).

    What confuses people about OPTIONS(*OMIT) is that it does notallow you to omit parameters. You stillhave to pass something for each parameter. But you can pass a special value of *OMIT. When thathappens, the compiler sets the address of the parameter to *NULL when it calls the program.

    For example, what if you wanted the Zone parameter of the previous example to be optional? When notpassed, you want to use zone A. When passed, you want to use the zone passed. For example:

    FPRICELIST IF E K DISK

    /copy prototypes,getPrice

    D GetPrice PID ItemNo 5P 0 constD pZone 1A const options(*omit)D Price 9P 2D pCountry 3A const options(*nopass)

    D Zone S 1AD Country S 3A inz('USA')/free

    if %addr(pZone) *NULL;Zone = pZone;

    endif;

    if %parms >= 4;Country = pCountry;

    endif;

  • 8/3/2019 Prototypes vs Entry

    6/9

    chain (ItemNo:Zone) PRICELIST;

    if not %found;Price = -1;return;

    endif;

    callp EXCHRATE('USA': Country: plPrice: Price);

    return;/end-free

    What this basically does is establish a default value for the Zone parameter. The caller can call it like this:

    callp GetPrice(ItemNo: *OMIT: Price: Country);

    In this case, it uses the default zone of A. On the other hand, the caller can call it as follows if the callerwants to specify a price zone:

    callp GetPrice(ItemNo: 'B': Price: Country);

    I could've used a variable for the price zone as well. Because it's CONST, I was able to pass the literal 'B'instead of a variable. I also could've left the country code off, because OPTIONS(*NOPASS) was specifiedfor that parameter.

    Combining Options on the OPTIONS KeywordYou might be wondering, "What if I wanted a parameter to be both *OMIT and *NOPASS? Can it be done?"The answer is yes. You can specify more than one OPTIONS keyword on a prototype (or PI) simply byseparating them with colons. For example:

    D GetPrice PID ItemNo 5P 0 constD pZone 1A const options(*omit)D Price 9P 2

    D pCountry 3A constD options(*nopass:*omit)

    In this case, the country code is both *NOPASS and *OMIT. That means that you can pass a country code,or you can leave it off, or you can specify the special value of *OMIT.

    D Country S 3A inz('USA')..if %parms >= 4 AND %addr(pCountry) *NULL;

    Country = pCountry;endif;

    This code first checks whether a Country parameter was passed by making sure that at least four

    parameters were passed to the subprocedure. If so, it checks whether the fourth parameter was *OMIT bychecking whether the parameter's address is *NULL. Only if it was both passed and not *NULL can it beused.

    Note: I emphasize that you doneed to check both %PARMS and %ADDR when a parameter is defined withboth *NOPASS and *OMIT. I frequently get questions from people who checked only the address. At first,this might seem to work, because the iSeries initializes memory to hex zeroes (which is the same as*NULL). However, it won't work reliably all the time, and your program might fail unexpectedly in production.Please make sure that if you specify both *NOPASS and *OMIT, you check for both %PARMS and %ADDR.

  • 8/3/2019 Prototypes vs Entry

    7/9

    OPTIONS(*VARSIZE)A prototype makes sure that your parameters are as long as the ones that the program is expecting. This isusually what you want, but once in a while, situations occur in which you'd be happy to allow a smallervariable.

    For example, what if you wrote a program that centered the contents of a character field? You wouldn't wantto limit the caller to always passing a field of a particular size. You'd want your program to work with any

    size.

    Earlier in this article, I referred to a program called CTR001R4 that centers a string. It has the followingprototype:

    D Center PR ExtPgm('CTR001R4')D String 65535A options(*varsize)D Length 15P 5 const

    As I said, I wouldn't want to force every program that calls this to pass a 65,535-character string. That wouldbe frustrating for callers to always have to move their data to a field that large. I want to make it as easy aspossible for callers to call my routine.

    However, I can't use CONST for the String parameter, because I need to be able to return the centeredstring. It's not input only!

    That's why I coded OPTIONS(*VARSIZE). This option tells the compiler not to check the length of thecaller's variable. All it does is turn off one of the compiler's safety checks.

    Because the caller can pass any size variable it wants, it does not have to pass a 65,535-character string.Therefore, I have to ensure that I never look at the part of the string that it didn't pass. That's why I also hadit pass a Length parameter. I can check the Length parameter to see how long the string is. Consider thefollowing code:

    /copy prototypes,centerD Center PID String 65535A options(*varsize)

    D Length 15P 5 const

    D len s 10I 0D trimlen s 10I 0D start s 10I 0D Save s 65535A varying

    /freelen = Length;Save = %trim(%subst(String:1:Len));trimlen = %len(Save);start = len/2 - trimlen/2 + 1;%subst(String:1:len) = *blanks;%subst(String:start:trimlen) = Save;return;

    /end-free

    The code that's red in this example uses the %SUBST BIF to make sure that it never refers to a part of thestring that wasn't passed. When I use %TRIM to trim the input string and save it to a temporary variable, I'mcareful to trim and save only the part that was passed. When I blank it out, I'm careful to set only the partthat was passed to *BLANKS.

    Disabling the compiler's check of the parameter's length is dangerous, but no more dangerous than usingCALL/PARM. I had to be very careful, because if I wrote data past the part of the string that my callerprovided, I could corrupt memory. This is the same danger involved with using CALL/PARM.

  • 8/3/2019 Prototypes vs Entry

    8/9

    OPTIONS(*RIGHTADJ)Back in V4R4, IBM added OPTIONS(*RIGHTADJ) to prototypes. This option comes into play when thecompiler creates a temporary variable for use with the CONST option. It tells the compiler that data assignedto the temporary variable should be right adjusted.

    For example, consider the following prototype:

    D MyProgram PR ExtPgm('MYPGM')D Parm1 20A const options(*RightAdj)

    If I call this program with a value shorter than 20 characters, the compiler right adjusts it. For example:

    /freeMyProgram('Test-O-Matic');

    /end-free

    When I run that code, the MYPGM program receives the results right adjusted. Because the data that Ipassed is 12 characters long, the result contains 8 blanks followed by my 12 characters and looks like this:

    ' Test-O-Matic'

    OPTIONS(*TRIM)In V5R3, IBM added OPTIONS(*TRIM) to allow the removal of blanks from a character string. In V5R2 andolder releases, blanks must be manually trimmed using the %TRIM BIF. Consider the following prototype:

    D JoinName PR ExtPgm('JOINNAME')D First 30A varying constD Last 30A varying constD WholeName 50A

    This is for a program that joins a first and last name to create a whole name. Here's the code for theprogram:

    /copy prototypes,joinnameD JoinName PID First 30A varying constD Last 30A varying constD WholeName 50A/free

    Wholename = Last + ', ' + First;return;

    /end-free

    When you call this program from your V5R2 or earlier system, you must use %TRIM to remove blanks fromthe parameters. Here's an example of calling it from V5R2:

    /copy prototypes,joinnameD First s 20A inz(' Scott ')

    D Last s 20A inz(' Klement ')D Whole s 50A/free

    JoinName(%trim(First): %trim(Last): Whole);// result is: "Klement, Scott "

    In this example, the CONST keyword tells the compiler to create temporary variables to pass to theJOINNAME program. The %TRIM BIF is used to strip the extra spaces off the variables before they'repassed to the program.

  • 8/3/2019 Prototypes vs Entry

    9/9

    In V5R3, you can use OPTIONS(*TRIM) so that trimming the parameters manually is unnecessary. To usethis feature, I change the prototype so that it looks like this:

    D JoinName PR ExtPgm('JOINNAME')D First 30A varying constD options(*trim)D Last 30A varying const

    D options(*trim)D WholeName 50A

    I also have to change the JOINNAME program's PI to match the prototype, so it looks like this:

    /copy prototypes,joinnameD JoinName PID First 30A varying constD options(*trim)D Last 30A varying constD options(*trim)D WholeName 50A/free

    Wholename = Last + ', ' + First;return;

    /end-free

    With these changes, I no longer have to use the %TRIM BIF when I call this routine. Instead, I can simplycode the following, because the compiler automatically trims the blanks for me:

    /copy prototypes,joinnameD First s 20A inz(' Scott ')D Last s 20A inz(' Klement ')D Whole s 50A/free

    JoinName(First: Last: Whole);// result is: "Klement, Scott