Test268 Section 4

From MidrangeWiki
Revision as of 15:05, 2 January 2007 by Kjburkhalter (talk | contribs) (Enumerate advantages of Prototypes compared with PARM and PLIST)
Jump to: navigation, search

<< Previous Section | Home | Next Section >>

Section 4 - Advanced RPG techniques (25%)

Given an example of a complex logical expression, determine its results

Given an example of deeply-nested logic within a sample of RPG code, determine the results of running the code

Use Data Structure arrays

If MyDS is a data structure array of 10 elements, then MyDS(5) will access the fifth element of that data structure array. But how do you access the subfields? That requires a bit more discussion.

When you define a data structure array, you not only use the Dim keyword, but you must also specify the Qualified keyword. The Qualified keyword was introduced in V5R1, and it allows you to specify the name of a data structure subfield qualified by the name of the data structure. For example, if data structure MyDS is defined with the Qualified keyword and it contains a subfield named Subfield1, then you would use the following notation to access the subfield:

MyDS.Subfield1

If data structure MyDS is also defined with the Dim keyword, then something like the following notation is used to access subfields in a specific element:

MyDS(5).Subfield1

In this example, you would be accessing the subfield named Subfield1 in the fifth element in the data structure array MyDS.

Code complex D-specs (e.g., OVERLAY, coding fields without attributes, etc.)

D DataStruct1     DS                  QUALIFIED
D  Field1                       15A   INZ('12345ABCDE12345')
D   Field2                       5A   OVERLAY(Field1)
D   Field3                       5A   OVERLAY(Field1:*NEXT)
D   Field4                       5A   OVERLAY(Field1:*NEXT)
D   Field5                       7A   OVERLAY(Field3:3)

D DataStruct2     DS                  LIKEDS(DataStruct1) INZ(*LIKEDS)
D  Name           S             20
D  Long_name      S             +5    LIKE(Name)
D  Lda_fld        S                   LIKE(Name) DTAARA(*LDA)

Use modern techniques to handle numbered indicators

Named Indicators Indicators are now a recognized data type in RPG, so we can define them in the D Specs, as shown below. Note that when testing an indicator you do not need to compare it against *ON or *OFF.

  DName+++++++++++ETDsFrom+++To/L+++IDc.Keywords+++++
  D BadStatus       S               N     
  D MonthEnd        S               N


The below code shows a named indicator being used for printer overflow. This is a simple example of the benefit of a named indicator; the name Overflow is more self-explanatory than *IN96 or *INOV. Also, you don't need to define the Overflow indicator in the D Specs; the compiler will define it for you.

  FReport    O    E             Printer OflInd(OverFlow)  
                                                                                                  
      Write Detail;
      If OverFlow;
          Write Header;
          OverFlow = *Off;
      EndIf;


Logical Expressions The below code shows the traditional means of setting an indicator; it should look familiar. It is setting an error condition if it is a month end and if the status is neither 'I' nor 'C'. At least I think it is setting an error condition; that is what indicator 31 is -- right?

   *In31 = *Off;
   If EndMonth = 'Y';           
       If Status <> 'I' and Status <> 'C';
           *In31 = *On;
       EndIf;        
   EndIf;   


And below you'll the same piece of code as a logical expression. The result of a logical expression is true or false, therefore it can be assigned directly to an indicator. Also, the EndMonth field has been replaced by the indicator MonthEnd, and *IN31 has been replaced by the indicator BadStatus.

   If MonthEnd;
       BadStatus = (Status <> 'I' and Status <> 'C');
   EndIf;  


