Customizing Your Development with Extensible RPG
by Joel Cochran
I introduced you to service programs and the binder source language in my last two articles. Now that you have bound your procedures into service programs, we need to refine our development method. These finishing touches revolve around reusability and make this approach truly complete and low-maintenance. The result is, effectively, your own "extended" version of RPG.
The Ties That Bind
When we combined all of our procedures into a service program, we ended up with a shorter version of the Create Program (CRTPGM) command, like so:
CRTPGM PGM(MYLIB/MYPGM) MODULE(MYLIB/ENTRYMOD) BNDSRVPGM(MYLIB/MYSRVPGM)
As I discussed in "Service Programs With a Smile," this is much cleaner than listing a bunch of modules on the CRTPGM command. If all of your procedures exist in a single *SRVPGM object, this method will suit you well. This is unlikely, however, and I would certainly not recommend that approach. One common method is to group procedures into service programs based on some commonality, such as function: all the string manipulation procedures go into one service program, all the higher-math operations go into another service program, and so on.
Assuming, then, that you will have multiple service programs, you need to list all the service programs that contain the referenced procedures in your program, on the BNDSRVPGM portion of the CRTPGM command:
CRTPGM PGM(MYLIB/MYPGM) MODULE(MYLIB/ENTRYMOD) BNDSRVPGM(MYLIB/MYSRVPGM1 MYLIB/MYSRVPGM2 MYLIB/MYSRVPGM3 MYLIB/MYSRVPGM4)
Now we've graduated to a longer command in the name of maintainability, which strikes me as counter-productive. Fortunately, once again, IBM has provided a better way. Enter the binding directory.
The concept of the binding directory is simple. It maintains a list of *SRVPGM and *MODULE objects, complete with the name and library of the listed objects. The compiler then references that list from top to bottom, in search of any referenced procedures in your program. If the procedure is found, the compiler then binds the appropriate object into your program automatically. The size and contents of a binding directory are irrelevant to the created program because only required objects are bound to your program, not every object in the binding directory. The binding directory is just a reference tool for the compiler: The actual bindings will be handled just as if you had typed them into the commands yourself.
In other words, if it's listed in the binding directory, you do not have to include it on the CRTPGM command. Instead, you need to reference the binding directory, like so:
CRTPGM PGM(MYLIB/MYPGM) MODULE(MYLIB/ENTRYMOD) BNDDIR(MYLIB/MYBNDDIR)
First, you need to create a binding directory. Binding directories have the object type *BNDDIR and can be created in any library. In fact, your programs can reference as many binding directories as you want, so feel free to organize them in the best way for your application. In my shop, we have one global binding directory and a series of application- or library-specific binding directories. However you decide to organize your binding directories, creating them couldn't be any easier; just issue the following command:
Now that you've created the directory, it's time to add some entries. There are several ways to accomplish this task. The most direct method is to use the Add Binding Directory Entry (ADDBNDDIRE) command. Prompting this command will show you the three attributes: BNDDIR, OBJ, and POSITION. These are pretty self-explanatory, so I'll only point out a couple of things: The default OBJ value includes a type of *SRVPGM, which you will need to change to *MODULE if you are adding a module. I don't recommend using modules this way outside of service programs, but this is a handy way to test your modules first.
Also, the POSITION attribute defaults to *LAST, which will only matter if you have multiple procedures with the same name in your directory. Like a library list, the compiler will start at the top and quit the first time it finds the procedure name. As such, it seems futile to have multiple procedures with the same name, not to mention confusing--a situation worth avoiding altogether. The command to remove an entry from a binding directory is RMVBNDDIRE. Prompting this command will reveal its simple and self-explanatory options.
Another useful command is Work with Binding Directory Entries (WRKBNDDIRE), which by habit is my preferred method. This command will bring up the entire list of objects within a binding directory and will allow you to add and remove entries from the directory via a simple interface. This is much simpler than the ADDBNDDIRE option and has the added benefit of displaying the creation date and time of the objects listed. Adding entries this way will use the ADDBNDDIRE defaults, but once you have entered 1 to create, and the object name, you can prompt the command with F4.
The last and most-functional option is Work with Binding Directories (WRKBNDDIR). You can specify a directory name of *ALL to view a list of all the BNDDIR objects in your library list. This is also a simple way to create a binding directory and provides additional options for displaying a binding directory's entries (DSPBNDDIRE), deleting a directory (DLTBNDDIR), and working with the entries for a directory (WRKBNDDIRE).
Embedding the Directory Reference in Your Source
From the first discussion of the CRTPGM command in "Service Programs With a Smile," we have worked hard to simplify the CRTPGM command, and one more thing can be done to make it even shorter. Now that you have your binding directory, the command looks like this:
CRTPGM PGM(MYLIB/MYPGM) MODULE(MYLIB/ENTRYMOD) BNDDIR(MYLIB/MYBNDDIR)
And as I mentioned, you can have as many binding directories as you want. So if you need several directories, the command starts to grow again:
CRTPGM PGM(MYLIB/MYPGM) MODULE(MYLIB/ENTRYMOD) BNDDIR(MYLIB/MYBNDDIR1 MYLIB/MYBNDDIR2 MYLIB/MYBNDDIR3)
Once again, you see that this could become a real maintenance headache. Fortunately you can specify binding directories on the header specifications, or "H-specs," of RPG IV source members. Multiple binding directories can be specified by listing them individually:
h bnddir('SERVICELIB/SERVICEDIR') h bnddir('CGILIB/CGIBNDDIR') h bnddir('CGILIB2/CGI2BNDDIR')
Multiple binding directories also can be specified by separating their entries with a colon (:).
Now the reference is handled by the individual modules, and once it's embedded in the source code you no longer have to reference the directories when you create the program. Now the command is finally whittled down to something like this:
CRTPGM PGM(MYLIB/MYPGM) MODULE(MYLIB/ENTRYMOD)
The Lowly /COPY Book
That is about as bare-bones as CRTPGM can get, so we have finally reached the end of our low-maintenance strategy, right? Well, not quite yet, but almost. There are still some things we can do to ease maintenance. The first is to embrace our past and take another look at /COPY. Regardless of its naysayers, good old /COPY cannot be ignored. In fact, if you've gotten this far you should already be using it to copy in procedure prototypes.
Working on that assumption, you can also put your H-specs in a /COPY. Why would you want to do this? I'll give you an example from my early days of RPG IV adoption. One of my first tasks was to convert an entire application from RPG III to RPG IV. After the initial conversion, I learned that Debug could be a real pain without using OPTION(*NODEBUGIO). I wanted to be sure that every program had this option, so I put one of my programmers to task inserting this H-spec into all of the application's source members. In an instance of unfortunate timing, I was busy learning the glories of binding directories, and so just as that person was finishing up I asked her do repeat the exercise and insert the BNDDIR statement for our new binding directory. The smoke that came from her ears prompted me to find another way.
I found that way in the lowly /COPY member. By using /COPY to set my H-specs, I now have a single point of entry and can guarantee some semblance of consistency and enforcement of compilation rules, as long as each member uses that /COPY. This method works great for any items that you want to include by default in your source.
I mentioned before that I have a global binding directory and then a binding directory for each application: I follow the same model for /COPY books; I have a set of global /COPY members that I reference in every RPGLE source member I create, and then another one for each application. The template I use to create new RPGLE source members starts off like this:
/copy rpgnextlib/qrpglesrc,cp_hspecs * Prototypes /copy rpgnextlib/qrpglesrc,cp_longprs * Constants /copy rpgnextlib/qrpglesrc,cp_const
By referencing all my procedures through a binding directory, found in the cp_hspecs /COPY, and including all the prototypes in the cp_longprs member, I always have access to all of my procedures. I've even gone a step further: I found myself repeatedly creating the same constant variables over and over from program to program, so I started putting them in a single /COPY as well, cp_const.
The result is that I have basically created my own personalized version of RPG, complete with my own built-in functions and constants. When used in this manner, RPG is essentially "extensible." And that's only for starters: I can continue extending the language for each application by adding application specific BIFs and constants, again pulled into all the application source members via /COPY.
A quick caveat: I have frequently simplified this even further by nesting /COPY statements in other /COPY members. However, when I started writing SQLRPGLE programs, I quickly learned that the SQL precompiler will not allow nested /COPYs. Also, I know many programmers express a certain level of angst over /COPY, and frequently with good reason based on their experience.
I would like to make it clear that I do not advocate using /COPY to clone program logic, subroutines, or subprocedures. Doing so defeats the purpose of modularized code because you still end up with multiple copies of the same source (granted in the program object), instead of one centralized object. Since our goal is to decrease maintenance work, that would be a hindrance, because now in the event of a source change every module using that /COPY must be found and recompiled. There are other problems as well, such as variable definitions, that make this an unsound approach.
Putting It All Together
The best part of this whole approach is that, once established, it is very low maintenance. Here are some key points to remember:
I am confident that adopting these techniques will make your maintenance simpler and your programming time more productive, not to mention that you, too, can have your own version of RPG.
Joel Cochran is the director of research and development for a small software firm in Staunton, Virginia, and is the author and publisher of www.RPGNext.com. E-mail: firstname.lastname@example.org
Editors: Shannon O'Donnell, Kevin Vandever
Managing Editor: Shannon Pastore
Contributing Editors: Howard Arner, Raymond Everhart,
G. Wayne Hawks, Joe Hertvik, Ted Holt, Marc Logemann, David Morris
Publisher and Advertising Director: Jenny Thomas
Advertising Sales Representative: Kim Reed
Contact the Editors: To contact anyone on the IT Jungle Team
Go to our contacts page and send us a message.
|Copyright © 1996-2008 Guild Companies, Inc. All Rights Reserved.|