Difference between revisions of "Test268 Section 4"
Kjburkhalter (talk | contribs) (→Use qualified Data Structures) |
m |
||
(14 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
− | |||
[[Category:Test268]] | [[Category:Test268]] | ||
− | |||
[[Test268 Section 3|<< Previous Section]] | [[:Category:Test268|Home]] | [[Test268 Section 5|Next Section >>]] | [[Test268 Section 3|<< Previous Section]] | [[:Category:Test268|Home]] | [[Test268 Section 5|Next Section >>]] | ||
Line 286: | Line 284: | ||
=== Translate operation codes not supported in /Free form (e.g., MOVE, CALL, etc. into /Free form) === | === Translate operation codes not supported in /Free form (e.g., MOVE, CALL, etc. into /Free form) === | ||
+ | CALL | ||
+ | d LoadFile PR ExtPgm('EXTPRGM') | ||
+ | d file 60 | ||
+ | LoadWaaDir(fileName); | ||
+ | |||
+ | |||
+ | MOVE | ||
+ | |||
+ | fileName = %trim(anotherField); | ||
+ | |||
+ | |||
+ | CharVar = %EDITC( NumVar : 'X' ); | ||
+ | |||
+ | |||
+ | %subst(currInv:1:6) = %char(invNo); | ||
=== Use qualified Data Structures === | === Use qualified Data Structures === | ||
Line 311: | Line 324: | ||
=== Use pointer data types === | === 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 === | === 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 === | === 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 === | === Determine appropriate use of passing parameters by value versus by reference === | ||
+ | In Passing Parameters by value the called program or procedure can change a value but it will not be reflected in the caller. | ||
+ | Any variable can be passed by value by specifying 'VALUE' in its definition. | ||
+ | |||
+ | In passing parameter by reference the address of the parameter itself is passed to the callee and thus it is done when callee is expected to modify the parameter value. | ||
+ | This type is the default passing type so no keyword is supposed to specify it. | ||
+ | This ways sometimes give performance benefits at run-time while passing large character fields. | ||
+ | |||
+ | There is a third type of passing parameter also and is termed as Call by '''Read only Reference''' | ||
+ | This is advisable to choose when the callee will not make changes in the parameter value; here the parameter is copied to a temporary location and this temporary address is send to the callee. | ||
+ | This can be done by specifying 'CONST' keyword in the definition. | ||
+ | |||
+ | Advantages of passing by value and read-only reference is that long expressions and literals can be passed along with the flexibility that data type and length of the parameter may not exactly match. | ||
=== Prototype System APIs and C functions === | === Prototype System APIs and C functions === | ||
Line 326: | Line 473: | ||
=== Understand the ability for RPG procedures to call and be called by Java Methods === | === Understand the ability for RPG procedures to call and be called by Java Methods === | ||
+ | |||
+ | RPG Code Example Calling BigDecimal Java Class | ||
+ | |||
+ | * Prototype the BigDecimal constructor that accepts a String | ||
+ | * parameter. It returns a new BigDecimal object. | ||
+ | * Since the string parameter is not changed by the constructor, we will | ||
+ | * code the CONST keyword. This will make it more convenient | ||
+ | * to call the constructor. | ||
+ | * | ||
+ | D bdcreate1 PR O EXTPROC(*JAVA: | ||
+ | D 'java.math.BigDecimal': | ||
+ | D *CONSTRUCTOR) | ||
+ | D str O CLASS(*JAVA:'java.lang.String') | ||
+ | D CONST | ||
+ | * | ||
+ | * Prototype the BigDecimal constructor that accepts a double | ||
+ | * parameter. 8F maps to the Java double data type and so must | ||
+ | * be passed by VALUE. It returns a BigDecimal object. | ||
+ | * | ||
+ | D bdcreate2 PR O EXTPROC(*JAVA: | ||
+ | D 'java.math.BigDecimal': | ||
+ | D *CONSTRUCTOR) | ||
+ | D double 8F VALUE | ||
+ | * Define fields to store the BigDecimal objects. | ||
+ | * | ||
+ | D bdnum1 S O CLASS(*JAVA:'java.math.BigDecimal') | ||
+ | D bdnum2 S O CLASS(*JAVA:'java.math.BigDecimal') | ||
+ | * | ||
+ | * Since one of the constructors we are using requires a String object, | ||
+ | * we will also need to construct one of those. Prototype the String | ||
+ | * constructor that accepts a byte array as a parameter. It returns | ||
+ | * a String object. | ||
+ | * | ||
+ | D makestring PR O EXTPROC(*JAVA: | ||
+ | D 'java.lang.String': | ||
+ | D *CONSTRUCTOR) | ||
+ | D bytes 30A CONST VARYING | ||
+ | * | ||
+ | * Define a field to store the String object. | ||
+ | * | ||
+ | D string S O CLASS(*JAVA:'java.lang.String') | ||
+ | * | ||
+ | * Prototype the BigDecimal add method. It accepts a BigDecimal object | ||
+ | * as a parameter, and returns a BigDecimal object (the sum of the parameter | ||
+ | * and of the BigDecimal object used to make the call). | ||
+ | * | ||
+ | D add PR O EXTPROC(*JAVA: | ||
+ | D 'java.math.BigDecimal': | ||
+ | D 'add') | ||
+ | D CLASS(*JAVA:'java.math.BigDecimal') | ||
+ | D bd1 O CLASS(*JAVA:'java.math.BigDecimal') | ||
+ | D CONST | ||
+ | * | ||
+ | * Define a field to store the sum. * | ||
+ | D sum S O CLASS(*JAVA:'java.math.BigDecimal') | ||
+ | D | ||
+ | D double S 8F INZ(1.1) | ||
+ | D fld1 S 10A | ||
+ | * Define a prototype to retrieve the String version of the BigDecimal | ||
+ | D getBdString PR O CLASS(*JAVA:'java.lang.String') | ||
+ | D EXTPROC(*JAVA: | ||
+ | D 'java.lang.BigDecimal': | ||
+ | D 'toString') | ||
+ | * Define a prototype to retrieve the value of a String | ||
+ | D getBytes PR 65535A VARYING | ||
+ | D EXTPROC(*JAVA: | ||
+ | D 'java.lang.String': | ||
+ | D 'getBytes') | ||
+ | * Define a variable to hold the value of a BigDecimal object | ||
+ | D bdVal S 63P 5 | ||
+ | * Call the constructor for the String class, to create a String | ||
+ | * object from fld1. Since we are calling the constructor, we | ||
+ | * do not need to pass a String object as the first parameter. | ||
+ | * | ||
+ | C EVAL string = makestring('123456789012345678901234567890') | ||
+ | * | ||
+ | * Call the BigDecimal constructor that accepts a String | ||
+ | * parameter, using the String object we just instantiated. | ||
+ | * | ||
+ | C EVAL bdnum1 = bdcreate1(string) | ||
+ | * | ||
+ | * Call the BigDecimal constructor that accepts a double | ||
+ | * as a parameter. | ||
+ | * | ||
+ | C EVAL bdnum2 = bdcreate2(double) | ||
+ | * | ||
+ | * Add the two BigDecimal objects together by calling the | ||
+ | * add method. The prototype indicates that add accepts | ||
+ | * one parameter, but since add is not a static method, we | ||
+ | * must also pass a BigDecimal object in order to make the | ||
+ | * call, and it must be passed as the first parameter. | ||
+ | * bdnum1 is the object we are using to make the | ||
+ | * call, and bdnum2 is the parameter. | ||
+ | * | ||
+ | C EVAL sum = add(bdnum1:bdnum2) | ||
+ | * sum now contains a BigDecimal object with the value | ||
+ | * bdnum1 + bdnum2. | ||
+ | C EVAL bdVal = %DECH(getBdString(sum) : 63 : 5) | ||
+ | * val now contains a value of the sum. | ||
+ | * If the value of the sum is larger than the variable "val" can | ||
+ | * hold, an overflow exception would occur. | ||
+ | |||
+ | |||
+ | Call an RPG program from a JAVA program | ||
+ | |||
+ | Create a PCML file | ||
+ | <pcml version="1.0"> | ||
+ | <program name="DateCalc" path="/QSYS.LIB/MYLIB.LIB/SY0062P.PGM"> | ||
+ | <data name="DateIn" type="packed" length="8" usage="inputoutput"/> | ||
+ | <data name="Type" type="char" length="1" usage="inputoutput"/> | ||
+ | <data name="Number" type="packed" length="5" usage="inputoutput"/> | ||
+ | <data name="DateOut" type="packed" length="8" usage="output"/> | ||
+ | <data name="EOP" type="char" length="1" usage="inputoutput"/> | ||
+ | </program> | ||
+ | </pcml> | ||
+ | Figure 1: PCML file | ||
+ | |||
+ | Figure 1 illustrates a PCML file used to support the calling of a simple RPG program. As you can see this file accomplishes two functions: | ||
+ | |||
+ | 1. The <program name=… tag assigns an alias of your choice to the fully qualified relative path to the program on an iSeries machine. The term “relative path” here means that the program SY0062P.PGM is located in “MYLIB.LIB” within the QSYS file system on an iSeries machine somewhere. The path is relative because it does not provide a path to the machine where the program is located. | ||
+ | 2. The <data name=… tags define the input, output, and inputoutput parameters to be exchanged with the program. The data tags include the following parameters: name= you may assign any name you wish to the parameter. type= this is the OS/400 data type for the parameter length= length of the field usage= is input, output, or inputoutput | ||
+ | This file is created in your favorite text editor and stored in the classpath accessible to your Java program. For simplicity, we named the PCML file SY0062P.PCML where SY0062P is the name of the RPG Program that we will be calling. | ||
+ | |||
+ | Write your Java Program | ||
+ | /** | ||
+ | * Testing Java call RPG program. | ||
+ | * Creation date: (11/13/2001 11:35:27 AM) | ||
+ | * @author: James Zhang | ||
+ | */ | ||
+ | import com.ibm.as400.data.*; | ||
+ | import com.ibm.as400.access.*; | ||
+ | class TestCallRpg { | ||
+ | /** | ||
+ | * TestCallRpg constructor. | ||
+ | */ | ||
+ | public TestCallRpg() { | ||
+ | super(); | ||
+ | } | ||
+ | /** | ||
+ | * This program is written as an application | ||
+ | * to easily demonstrate calling an RPG program | ||
+ | * and run the program from your development environment. | ||
+ | */ | ||
+ | public static void main(String[] args) { | ||
+ | /* Create a connection the iSeries where the program you wish to call resides */ | ||
+ | AS400 sys; | ||
+ | sys = new AS400("yourMachineName","USERID","PASSWORD"); | ||
+ | if(sys == null){ | ||
+ | System.out.println("Connection failed"); | ||
+ | System.exit(1); | ||
+ | } | ||
+ | ProgramCallDocument pcml; | ||
+ | try{ | ||
+ | pcml = new ProgramCallDocument(sys, "SY0062P"); // open the PCML File | ||
+ | // set all the input parameters for program alias DateCalc | ||
+ | pcml.setValue("DateCalc.DateIn", new Integer(20011112)); | ||
+ | pcml.setValue("DateCalc.Type","M"); | ||
+ | pcml.setValue("DateCalc.Number",new Integer(5)); | ||
+ | pcml.setValue("DateCalc.EOP","0"); | ||
+ | // call the program | ||
+ | boolean rc = pcml.callProgram("DateCalc"); | ||
+ | if(!rc){ | ||
+ | System.out.println("Program failed"); | ||
+ | }else{ | ||
+ | System.out.println("Date Out = " + | ||
+ | pcml.getValue("DateCalc.DateOut")); | ||
+ | } | ||
+ | }catch(PcmlException pe){ | ||
+ | System.out.println(" Caught Exception "); | ||
+ | pe.printStackTrace(); | ||
+ | }finally{ | ||
+ | System.exit(0); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | Figure 2: Java program that calls RPG program | ||
+ | |||
+ | Figure 2 illustrates a very simple Java application that calls an RPG program on the same or remote AS/400 (iSeries) machine. Please note that this example assume you have installed the Java Toolbox in your development environment and the Toolbox packages are available to your development tool. | ||
+ | |||
+ | 1. You must import IBM’s iSeries Java Toolbox packages: com.ibm.as400.data.* and com.ibm.as400.access.* | ||
+ | 2. This is the code to connect to an AS/400 (iSeries) machine. You must specify the name of the machine, a valid userid and password. The user profile must have sufficient authority to call the program you wish to call and access any resources required by the program. The user profile must also have a valid library list that provides access to any files or resources used by your program. If the call is made to a remote machine (a machine other than one where your Java program is running) a server job is started. Your program will run within this server job. | ||
+ | 3. In the pcml = new… you specify the location of the PCML file created above. | ||
+ | 4. You then supply values for each parameter required by your RPG program. In this simplistic example, we are supplying literals, in a real world scenario, these might be variables obtained from an HTML form, XML file, or otherwise developed by your Java application. | ||
+ | 5. Call the program. | ||
=== Enumerate advantages of Prototypes compared with PARM and PLIST === | === 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) === | === Determine appropriate use for prototype keywords, such as CONST, VALUE, and OPTIONS (*NOPASS, *OMIT, *VARSIZE) === |
Latest revision as of 03:43, 5 February 2010
<< Previous Section | Home | Next Section >>
Contents
- 1 Section 4 - Advanced RPG techniques (25%)
- 1.1 Given an example of a complex logical expression, determine its results
- 1.2 Given an example of deeply-nested logic within a sample of RPG code, determine the results of running the code
- 1.3 Use Data Structure arrays
- 1.4 Code complex D-specs (e.g., OVERLAY, coding fields without attributes, etc.)
- 1.5 Use modern techniques to handle numbered indicators
- 1.6 Determine appropriate use of system APIs
- 1.7 Code subprocedures
- 1.8 Declare and use subprocedures
- 1.9 Create and use multiple occurrence data structures
- 1.10 Use externally-described data structures
- 1.11 Write logic (including I/O operations) without numbered indicators
- 1.12 Code and use /Free format Calc specifications
- 1.13 Code and use Short Form expressions (e.g., + =)
- 1.14 Translate operation codes not supported in /Free form (e.g., MOVE, CALL, etc. into /Free form)
- 1.15 Use qualified Data Structures
- 1.16 Use pointer data types
- 1.17 Code and use Named Constants
- 1.18 Prototype program Calls
- 1.19 Determine appropriate use of passing parameters by value versus by reference
- 1.20 Prototype System APIs and C functions
- 1.21 Understand the ability for RPG procedures to call and be called by Java Methods
- 1.22 Enumerate advantages of Prototypes compared with PARM and PLIST
- 1.23 Determine appropriate use for prototype keywords, such as CONST, VALUE, and OPTIONS (*NOPASS, *OMIT, *VARSIZE)
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)
CALL
d LoadFile PR ExtPgm('EXTPRGM') d file 60
LoadWaaDir(fileName);
MOVE
fileName = %trim(anotherField);
CharVar = %EDITC( NumVar : 'X' );
%subst(currInv:1:6) = %char(invNo);
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
In Passing Parameters by value the called program or procedure can change a value but it will not be reflected in the caller. Any variable can be passed by value by specifying 'VALUE' in its definition.
In passing parameter by reference the address of the parameter itself is passed to the callee and thus it is done when callee is expected to modify the parameter value. This type is the default passing type so no keyword is supposed to specify it. This ways sometimes give performance benefits at run-time while passing large character fields.
There is a third type of passing parameter also and is termed as Call by Read only Reference This is advisable to choose when the callee will not make changes in the parameter value; here the parameter is copied to a temporary location and this temporary address is send to the callee. This can be done by specifying 'CONST' keyword in the definition.
Advantages of passing by value and read-only reference is that long expressions and literals can be passed along with the flexibility that data type and length of the parameter may not exactly match.
Prototype System APIs and C functions
Understand the ability for RPG procedures to call and be called by Java Methods
RPG Code Example Calling BigDecimal Java Class
* Prototype the BigDecimal constructor that accepts a String * parameter. It returns a new BigDecimal object. * Since the string parameter is not changed by the constructor, we will * code the CONST keyword. This will make it more convenient * to call the constructor. * D bdcreate1 PR O EXTPROC(*JAVA: D 'java.math.BigDecimal': D *CONSTRUCTOR) D str O CLASS(*JAVA:'java.lang.String') D CONST * * Prototype the BigDecimal constructor that accepts a double * parameter. 8F maps to the Java double data type and so must * be passed by VALUE. It returns a BigDecimal object. * D bdcreate2 PR O EXTPROC(*JAVA: D 'java.math.BigDecimal': D *CONSTRUCTOR) D double 8F VALUE * Define fields to store the BigDecimal objects. * D bdnum1 S O CLASS(*JAVA:'java.math.BigDecimal') D bdnum2 S O CLASS(*JAVA:'java.math.BigDecimal') * * Since one of the constructors we are using requires a String object, * we will also need to construct one of those. Prototype the String * constructor that accepts a byte array as a parameter. It returns * a String object. * D makestring PR O EXTPROC(*JAVA: D 'java.lang.String': D *CONSTRUCTOR) D bytes 30A CONST VARYING * * Define a field to store the String object. * D string S O CLASS(*JAVA:'java.lang.String') * * Prototype the BigDecimal add method. It accepts a BigDecimal object * as a parameter, and returns a BigDecimal object (the sum of the parameter * and of the BigDecimal object used to make the call). * D add PR O EXTPROC(*JAVA: D 'java.math.BigDecimal': D 'add') D CLASS(*JAVA:'java.math.BigDecimal') D bd1 O CLASS(*JAVA:'java.math.BigDecimal') D CONST * * Define a field to store the sum. * D sum S O CLASS(*JAVA:'java.math.BigDecimal') D D double S 8F INZ(1.1) D fld1 S 10A * Define a prototype to retrieve the String version of the BigDecimal D getBdString PR O CLASS(*JAVA:'java.lang.String') D EXTPROC(*JAVA: D 'java.lang.BigDecimal': D 'toString') * Define a prototype to retrieve the value of a String D getBytes PR 65535A VARYING D EXTPROC(*JAVA: D 'java.lang.String': D 'getBytes') * Define a variable to hold the value of a BigDecimal object D bdVal S 63P 5 * Call the constructor for the String class, to create a String * object from fld1. Since we are calling the constructor, we * do not need to pass a String object as the first parameter. * C EVAL string = makestring('123456789012345678901234567890') * * Call the BigDecimal constructor that accepts a String * parameter, using the String object we just instantiated. * C EVAL bdnum1 = bdcreate1(string) * * Call the BigDecimal constructor that accepts a double * as a parameter. * C EVAL bdnum2 = bdcreate2(double) * * Add the two BigDecimal objects together by calling the * add method. The prototype indicates that add accepts * one parameter, but since add is not a static method, we * must also pass a BigDecimal object in order to make the * call, and it must be passed as the first parameter. * bdnum1 is the object we are using to make the * call, and bdnum2 is the parameter. * C EVAL sum = add(bdnum1:bdnum2) * sum now contains a BigDecimal object with the value * bdnum1 + bdnum2. C EVAL bdVal = %DECH(getBdString(sum) : 63 : 5) * val now contains a value of the sum. * If the value of the sum is larger than the variable "val" can * hold, an overflow exception would occur.
Call an RPG program from a JAVA program
Create a PCML file <pcml version="1.0"> <program name="DateCalc" path="/QSYS.LIB/MYLIB.LIB/SY0062P.PGM"> <data name="DateIn" type="packed" length="8" usage="inputoutput"/> <data name="Type" type="char" length="1" usage="inputoutput"/> <data name="Number" type="packed" length="5" usage="inputoutput"/> <data name="DateOut" type="packed" length="8" usage="output"/> <data name="EOP" type="char" length="1" usage="inputoutput"/> </program> </pcml> Figure 1: PCML file
Figure 1 illustrates a PCML file used to support the calling of a simple RPG program. As you can see this file accomplishes two functions:
1. The <program name=… tag assigns an alias of your choice to the fully qualified relative path to the program on an iSeries machine. The term “relative path” here means that the program SY0062P.PGM is located in “MYLIB.LIB” within the QSYS file system on an iSeries machine somewhere. The path is relative because it does not provide a path to the machine where the program is located. 2. The <data name=… tags define the input, output, and inputoutput parameters to be exchanged with the program. The data tags include the following parameters: name= you may assign any name you wish to the parameter. type= this is the OS/400 data type for the parameter length= length of the field usage= is input, output, or inputoutput This file is created in your favorite text editor and stored in the classpath accessible to your Java program. For simplicity, we named the PCML file SY0062P.PCML where SY0062P is the name of the RPG Program that we will be calling.
Write your Java Program
/** * Testing Java call RPG program. * Creation date: (11/13/2001 11:35:27 AM) * @author: James Zhang */ import com.ibm.as400.data.*; import com.ibm.as400.access.*; class TestCallRpg { /** * TestCallRpg constructor. */ public TestCallRpg() { super(); } /** * This program is written as an application * to easily demonstrate calling an RPG program * and run the program from your development environment. */ public static void main(String[] args) { /* Create a connection the iSeries where the program you wish to call resides */ AS400 sys; sys = new AS400("yourMachineName","USERID","PASSWORD"); if(sys == null){ System.out.println("Connection failed"); System.exit(1); } ProgramCallDocument pcml; try{ pcml = new ProgramCallDocument(sys, "SY0062P"); // open the PCML File // set all the input parameters for program alias DateCalc pcml.setValue("DateCalc.DateIn", new Integer(20011112)); pcml.setValue("DateCalc.Type","M"); pcml.setValue("DateCalc.Number",new Integer(5)); pcml.setValue("DateCalc.EOP","0"); // call the program boolean rc = pcml.callProgram("DateCalc"); if(!rc){ System.out.println("Program failed"); }else{ System.out.println("Date Out = " + pcml.getValue("DateCalc.DateOut")); } }catch(PcmlException pe){ System.out.println(" Caught Exception "); pe.printStackTrace(); }finally{ System.exit(0); } } }
Figure 2: Java program that calls RPG program
Figure 2 illustrates a very simple Java application that calls an RPG program on the same or remote AS/400 (iSeries) machine. Please note that this example assume you have installed the Java Toolbox in your development environment and the Toolbox packages are available to your development tool.
1. You must import IBM’s iSeries Java Toolbox packages: com.ibm.as400.data.* and com.ibm.as400.access.* 2. This is the code to connect to an AS/400 (iSeries) machine. You must specify the name of the machine, a valid userid and password. The user profile must have sufficient authority to call the program you wish to call and access any resources required by the program. The user profile must also have a valid library list that provides access to any files or resources used by your program. If the call is made to a remote machine (a machine other than one where your Java program is running) a server job is started. Your program will run within this server job. 3. In the pcml = new… you specify the location of the PCML file created above. 4. You then supply values for each parameter required by your RPG program. In this simplistic example, we are supplying literals, in a real world scenario, these might be variables obtained from an HTML form, XML file, or otherwise developed by your Java application. 5. Call the program.
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.