What Are Service Programs?
A service program is the middle
ground between a module and a separate standalone program. A module will become
a part of a program when the program is compiled. A standalone program will be
completely separate and will be called independently each time it is used from
the main program. A service program will bind to a program the first time it is
called by the main program. So you pay the overhead the first time it's used
from the main program, but all subsequent calls will behave as if they were
bound to the program.
The service program is not
actually part of the main program; instead, it binds to the main program the
first time it's called.
Service programs are a very powerful component of the ILE environment. A service program allows a program to reference a module dynamically rather that statically.
A service program is created with the command CRTSRVPGM. One or more modules are used to create a service program. In our example, we're going to create a service program using our one procedure module. We'll give the service program the same name as the module (which has the same name as the source member).
The service program will have an object type *SRVPGM. A service program can kind of be thought as a collection of modules, which in turn has a collection of procedures.
CRTPGM PGM(prog1) MODULE(prog1) BNDSRVPGM(procs1)
The entry module defaults to the first (and only) module. The program will be created with a reference to the service program, rather than an actual copy of it. Sometimes this is called bind by reference vs. bind by copy.
Obviously, the main advantage this provides is that when the modules need to be changed and re-compiled, only the service program needs to be changed. The next time the program object runs, the new version of the service program will be retrieved. In this sense, a service program is very similar to an externally called program, while retaining the benefits of ILE procedures.
The only significant disadvantage dynamic binding has over static binding, is that service programs do take some extra resources to resolve references at run time vs. compile time for modules. This extra overhead should be irrelevant however compared to the maintenance benefits provided. The only exception to this is if a program that uses a service program is called several hundred times online of thousands of times in batch.
When to Put a Procedure into a Service Program
To determine when a procedure should
be put into a program, you need to consider whether the procedure could be
useful in multiple places. If you have some code in which you created a
procedure that is useful only in a single program, then you will probably want
to keep the procedure contained within the code that you are using it in. But
if you have a procedure that could be used in multiple programs, you may want
to consider putting it into a service program.
Prime candidates for service program
procedures would be procedures created during the development of a new project
in which you'll be reusing specific capabilities. Or consider generic
functionality that could be used anywhere. For existing functionality, you
could put the procedure into a service program and gradually replace the
outdated references as you encounter them.
Signatures
When a service program is created, it is assigned a unique signature based on the procedure sequence and exports. When a program is bound to a service program, it is bound to the signature as well. If new procedures are added to the service program, or exports are re-arranged, a different signature will be generated. If the program is run against the new version of the service program without being re-bound, a run-time error will generate.A signature in this sense is very similar to a record format level identifier. If a record format changes and the program is not re-compiled, you will get a level check error which is very similar to a signature violation.
By using a binder language you can explicity specify a signature and eliminate signature violations.
Q: What is a signature?
A: A signature is a value that provides a similar check for service programs that a level check does for files. It helps ensure that changes made to the service program are done in such a way that the programs using them can still function properly. The signature is typically generated by the system when you issue a Create Service Program (CRTSRVPGM) command specifying Export(*ALL). The signature value is generated by an algorithm that uses as input the names of all of the service programs "exports" and their sequence. "Exports" are the names of callable procedures and any exported data items. When you create a program that references that service program, its current signature value is copied into your program. If the export list were to be changed without the programs detection, the program could potentially call the wrong procedure.
Q: What causes a signature to change?
A: A signature changes when the list of exports for the service program changes. The most common cause of a signature change is adding a new procedure to the service program. Despite popular "wisdom" to the contrary, a change in a procedures parameters doesn't change the service program's signature.
Q: What happens when the signature changes?
A: When a program is called, it immediately checks the signature value of any service programs that it uses and produces a signature violation message if the signatures don't match. This happens at program start up not when you actually call a service program procedure.
Q: I have a Signature Violation-what now?
A: You have a couple of options to correct this situation. You can either re-bind all of the programs that reference the changed service program or you can create binder language to manage the service programs signatures.
Q: How do I re-bind the programs that reference the service program?
A: Use the Update Program (UPDPGM) command. Because you aren't replacing any modules, specify Module(*None). You don't need to specify the service program because the UPDPGM command automatically re-checks the signatures of any bound service programs. If any signatures have changed, it also updates the programs signature values.
Q: Why not re-compile the programs or re-create the programs with the Create Program (CRTPGM) command?
A: It's typically better to make only one change at a time to avoid the possibility of introducing unwanted or unnecessary changes while fixing a problem. Re-compiling or re-creating could potentially introduce changes. For example, if the source has changed, re-compiling the program definitely introduces changes. Even if you re-create the program from some previously compiled modules, you cannot always be sure they represent the exact same code currently in the program. In addition, other program attributes (related to authority, Activation Group, etc.) could change when you re-create it. But perhaps the best reason to use UPDPGM is because it's easier and faster than either of the other options.
Q: How do I know which programs need to be updated?
A: That can be difficult. If you have a cross-reference tool that understands service programs, it should provide the information you need. Otherwise, you can display each program using the Display Program (DSPPGM) command and see (on the fourth display screen) the list of service programs it uses and what signature value it's looking for. Of course, if you know the signature has changed, the fact that the program references the service program tells you it must be updated. This method is tedious to do manually. You could also write a program to automatically perform the updates for you using the appropriate APIs (DSPPGM has no outfile support). This is beyond the scope of this article but we may cover it in the future.
Q: What about binder language?
A: If you don't want to update several programs every time you add a new procedure to your service program, you can create binder language to manage the signature values. Let's examine an example.
Say you have a service program called DateStuff that originally contained two procedures called DayOfWeek and DayName. For purposes of writing binder language, it doesn't matter if both procedures are in the same module or combination of modules. Binder language only cares about the procedure names. When you originally created DateStuff, you specified Export(*All), because you weren't using binder language.
Recently you added a new module to the service program that contains procedures named GetDuration and LastDayOfMonth and specified Export(*All) again. Now your signature is different because you added procedures (exports) that changed the signature value. Programs that were working fine with the old version of the service program now fail with a signature violation.
Let's create some binder language to fix this. Key your binder language into a source file called QSRVSRC with a member name that matches your service program: DateStuff. Member type is BND. The binder language should look like this:
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL(DayName)
EXPORT SYMBOL(DayOfWeek)
EXPORT SYMBOL(GetDuration)
EXPORT SYMBOL(LastDayOfMonth)
ENDPGMEXP
STRPGMEXP PGMLVL(*PRV)
EXPORT SYMBOL(DayName)
EXPORT SYMBOL(DayOfWeek)
ENDPGMEXP
Note that the sequence of the procedure names (called export symbols in binder language) is important. Your previous export list (*PRV) must be in the sequence that the system put it in when the service program was first created with Export(*All). Because the system puts the procedure name exports in alphabetical sequence, that's what we've done here. It's equally critical that the *CURRENT export list maintains the sequence of ALL exports in ALL *PRV lists. That is, DayName must remain in the first position and DayOfWeek in position 2, etc.
Now that you have the binder language created, you can issue an Update Service Program (UPDSRVPGM) command to your DATESTUFF Service Program, this time specifying Export (*SRCFILE). Make sure the source member and file names are specified correctly.
Now your service program has two different signatures-one for each export list in the binder language. The signature generated by the *PRV list should match the service programs original, so all of the old programs will continue to work. New or updated programs will automatically pick up the signature generated by the *CURRENT list.
If you need to add another module in the future, you can create a second *PRV list. Just remember that the sequence of the procedure names is critical. Make a block copy of the *CURRENT list and change PGMLVL in one of them to *PRV. Add any new procedure names to the END of the new *CURRENT list and don't remove or re-sequence the procedure names in any of the export lists. You can apply your binder language on CRTSRVPGM or UPDSRVPGM after it's created.
This technique uses system-generated signature values. It's also possible to hard code your own signature values.
Q: Is there a way to make RPG programs ignore signature violations in service programs?
A: Yes. You can use binding source to force LVLCHK(*NO) which creates a signature of zero. However, like LVLCHK(*NO) on database files, doing so is a truly stupid idea. And since you must create binding source to set a zero signature you might as well learn to use it properly.
A lot of discussions could be avoided if IBM would just add a LVLCHK parameter to the CRTSRVPGM command instead of forcing us to write and maintain a mostly useless binding source only for that purpose.
ReplyDelete