Built In-Functions We no longer need to use resulting indicators on operation codes. Instead, we can use the relevant I/O BIF. The I/O BIFs are %OPEN, %FOUND, %EOF, %ERROR, %EQUAL and %STATUS. The code below shows an example of using the %OPEN, %ERROR and %FOUND BIFs. The %OPEN BIF indicates whether or not the file is open. The %ERROR BIF is set when the E extender is defined on an operation code (Chain in this case). We use the E extender in place of an error indicator in the low position. The %FOUND BIF is set automatically by the Chain operation.

    If  %Open(FileB);
       Chain(E) Key FileB;
       Select;
         When %Error;
           Dsply 'File Error'; 
         When %Found(FileB);
           Dsply 'Got It';  
       EndIf; 
    EndIf;

One of the nice things about %FOUND is that it reads the right way round, as opposed to the indicator on the CHAIN operation where the indicator is true if the record is NOT found.

Indicator Data Structure By default, the indicators 01 to 99 on a display or print file are mapped to the indicators 01 to 99 in an RPG program. But when we use the file keyword INDDS (Indicator Data Structure), they are mapped to a data structure instead of the indicators. In other words, the 99 indicators used with the display or print file are now associated with the data structure instead of the indicators *IN01 to *IN99 in the program.

The code below shows an example of using the INDDS. The indicators for the display file are mapped to the data structure DspInd. DspInd is a 99 byte data structure, with each byte corresponding to one of the 99 indicators for the display file. This means that for the display file, we MUST use the indicators in the data structure and NOT the numbered indicators. For example, in this program, if we turned on indicator *IN31, it would have no impact on the display file. The program logic must refer to the indicator Error in order to have an impact on the display file. Named indicators are a lot easier to decipher!

FDisplay   CF   E             WORKSTN INDDS(DspInd) 
    
D DspInd         DS                                        
 * Response indicators              
D   F3Exit               3      3N                         
D   F12Cancel           12     12N                         
 * Conditioning indicators  
D  AllErrors            31     33    
D   Error               31     31N                         
D   StDateErr           32     32N                         
D   EndDateErr          33     33N                         
                                  
       Eval      AllErrors = *Zeros;  
                   
       StDateErr = StartDate < Today;
       EndDateErr = EndDate < StartDate;
       Error = StDateErr or EndDateErr;
       ExFmt     MyScreen;  
                         
       If  F3Exit or F12Cancel; 


The use of INDDS requires the use of the file level keyword INDARA in the DDS for the display/print file. In most cases, changing a file to use INDARA will have no effect unless a RESET or CLEAR operation is used. In this case it will now be necessary to also RESET/CLEAR the associated indicators.

Determine appropriate use of system APIs

Code subprocedures

  • Internal Program Sub-Procedure
D Inter_Func      PR            10I 0              
D  Term1                         5I 0 VALUE
D  Term2                         5I 0 VALUE
D  Term3                         5I 0 VALUE
  • External Module Sub-Procedure
D Extrn_Func      PR            10I 0 EXTPROC('MODULE_FUCT')             
D  Term1                         5I 0 VALUE
D  Term2                         5I 0 VALUE
D  Term3                         5I 0 VALUE
  • External Program Sub-Procedure
D Pgm_Func        PR            10I 0 EXTPGM('PGM_FUCT')             
D  Term1                         5I 0 VALUE
D  Term2                         5I 0 VALUE
D  Term3                         5I 0 VALUE

Declare and use subprocedures

Declare the Function and call it

D Function        PR            10I 0              
D  Term1                         5I 0 VALUE
D  Term2                         5I 0 VALUE
D  Term3                         5I 0 VALUE
D  RC             S             10I 0 INZ(*ZEROS)
 /free
   RC = Function(Term1:Term2:Term3);
   *INLR = *ON;
   RETURN;
 /end-free

start the function and return the results

P Function        B                             
D Function        PI            10I 0              
D  Term1                         5I 0 VALUE
D  Term2                         5I 0 VALUE
D  Term3                         5I 0 VALUE
D Result          S             10I 0                 
 /free
   Result = Term1 ** 2 * 17 + Term2 * 7 + Term3;
   return Result;
 /end-free
P Function        E

