- The developer lets the system generate multiple signatures based on multiple export lists in the binder language.
- The developer supplies multiple export lists and also supplies multiple hard-coded values for the signatures rather than letting the system generate the signature values.
- The developer provides one hard-coded signature value, which stays the same over time, and one export list, which changes with each new version of the service program.
Binder Language Syntax
First of all, let's look at binder language syntax. The source is typically stored in a source member of type BND. The default source file name is QSRVSRC, and the default is to give the member the same name as the service program. We'll see a little later where these defaults come into play in case you want to change them.
Here is an example of what the binder language might look like for the CUSTPROCS service program used in the original example.
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL(GetCustInfo)
EXPORT SYMBOL(SearchByPhone)
EXPORT SYMBOL(ValidCust)
ENDPGMEXP
Binder language is free-format, so the indentation of the export symbol statements is only to improve readability. There are only three commands in binder language, and this example uses all of them: Start Program Export (STRPGMEXP), Export (EXPORT), and End Program Export (ENDPGMEXP). You may recall from the previous article that the list of procedure addresses attached to the service program is called an export list. What this code example does is force that list to be created in the sequence that the names are listed in the export statements. The first procedure address in the export list is for a procedure named GETCUSTINFO, and the third procedure address is for VALIDCUST.
The term "SYMBOL" in the export statements simply refers to the name of the exported item, which may be either a procedure or (far less frequently) an exported data item. You may want to note that even though I keyed the symbol names in mixed case, the actual procedure names are treated as all uppercase. Since these are RPG subprocedures, the names would normally be all uppercase unless the ExtProc keyword were specified, which is rare. If the procedure names were mixed case, they would need to be enclosed in either single or double quotation marks in the symbol parameter.
Once we have the binder language we want to use in a source member, it is compiled at the time we create the service program. On the Create Service Program (CRTSRVPGM) command, the Export parameter determines whether or not binder language is to be used. If you are creating a service program without binder language, you must specify Export(*All), which means there is no binder language. Export(*SrcFile), which is the shipped default value, means the binder language can be found in the source file and member name in the next two parameters. The shipped values for those parameters are QSRVSRC and *SRVPGM, meaning the same name as the service program.
Multiple System-Generated Signatures
In this example, the default behavior of letting the system generate a signature value is used. We'll see a little later how to specify your own signature value if you want. In this case, the system will generate a signature using an algorithm based on the names and their sequence in the export list. In other words, as long as the binder language stays the same, I can re-create the CUSTPROCS service program many times and the signature will remain the same.
However, if I were to change this list of exports—for example, to add a new callable procedure to the service program and therefore to the export list—then the generated signature value would change to reflect the modified list of exports. That would cause any programs that were using CUSTPROCS to fail with a signature violation, just as it would have if we had not been using binder language. But by using binder language, I have the option of creating multiple valid signatures for the service program by supplying multiple export lists. One signature value gets generated for each export list. The following example illustrates this:
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL(GetCustInfo)
EXPORT SYMBOL(SearchByPhone)
EXPORT SYMBOL(ValidCust)
EXPORT SYMBOL(SearchByCustNo)
ENDPGMEXP
STRPGMEXP PGMLVL(*PRV)
EXPORT SYMBOL(GetCustInfo)
EXPORT SYMBOL(SearchByPhone)
EXPORT SYMBOL(ValidCust)
ENDPGMEXP
Note the use of the PGMLVL parameter to specify that the current signature to be generated for the service program is based on all four procedure names while a previous (*PRV) signature value is to be generated for the list of procedure names as specified in the original export list. Therefore, the previous signature value will be the same as the one the service program had originally, and therefore, all the programs will continue to work.
You may be wondering what will happen when I need to add yet another procedure to CUSTPROCS. The answer is that I will create another *PRV signature. I can have as many previous signatures as I want, but only one current signature can be specified.
You need to understand three details about the process:
- Despite the fact that the binder language specifies two (or more) export lists, there is only one real list of procedure addresses that is used at run time, and it is always the one marked as current in the binder language. The sole purpose of any/all lists of exports in the binder language marked as *PRV is to generate a signature value. Old programs expecting *PRV signature values will find them and continue to work. Any new or updated programs created will pick up the current signature value.
- The fact that the previous signatures are the same as the earlier values is simply because the list of exports matches the earlier export list—not because the service program is actually remembering what previous signatures it had. In other words, if your binder language contained a *PRV export list that was different from what the earlier current export list had specified (e.g., a different number of or sequence of symbols in the list), then a "previous signature" will be generated, but it will not match the earlier signature. Therefore, the programs previously bound to the service program would fail because they are still looking for the original signature.
- Because of point 1 above, the most important thing to remember about binder language is that the sequence of exports (typically procedure names) must remain consistent throughout all export lists, both current and previous. All new exports must always be added at the end of the list. Also Exports must not be removed from the list if you want to keep the previous signatures working. While one could technically create a scenario where this could work, it is cumbersome and error-prone.
Remember that when a program is setting up for a procedure to call, it is locating the procedure by using the relative position within current export list at the time the program was bound to the service program. Figure 1 below is the same one used in the last article to describe how OEPGM01 interacts with CUSTPROCS. When I recreated the service program to add the new SearchByCustNo procedure, I made sure that the other three procedures remained in the same positions in the current export list by adding my new export to the end of the current list in my binder language. OEPGM01 will still find the signature value it is expecting (now a previous signature), and it will still find ValidCust in the export #3 and GetCustInto in export #1, even though the current signature value and export list look like the ones in Figure 2. Note that because I used the binder language above when recreating CUSTPROCS, I did not need to do anything to OEPGM01 or any other programs that had been using the original version of CUSTPROCS.
Figure 1: Here's the original version of CUSTPROCS referenced by OEPGM01. (Click images to enlarge.)
Figure 2: And this is the new version of CUSTPROCS referenced by OEPGM01.
The example we've seen so far illustrates the use of multiple system-generated signatures managed by binder language, which is scenario #1 from the primary scenarios we outlined in the earlier article. Let's now look at the other two scenarios to see other ways to use binder language for signature management.
Hard-Coded Signature Values
The STRPGMEXP statement has a SIGNATURE parameter. The default value for this parameter is *GEN (generated). The other alternative is to hard code a signature value of our own. One way a hard-coded signature can be used to our advantage is to put some meaningful information into the signature for documentation purposes. When the system generates the signature value, it is a number that has no meaning to mere mortals. But we can put whatever value we want in there, such as, for example, the date, the time, or other useful information about the new version of the service program. Using this scenario, my binder language might look something like the following:
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTPROCS19SEP07')
EXPORT SYMBOL(GetCustInfo)
EXPORT SYMBOL(SearchByPhone)
EXPORT SYMBOL(ValidCust)
EXPORT SYMBOL(SearchByCustNo)
ENDPGMEXP
STRPGMEXP PGMLVL(*PRV) SIGNATURE('CUSTPROCS22MAR07')
EXPORT SYMBOL(GetCustInfo)
EXPORT SYMBOL(SearchByPhone)
EXPORT SYMBOL(ValidCust)
ENDPGMEXP
All the same rules for exports lists are still in effect here: There are multiple export lists, and the current export list must retain the positions of all exports from all previous/earlier export lists. There is still only one current list, which is the only "real" export list used at run time. Any and all *PRV export lists are there only for purposes of maintaining the earlier signatures. The advantage in this case is that the signature values themselves, which are visible from the objects using the Display Service Program (DSPSRVPGM) and Display Program (DSPPGM) commands, are far more meaningful, which could make maintenance and problem determination easier.
The third scenario for using binder language is one that seems to be growing in popularity. It also uses a hard-coded signature value, but instead of maintaining multiple signature values and multiple export lists, there is only one export list (the current one) and therefore only one signature value. The most common value for the signature I've seen used in this case is the name of the service program.
In this scenario, the binder language would look like this the first time the service program was created:
STRPGMEXP PGMLVL(*Current) SIGNATURE('CUSTPROCS')
EXPORT SYMBOL(GetCustInfo)
EXPORT SYMBOL(SearchByPhone)
EXPORT SYMBOL(ValidCust)
ENDPGMEXP
The second time the service program is created, the binder language looks like this:
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('CUSTPROCS')
EXPORT SYMBOL(GetCustInfo)
EXPORT SYMBOL(SearchByPhone)
EXPORT SYMBOL(ValidCust)
EXPORT SYMBOL(SearchByCustNo)
ENDPGMEXP
Note that even though we have only one export list in the binder language each time, it is still essential that each "old" procedure name remain in the same sequential position in every iteration of the binder language—i.e., GetCustInfo must always be in position 1 and ValidCust must always be in position 3, etc.
The advantage of this scenario is that the binder language doesn't become cluttered up with many, many old export lists and signatures that may need to be cleared out from time to time. The disadvantage of this approach is that there is very little help with problem determination if something were to go wrong at some point.
For example, if a developer who hadn't read this article decided to resequence the exports in the binder language or decided to add SearchByCustNo as the first export symbol statement as they recreated CUSTPROCS, OEPGM01 and all other programs using CUSTPROCS would likely begin to run the wrong procedures at the wrong times. This kind of error would typically be very difficult to track down. While this situation could certainly occur with the other two binder language scenarios as well, at least the *PRV export lists might help to point out the error. In this case, with only one version of the export list, it would be difficult to determine not only what went wrong, but also how to fix the export list to make it right!
Closing Thoughts
One more point I'll mention here is that the Retrieve Binder Source (RTVBNDSRC) command will generate binder language for you—either from an existing service program or from a list of modules that will be put into a service program. Some people find this a useful starting point for their binder language. Personally, I tend not to use the command simply because the syntax is very simple and I'll need to be maintaining the binder language source manually over time anyway.
Binder language can be used for more than just managing signature values, but this is its most common purpose in life. I hope the examples of using binder language in these scenarios helps you in determining the best way to manage your service program signatures. Remember that binder language is always optional. You may create and maintain service programs without it by specifying Export(*All) each time you create or re-create your service programs. Just remember that if the change you made to the service program changed the export list (e.g., by adding a new callable procedure), you will need to re-bind all the programs that use that service program. See the previous RPG Developer article for more information on that.
Happy binding!
LATEST COMMENTS
MC Press Online