Changes to Varying Dimension Arrays for RPG
Jon Paris and Susan Gantner explain more about this new feature of IBM i 7.4
By Jon Paris and Susan Gantner08/12/2019
In our first article on the new 7.4 features we covered the long-awaited varying-dimension array support. That original article introduced what you need to know for most uses of this powerful new feature. As we mentioned then, due to some limitations of the current support, safely passing this type of array as a parameter requires the use of some additional options which we'll be covering here.
As we mentioned in the first article, %ELEM has been enhanced with some additional features for the new varying length arrays. The biggest change is that it can now be used to set the size of the array as opposed to simply retrieving it. In the way of a quick review, we can define an array like this:
Dcl-S myVarArray Char(10) Dim ( *Var : 1000 );
This defines an array which can have a maximum of 1000 elements but begins with zero elements. %ELEM must be used to set (or change) the number of active elements before attempting to populate elements in the array, like this:
%Elem( myVarArray ) = 900 myAutoArray(900) = 'Highest;
%ELEM can also be used to find the current number of active elements in the array:
If index <= %Elem( myVarArray );
Or to find the maximum number of elements the array could potentially hold using the *Max keyword with %ELEM:
If index <= %Elem( myVarArray : *Max );
Additional Keywords for %ELEM
Let's now look at the additional keywords that are available for %ELEM. After that, we'll also describe when and why you may need to use these, especially when passing such arrays to another program or procedure.
The basic syntax is:
%ELEM( arrayName : keyword ).
The keywords and their usage are:
As shown earlier, this returns the maximum number of elements that the array can possibly hold I.e. the maximum specified on the DIM.
This returns or sets the current array capacity and the storage associated with it. It does not affect the number of active elements and will never reduce the allocated size to less than the number of active elements. We’ll take a look at this feature in action later in this article.
This prevents initialization of any new elements that are added to the array via *ALLOC. At first glance this seems a like rather strange option, but all will be made clear in a moment when we look at a practical usage of the feature.
First, let's look at a simple example to get an idea of how the basic mechanics work.
We start by defining the array (A) as being variable in size and with a maximum of 100 elements. We then use %ELEM to set the number of elements to 10 (B). The 10 new elements will be initialized to zero (the default value for integers).
Next (C) we loop through the array storing the appropriate index value in each entry. Note that %Elem( varyArray ) will return a value of 10 since that is the current array allocation.
(A) Dcl-S varyArray Int(5) Dim(*Var: 100 ); Dcl-S i Int(5); (B) %Elem( varyArray ) = 10; // Set maximum capacity to 10 (C) For i = 1 to %Elem( varyArray ); varyArray(i) = i; EndFor; // The following display shows the value 5 Dsply ('Element 5 has the value ' + %Char( varyArray(5)) ); (D) %Elem(varyArray) = 1; // Set maximum to 1 element (E) %Elem(varyArray : *KEEP ) = 5; // And now to 5 (F) Dsply ('Element 5 should still be 5 and is ' + %Char(varyArray(5)) );
The next task (D) is to set the active element count to 1. This only affects element count; the storage associated with the array is not changed as we will demonstrate in a moment.
We then immediately (E) change the count to 5 but with the *KEEP option. This causes the array to be enlarged but the existing content of the additional 4 elements (indexes 2 through 5) is retained. Since earlier we had contrived those entries to contain the values 2, 3, 4 and 5 those values should still be there which the display at (F) demonstrates. Had the *KEEP option been omitted, then elements 2 through 5 would have been initialized to zero.
At this point, you're probably asking yourself why on earth you would want or need to do this. Good question! Let's look at an example where this comes in handy.
Suppose we have a situation where program A is going to call program (or subprocedure) B to get a list of items for processing. Since the size of the potential list is not known in advance, using the new dynamic array support would appear to be a good choice.
Our first challenge is how to describe the array on the prototype for the call. As noted in the earlier article, these new-style array definitions cannot yet be used on prototypes, so how do we handle this? The answer is that the prototype needs to define a regular array with the maximum size it will ever be, and add Options(*Varsize) to the definition so that RPG will permit the variable length array to be passed, no matter how many elements it may currently have.
The second challenge relates to the way in which parameters are normally passed on the system. The norm is to pass them by reference—and for programs it’s the only option. When we say "by reference" we mean that what is passed is a pointer containing the address of the beginning of the parameter data. The critical thing to note is that it is just a pointer. No information about the size, type, or length of the data is passed.
Why is this a concern? Because with varying size arrays, storage is only allocated based on the amount requested via %ELEM. So, if program A currently has set the array to a capacity of (say) 10 elements, then if program B were subsequently to place a value in element 20 it would potentially overwrite storage belonging to another variable. Yikes!
For this reason, it’s vital that we set the storage allocation for the array to a value high enough to accommodate the maximum number of elements that program B may wish to add. For simplicity in our example we will simply set the size to its maximum value.
However, once program B completes processing, we might wish to free up the extra memory and set the current allocation to the actual number of active elements. This will also simplify any sorting/searching etc. of the array. There’s a potential problem though.
Suppose that at the time we passed the array to program B that there were five active elements and B increases this number to 10. But program A still thinks that there are only five active elements. Simple—we just need to use %ELEM to increase the number to 10 when B returns right? Almost…
Remember that when the active number of elements is increased the additional elements are initialized to their defaults. In this case that would cause the values that program B just carefully added to the array to be overwritten—not a good idea.
This is where *KEEP comes into play. By using it when setting the new active element count RPG will preserve those values.
To make all this possible, our example code will pass a parameter to program B that it can use to notify program A of the updated number of active elements. In fact, we can also use this parameter to inform B of the number of entries currently in the array so that it knows where to start adding new entries.
So given this situation, let's look at how this would be coded.
(G) Dcl-Pr ProgramB ExtPgm; itemCount Int(5); items Char(10) Dim ( 1000 ) Options(*VarSize); End-Pr; Dcl-S activeElements Int(5); (H) Dcl-S itemList Char(10) Dim( *Var : 1000 ); // Various operations to add elements to array etc. (I) activeElements = %Elem(itemList); // Capture active count // Increase storage allocation to maximum prior to calling B (J) %Elem( itemList : *ALLOC) = %Elem( itemList : *MAX ); (K) ProgramB( activeElements : itemList ); // Call program B // Resize array's storage retaining existing values (L) %Elem( itemList : *Keep) = activeElements;
At (G) you can see the prototype for program B - note the use of Options(*VarSize). Remember that we can't (yet) define the parameter array to be variable on the prototype.
The actual array that we are using is defined at (H). The critical thing to remember is that the compiler will only have allocated enough memory to the array to accommodate its current size. So before we pass the array to program B we need to capture the current size of the array (I) before increasing the memory reservation (J) to the maximum size it can have.
Once this has been done we can safely call B and pass it the array and the current active element count (K). When B has finished its work it will have set the new active element count and program A can use that (L) to set the active elements number for the array.
As you have seen, there's a lot to this new array support. Some aspects, like the *KEEP option, you may never need to use. But understanding the basics of how they work is vital to using them safely.