Create and use multiple occurrence data structures

 DS1 and DS2 are multiple occurrence data structures.
 Each data structure has 50 occurrences.
 D DS1             DS                  OCCURS(50)
 D  FLDA                   1      5
 D  FLDB                   6     80
  *
 D DS2             DS                  OCCURS(50)
 D  FLDC                   1      6
 D  FLDD                   7     11

DS1 is set to the third occurrence. The subfields FLDA and FLDB of the third occurrence can now be used. The MOVE and Z-ADD operations change the contents of FLDA and FLDB, respectively, in the third occurrence of DS1.

 C
 C     3             OCCUR     DS1
 C                   MOVE      'ABCDE'       FLDA
 C                   Z-ADD     22            FLDB

DS1 is set to the fourth occurrence. Using the values in FLDA and FLDB of the fourth occurrence of DS1, the MOVE operation places the contents of FLDA in the result field, FLDX, and the Z-ADD operation places the contents of FLDB in the result field, FLDY.

 C
 C     4             OCCUR     DS1
 C                   MOVE      FLDA          FLDX
 C                   Z-ADD     FLDB          FLDY

DS1 is set to the occurrence specified in field X. For example, if X = 10, DS1 is set to the tenth occurrence.

 C     X             OCCUR     DS1

DS1 is set to the current occurrence of DS2. For example, if the current occurrence of DS2 is the twelfth occurrence, DSI is set to the twelfth occurrence.

 C     DS2           OCCUR     DS1

The value of the current occurrence of DS1 is placed in the result field, Z. Field Z must be numeric with zero decimal positions. For example, if the current occurrence of DS1 is 15, field Z contains the value 15.

  C                   OCCUR     DS1           Z
  C

DS1 is set to the current occurrence of DS2. The value of the current occurrence of DS1 is then moved to the result field, Z. For example, if the current occurrence of DS2 is the fifth occurrence, DS1 is set to the fifth occurrence. The result field, Z, contains the value 5.

  C
  C     DS2           OCCUR     DS1           Z

DS1 is set to the current occurrence of X. For example, if X = 15, DS1 is set to the fifteenth occurrence. If X is less than 1 or is greater than 50, an error occurs and %ERROR is set to return '1'. If %ERROR returns '1', the LR indicator is set on.

  C
  C     X             OCCUR (E) DS1
  C                   IF        %ERROR
  C                   SETON                                        LR
  C                   ENDIF

Use externally-described data structures

Declare an externally-described data structure based on the inventory file (INVNTRY).

    d InvRec        e ds                  Based(pInvRec) ExtName(INVNTRY)

Write logic (including I/O operations) without numbered indicators

/Free
 SETLL (variable1:variable2) filename;
 IF %EQUAL(filename);
    READE(N) (variable1:variable2) filename;
    DOW NOT %EOF(filename);
       // logic
       READE(N) (variable1:variable2) filename;
    ENDDO;
 ENDIF; 
/End-Free
/Free
 CHAIN (variable3) filename;
 IF %FOUND(filename);
    // logic
    UPDATE filerec;
 ENDIF;
/End-Free

Code and use /Free format Calc specifications

/Free
 // Convert from a char to Num
 num = %DEC(char);
 // Chain to the correct record, checking for error with no rec lock
 CHAIN(EN) (char) filename1;
 IF %FOUND(filename1) and NOT %ERROR;  // If Condiditon
    CHAIN (num) filename2; // Chain to next file
    IF %FOUND(filename2); // If Condition
       filevar1 += 1;
       filevar2 = char;
       filevar3 = proc_call(filevar1:filevar2); // Call a Procedure
       // Update only field filevar3 in filename2
       UPDATE filerec2 %FIELDS(filevar3); 
    ENDIF;
 ELSEIF %ERROR;
    DSPLY 'There was an error';
    *INLR = %ERROR;
    RETURN;
 ENDIF;
 *INLR = *ON;
 RETURN;
/End-Free

