Files in Subprocedures
April 28, 2010 Jon Paris
Subprocedures have been with us since V3R2/V3R6 and have had a major impact on the way that we build applications. Or at least they should have done. Sadly, many RPGers remain firmly rooted in the past.
Those of you who do make regular use of subprocedures have undoubtedly found yourself wishing from time to time that you could define files within them rather than having to resort to accessing globally defined files. With the advent of the 6.1 release of the IBM i operating system, your wishes were granted. Files can indeed now be defined within subprocedures.
The sample subprocedure presented here accesses two files to build a composite product description. Since there are a number of considerations when using files within subprocedures, I’ll work my way through the code to point out the areas in which it differs from conventional file handling.
The two files are defined at (A) and (B). Note that with (A), in keeping with regular RPG specification sequence, these F-specs must come before the subprocedure’s Procedure Interface (PI) definition (at B), since in the world of RPG, F comes before D! The result is that the P-spec that marks the start of the subprocedure is now separated from the PI, something that I found hard to remember to begin with.
In this example there is nothing unusual about the F-specs themselves, but we will discuss one new F-spec feature later in this tip.
Although not new for this release, the data structure definitions at (C) may not be familiar to you, since they use the LikeRec keyword. The reason that these definitions are required is quite simple. In subprocedures there is no support for I or O specs, and therefore none are generated for any files defined in the subprocedure. As a result, all I/O must be done using the result-field data structure option, and we must therefore define data structures to match the record layouts we require.
// GetDescr subprocedure - uses Product and Category files P GetDescription B (A) FXProducts IF E K Disk FXCategors IF E K Disk (B) D GetDescription PI 50a Varying D categoryCode 2a D productCode 5a (C) D categoryData DS LikeRec(CATEGORREC) D productData DS LikeRec(PRODUCTREC) D fullDesc S 50a Varying /Free (D) Chain (categoryCode: productCode) XProducts productData; If %Found(XProducts); fullDesc = %Trim(productData.shortDesc); (E) Chain productData.catCode XCategors categoryData; If %Found(XCategors); fullDesc += ' (' + %Trim(categoryData.catName) + ')'; Else; fullDesc += ' (Category ' + productData.catCode + ' Not found)'; EndIf; Else; fullDesc = 'Product ' + productCode + ' Not found'; EndIf; Return fullDesc; /End-Free P GetDescription E
You can see the first of the file operations at (D) where we use the Category and Product codes to CHAIN to the XProducts file. The ability to simply reference the two key fields within parentheses was added back in V5R2 in case you are unfamiliar with it. No more KLISTS! The productData data structure is specified as the result field to receive the record. If the record is found, we proceed to the second CHAIN, at (E), to retrieve the category description.
Although I could have used the category code that was passed in as part of the product code, I chose to use the category code from the product file as the key. I did so for one reason: to highlight that the productData data structure is implicitly qualified, since it was defined with the LikeRec keyword. So the full name of the category code in the data structure is actually productData.catCode, and this is the name used as the key field.
The rest of the logic is just standard RPG and builds the complete description for return to the calling program. Similarly, the balance of the program, whose only role in life is to allow me to test this subprocedure, is pretty simple so I won’t waste time describing it here.
So, is that all there is to it?
Not really. There are a few other things to consider.
First, all subprocedures allocate the storage for their variables when they are called, and release it when they return. Guess what? The same thing happens with files opened within subprocedures. They are opened when the subprocedure is called, and closed when it exits, much the same as it would be in a program that always sets on LR before it returns to its caller. This could result in significant performance problems for the unwary. Constantly opening and closing files is an expensive business. So how can we avoid this overhead?
One answer is to specify the keyword STATIC on the file’s F-spec. When this is done, the file will be opened the first time the subprocedure is called and will remain open until explicitly closed, or when the Activation Group ends, whichever comes first. Using STATIC in conjunction with USROPN would allow you to have full control of the file. Remember, you will also need to add the STATIC keyword to the data structure if you want to retain record data between subprocedure invocations.
The STATIC approach is not going to work if you intend to make use of the subprocedure’s ability to call itself recursively–for example when building a manufacturing parts list. If the file were defined as static, then the same open, and therefore the same file cursor, will be “seen” by all invocation levels just as a static variable is visible at all recursion levels. The other issue to consider when using the STATIC keyword is that STATIC storage is not released by setting on LR in the main body of the program. As a result any files declared as STATIC will remain open–even if the program ends. It will only be closed when explicitly closed, or when the Activation Group ends.
The ability to define files within subprocedures certainly increases their utility and our ability to build reusable components, but because of the performance implications you need to think about how you are going to use them.
One byproduct of this 6.1 support was that IBM also added the ability to pass files as parameters. In the long run, this capability may be far more interesting as it allows you to write reusable subprocedures that can manipulate files, but allows the “ownership” of the file to remain with the program or procedure that originally opened it. But that’s a topic for a future tip.
Jon Paris is one of the world’s most knowledgeable experts on programming on the System i platform. Paris cut his teeth on the System/38 way back when, and in 1987 he joined IBM’s Toronto software lab to work on the COBOL compilers for the System/38 and System/36. He also worked on the creation of the COBOL/400 compilers for the original AS/400s back in 1988, and was one of the key developers behind RPG IV and the CODE/400 development tool. In 1998, he left IBM to start his own education and training firm, a job he does to this day with his wife, Susan Gantner–also an expert in System i programming. Paris and Gantner, along with Paul Tuohy and Skip Marchesani, are co-founders of System i Developer, which hosts the new RPG & DB2 Summit conference. Send your questions or comments for Jon to Ted Holt via the IT Jungle Contact page.