Code and use Short Form expressions (e.g., + =)

i += 1;

Translate operation codes not supported in /Free form (e.g., MOVE, CALL, etc. into /Free form)

Use qualified Data Structures

The data structure contains the components of a phone number. The use of the QUALIFIED keyword on the DS line identifies the data structure as being qualified. This means that all references to the subfields in the data structure must be qualified with the data structure name using a dot notation (data structure name dot field name – DSNAME.SUBFIELD). The subfields in the qualified data structure may not be referenced by their field name alone.


 D Phone           DS                  Qualified
 D  CountryCode                   5    Varying
 D  NDDPrefix                     5    Varying
 D  AreaCode                      5    Varying
 D  Number                        9    Varying
 D  Extension                     4    Varying
 D  IDDPrefix                     5    Varying Dim(5)
 If Phone.CountryCode <> '353' ;
    DialNumber = Phone.IDDPrefix(1) + Phone.CountryCode;
 Else;
     DialNumber = Phone.NDDPrefix;
 EndIf;
 DialNumber = DialNumber + ' ' + Phone.AreaCode + Phone.Number;


Qualified data structures mean that you can now have the same field name in multiple data structures. It is even possible to have different definitions (data type, length) for a field in different data structures (possible but not desirable).

Use pointer data types

What Are Pointer Data Types?

A pointer data type is a reference to a variable, an array, or a data structure. It is a field that contains a memory address. Let's take a field defined in a D-spec:

 D MyName          s             30

Now we can assign a value to that variable, as follows:

 MyName = 'Kevin Vandever';

Or, for you traditionalists (or those not yet on V5R1):

 C                   Eval      MyName = 'Kevin Vandever'

In either case, what I've done is assigned a value to the space taken up by the variable, MyName. The memory for MyName is allocated when the program starts up, or loads into memory. The value of Kevin Vandever occupies the space of MyName. A pointer comes in when we control the actual address in memory, where MyName exists, and not the variable itself. Check out this code:

 D MyName          S             30A   Based(Pointer1)
 D Pointer1           S                  *    Inz

I have the same MyName variable, but now I've based it on a pointer by using the keyword Based. Then I defined my pointer, Pointer1. I can manipulate the memory as well as the variable. For example, I could set Pointer1 to a different memory address and thus change the contents of MyName. I could then assign a new value to MyName and thus change the contents that the address in Pointer1 points to. Get it? If not, don't worry; we'll take a closer look at basing pointer data types as well as another pointer data type.

Basing Pointers

The above example is an illustration of a basing pointer data type. We now have the ability to define data whose place in memory is designated by a pointer. This way, the variable does not have a fixed place in memory but is designated by the way that you set the pointer that the variable is based on.

A field, array, or data structure is based on a variable by using the Based keyword and assigning a pointer that will be used as the designator of memory. Then you define the pointer as a stand-alone field and place an asterisk (*) in the internal data type column of the D-spec. A pointer is 16 bytes long and may not have to be defined. If, in my example above, I hadn't coded the second D-spec defining Pointer1, the compiler would have done so for me using the name provided in the Based keyword. You must explicitly define a pointer if it is not used as a based pointer.

Now that you know how to define a basing pointer, it's time to manipulate that data a little bit. Here is a snippet of code that defines some basing pointers and messes around with them:

 D NewName         S             40A
 D MyName          S             30A   Inz('Kevin Vandever')
 D HerName         S             30A   Based(Pointer2)
 D Pointer1        S               *   Inz(%Addr(MyName))
 D Pointer3        S               *   Inz
* After the D specs, Pointer1 points to Kevin Vandever
* Now, assign Pointer2 a memory address and HerName a value
 C                   Eval      Pointer2  = %Addr(NewName)
 C                   Eval      HerName = 'Corina Vandever'
* Allocate storage for Pointer3
 C                   Eval      Pointer3 = %Alloc(40)
 C                   Eval      Pointer3 = %Addr(NewName)
* Another way to initialize storage
 C                   Eval      %Str(Pointer3 : 40) =
 C                             %Str(Pointer2 : 6)  +
 C                             ' And '  + %Str(Pointer1)
 C     NewName       Dsply
 C                   Eval      *InLr = *On

What did I do in that code? Not much, really. I just showed some of the different ways to manipulate storage. First, I defined three fields: NewName, MyName, and HerName. NewName and MyName are normal alpha fields, defined when the program loads. HerName is also an alpha field, but it is based on Pointer2. I then defined three pointers: Pointer1, Pointer2, and Pointer3. Pointer2 was implicitly defined because it was used in the Based keyword. I didn't have to define it in my D-specs. Pointer2 and Pointer3 have been initialized to nulls (*NULL), which is the default if nothing is entered in the INZ keyword. Pointer1, however, has been initialized a little differently. I used the address built-in function (%Addr) to assign the storage address of the field MyName to Pointer1.

In my C-specs, I first assign a memory address to Pointer2 because it is Null right now. I used the %Addr built-in function again and assigned Pointer2 the storage address of NewName, which was defined in the D-specs. I couldn't use HerName here, because that field has not been assigned storage yet. Remember, based variables are defined dynamically, not when the program loads. I then define a value to HerName, which I can now do because its based pointer, Pointer2, has been assigned a storage address. Because HerName is based on Pointer2, and Pointer2 contains the address of NewName, after I assign Corina Vandever to HerName, the value of NewName is also Corina Vandever, because it shares the same storage as HerName. Take a minute to let that soak in.

Next, I use the %Alloc built-in function to allocate storage for Pointer3. In this case, I allocate 40 bytes of storage. This just allocates storage; it does not base the pointer on any variable. If I wanted to do that, I could use the %Addr built-in function to not only allocate 40 bytes of storage but also assign it the storage occupied by a specific variable--in this case, the NewName variable. There is no need for both lines of code. I just wanted to show you the different ways to allocate storage. The reason why I would rather use the %Addr function in this case is that I want to manipulate Pointer3 and see that manipulation reflected in the NewName variable. Let's look at that code now.

I use the Get or Store Null Terminated String built-in function, %Str, to build the null terminated string Corina and Kevin Vandever. This built-in function only works with based pointers and allows you to build or extract null-terminated strings from storage. Finally, I display the NewName variable for all to see.

So why would you want to do any of this? First of all, dynamic storage allocation potentially allows you to manage your storage better and more efficiently. This might come in handy when working with large or inconsistently sized data structures that might otherwise allocate unnecessary storage. I have used them to dynamically allocate storage based on data received from a data queue. I receive anywhere from 50 to 64,000 bytes of data per request. I didn't want to statically define a 64K data structure to hold the data, especially since the majority of the entries were on the small side. So I used pointers to allocate storage as I needed it. That way, my structure was only as large as the number of byes received from the queue.

Another reason to learn about pointers is that many C-based APIs accept pointers as parameters and in order to work with those APIs you must be able to properly define and use pointers in your program.

Now let's take a look at another type of pointer data type.

Procedure Pointers

This type of pointer allows you to point to procedures or functions that are bound to your program. This makes it very easy to dynamically reference the procedure that you want to call. Procedure pointers are defined much the same way as the based pointers, except that when you define a procedure pointer, you do so by using the procedure pointer (ProcPtr) keyword to tell the compiler that this is a procedure pointer. Let's take a look at a code snippet:

 D Pointer1        S               *   ProcPtr 
 D MyProc          PR              4   ExtProc(Pointer1)
 D   Parm1                        20   Value
 C                   Eval      Pointer1 = %PAddr('SomeProc')
 C                   Eval      ReturnCode = MyProc(Parm1)

I first define the pointer, Pointer1, and make it a procedure pointer by using the keyword ProcPtr. Then I define the prototype for my procedure as I normally would, except that instead of hard-coded procedure or function as the parameter to the external procedure (ExtProc) keyword, I refer to the pointer that I just defined. I then use the pointer address built-in function (%PAddr) to assign the address of a procedure to my pointer. This %PAddr function works just like the %Addr function I used with regular pointers, except that it is used with procedure pointers. Next, I call my procedure like I would any other procedure. MyProc is really a call to SomeProc, because that is the procedure my pointer points to. If I wanted to call another external procedure, I could easily do so by assigning the address of another procedure to Pointer1, then calling MyProc again--assuming that the procedure shared the characteristics of the original prototype.

The procedure pointer data type allows you to define generic procedure prototypes and to dynamically plug in the external procedure you really want to call. This technique could add flexibility to your applications and cut down on development time.

Point Away

I have covered the very basics of pointer data types and provided you with a few reasons why you might want to use them. Give them a try and come up with some of your own ideas--not that you can't use mine, but I may have sold pointers short. If you do use them, you have to be very careful. With the power of being able to manipulate memory comes the potential for data corruption that just wouldn't happen if you didn't use pointers. But with that power also comes the potential for increased flexibility and efficiency, and you have to decide if the risk is worth the potential reward.

by Kevin Vandever

Code and use Named Constants

In the RPG IV language, constants are defined in definition specs. They are distinguished from other data definitions by the letter C in position 24 (definition type), and a constant value in positions 44 through 80 (keywords). In the keyword section, you may code the constant value by itself or as a parameter of the CONST keyword, as the following two definitions illustrate.

 D NbrOfRegions    c                   const(12)
 D NbrOfRegions    c                   12


Example..

 D WorkDaysPerWeek...
 D                 c                   const(5)
 eval QtyDue = TotQty / WorkDaysPerWeek

Prototype program Calls

Prototypes are defined on the D specifications. The format of a prototype is very similar to a data structure, except that the type is PR as opposed to DS. You can provide your own name for the CALLP (PromptProduct). The EXTPGM keyword indicates that this is the equivalent of a CALL operation, and it identifies the name of the called program (PRP01R). The names of the subfields in the prototype are irrelevant, what are important are the number of subfields (i.e. parameters) and the definition of each. In the example, the compiler will ensure that two parameters are passed, that Parm1 is a 30 character field and that Parm2 is a 1 character field.

 D PromptProduct   PR                  ExtPgm('PRP001R')
 D  FirstParm                    30
 D  SecondParm                    1
 C                   CallP     PromptProduct(Parm1:Parm2)


The Procedure Interface What about the called program? Just as the compiler will validate the parameters on the call, you also want it to validate the parameters in the called program. You achieve that by replacing the *ENTRY PLIST with a Procedure Interface. The compiler will, again, require a prototype to enable it to validate the parameters.

 D PromptProduct   PR                  ExtPgm('PRP001R')
 D  FirstParm                    30
 D  SecondParm                    1
 D PromptProduct   PI                  
 D  GetCode                      30
 D  Description                   1


Since the prototype is required in at least two programs, it makes sense to put it in a copy member and include it using the /COPY compiler directive.

And it makes even more sense to put all prototypes in a single copy member and include it in all programs using a /COPY compiler directive. Think of this as the source member containing the rules for all calls within your application.

Determine appropriate use of passing parameters by value versus by reference

Prototype System APIs and C functions

Understand the ability for RPG procedures to call and be called by Java Methods

Enumerate advantages of Prototypes compared with PARM and PLIST

The prototyped calls (CALLP or a function call) are just as efficient as CALL and CALLB and offer the advantages of prototyping and parameter passing by value. Neither CALL nor CALLB can accept a return value from a procedure.

Determine appropriate use for prototype keywords, such as CONST, VALUE, and OPTIONS (*NOPASS, *OMIT, *VARSIZE)

<< Previous Section | Home | Next Section >>