Why service programs need binding source
Paul Tuohy05.05.2004
When you start to delve into the wonderful world of ILE and service programs, you quickly come across binding source. You know you should use it, but you're not sure why. It has something to do with Signatures which are like Level Checks. You know that bad things will happen if you don't use binding source; namely, if you recreate the service program you will have to recreate all programs that use the service program.
You put up with maintaining binding source when you need to add procedures to the service program, but it is annoying and you'are still not sure why you have to do it. Here is an explanation of what binding source provides and why you should use it.
Dynamic vs. static
What does a procedure need when it makes a call? It needs the address of the code it is calling, and it needs to provide the addresses of the parameters it is passing.
A Dynamic Call means that these addresses are calculated each time the call is made. A Static Call means that the addresses are calculated when the program is created.
If you have an RPG IV program with a subprocedure coded at the end of the source, what happens when you create the program? CRTPGM generates a pointer with the address of the subprocedure. That is why subprocedure calls are fast; the program has a pointer to where the procedure is in memory.
But what about service programs? They are separate objects where all calls are static with the overhead of one dynamic call (the initial loading of the service program). Also, we can change the service program -- which can change the addresses of procedures -- but we don't have to recompile the programs. How can CRTPGM figure out the addresses of procedures that will be loaded in a separate program? That is why you need binding source.
What is binding source?
Binding source provides control over two functions: signatures and exports.
A signature is the equivalent of a record format identifier on a file. The signature is stored in any program that uses the service program when the program is created. (Use DSPPGM DETAIL(*SRVPGM) to view the signatures embedded in a program.) The service program is loaded when the program is called, at which point the signature of the service program is checked against the signature in the program to ensure they are the same. Unlike files, a service program can have multiple signatures -- you can actually define your own signature. (Use DSPSRVPGM DETAIL(*SIGNATURE) to view the signatures in a service program.) Please note that signatures are shown in hex.
Exports is a list of the procedures that are exported (i.e. can be called) from the service program and, most importantly, the sequence in which they are exported. Remember, the overhead of using a service program is one dynamic call and all other calls are static; this is what the export list helps you achieve.
Binding source is usually stored in a member with the same name as the service program in a source physical file named QSRVSRC; these are the defaults for the SRCFILE and SRCMBR keywords on the CRTSRVPGM command.
An example
Let's look at an example of maintaining binding source when adding procedures to a service program. To reduce possible confusion, in this example all of the procedures will have the same parameter list.
In the beginning
You have a service program named DIRECTIONS that exports two procedures (GOLEFT and GORIGHT); it was created using the binding source shown in Figure 1. The STRPGMEXP instruction identifies that this is the current export list and that 'FIRST' is a supported signature. The EXPORT instructions identify the "callable" procedures in the service program. There may be other procedures in the service program that can be called only from procedures within the service program.
StrPgmExp PgmLvl(*CURRENT) Signature('FIRST')
Export Symbol('GOLEFT')
Export Symbol('GORIGHT')
EndPgmExp
Figure 1: Binding source for service program exporting two procedures
When you create the service program, it will contain two "arrays." The first is an array corresponding to the Export list in the binding source. The second is a corresponding array of the addresses of the procedures in the service program.
You create a program that calls the GORIGHT procedure. Two things happen: The program will store the signature of 'FIRST,' and it will equate that all calls to GORIGHT should call the second procedure exported from the service program, i.e. it does a lookup on the first array to determine that it should be using the procedure pointer in the second element of the second array.
Add a procedure
You decide to add a third procedure (GOBACK) to the service program, so you change the binding source as shown in Figure 2. This indicates that the service program now has two signatures (FIRST and SECOND). Also note that the *PRV does not have an Export list (you do not have to include the list if you are defining your own signatures). That is because the Export list is always taken from the *CURRENT definition.
StrPgmExp PgmLvl(*CURRENT) Signature('SECOND')
Export Symbol('GOLEFT')
Export Symbol('GORIGHT')
Export Symbol('GOBACK')
EndPgmExp
StrPgmExp PgmLvl(*PRV) Signature('FIRST')
EndPgmExp
Figure 2: Adding a third procedure to the binding source
What about your program? It will still work fine because it has a signature of FIRST, so it does not need to be compiled.
Add a procedure incorrectly
Now for a deliberate mistake to highlight what happens with binding source. You decide to add a fourth procedure (GOAHEAD) to the service program, so you change the binding source as sown in Figure 3, with the deliberate mistake of adding the new procedure at the top of the list instead of the bottom. The service program now has three signatures (FIRST, SECOND and THIRD).
StrPgmExp PgmLvl(*CURRENT) Signature('THIRD')
Export Symbol('GOAHEAD')
Export Symbol('GOLEFT')
Export Symbol('GORIGHT')
Export Symbol('GOBACK')
EndPgmExp
StrPgmExp PgmLvl(*PRV) Signature('SECOND')
EndPgmExp
StrPgmExp PgmLvl(*PRV) Signature('FIRST')
EndPgmExp
Figure 3: Adding a fourth procedure to the binding source in the wrong place
Again, your program will not fail when it is called because it has a matching signature with the service program (FIRST). But you will not get what you expect when it calls GORIGHT. Remember, when the program was created this was equated to calling the second procedure exported from the service program, which is now GOLEFT!
Refer to the ILE Concepts manual for an example of making this work to your advantage.
A tip
One of the tedious tasks with binding source is getting the original source created. It can be a lot of typing for a large service program. You may want to try the following:
- Create the service program using EXPORT(*ALL).
- Generate the binding source using the RTVBNDSRC command.
- Define your own signature. Although it is not a necessity, I prefer to have control over the signatures.
- If required, make changes to the binding source (rearrange the sequence or remove some export definitions).
- Recreate the service program using EXPORT(*SRCFILE), even if you have not changed the binding source. The binding source generated by the RTVBNDSRC command does not reflect the sequence in which procedures are actually exported from the service program.
- Create the programs that bind to the service program.
Although the use of binding source may at first appear to be an annoying overhead, I hope it is now evident that there is a method to the madness. Others wiser then I may think of better ways to achieve the same result, but I, for one, am willing to make do with binding source.
------
About the author: Paul Tuohy is CEO of ComCon, an iSeries consulting company. He is the author of Re-Engineering RPG Legacy Applications and is one of the quoted industry experts in the IBM Redbook "Who Knew You Could Do That With RPG IV?"
How do I create and use a service program
A small sample service program, with source, and instructions for how to
create and use it.
a. Create the 6 source members below, changing MYLIB to your library
name in all the source.
b. Use CRTRPGMOD to create the two srvpgm modules SRVSAMP1 and SRVSAMP2.
c. Use CRTCLPGM to create the CL program SRVSAMPCRT.
d. Call SRVSAMPCRT to create the srvpgm MYLIB/SRVSAMP.
e. Create a binding directory
===> CRTBNDDIR MYLIB/SRVSAMPBND
f. Add your service program to the binding directory
===> ADDBNDDIRE MYLIB/SRVSAMPBND OBJ((MYLIB/SRVSAMP *SRVPGM))
g. Create your test program. Since it has the BNDDIR keyword in the
H spec, it will automatically find your service program.
===> CRTBNDRPG MYLIB/SRVSAMPTST SRCFILE(MYLIB/QRPGLESRC)
h. Try calling your test program. Enter a value like 06.03.31 or 060331
i. Try adding a new procedure to module SRVSAMP1.
- say a procedure to get a numeric value similar to getAnswer
- add the prototype to SRVSAMPPR
- code the procedure in SRVSAMP1
- add an EXPORT line to QSRVSRC SRVSAMPBND
- call your CL to create the srvpgm again
- reclaim the activation group that your test program runs in
(use DSPPGM to see what activation group it is)
- call your test program again to make sure it still runs ok
with the new version of the service program
j. Add some code to your test program to call the new procedure, and
recompile and test.
Source files:
1. Binder source MYLIB/QSRVSRC SRVSAMPBND type BND:
/*------*/
/* Rules: */
/* 1. Never change the order of exports */
/* 2. Add new exports at the end */
/*------*/
strpgmexp signature('SRVSAMP')
export symbol('getAnswer') /* 1 */
export symbol('chkDate') /* 2 */
endpgmexp
2. CL to create service program MYLIB/QCLSRC SRVSAMPCRT type CLP:
crtsrvpgm mylib/srvsamp +
module(mylib/srvsamp1 +
mylib/srvsamp2) +
srcfile(mylib/qsrvsrc) srcmbr(srvsampbnd)
3. RPG prototype source MYLIB/QRPGLESRC SRVSAMPPR type RPGLE:
/if defined(SRVSAMPPR_COPIED)
/eof
/endif
/define SRVSAMPPR_COPIED
D getAnswer pr 25a varying
D extproc('getAnswer')
D question 25a const varying
D chkDate pr n
D extproc('chkDate')
D input 10a const varying
D output d
D formatParm 10a const varying
D options(*nopass)
4. RPG test program source MYLIB/QRPGLESRC SRVSAMPTST type RPGLE:
H dftactgrp(*no) bnddir('MYLIB/SRVSAMPBND')
/copy srvsamppr
D ans s 10a varying
D date s d
D ok s n
/free
ans = getAnswer ('Give a date in ymd format');
ok = chkDate (ans : date : '*YMD');
if ok;
dsply ('That was ok, date was ' + %char(date));
else;
dsply 'Oops, was not valid';
endif;
*inlr = '1';
5. RPG srvpgm module 1 MYLIB/QRPGLESRC SRVSAMP1 type RPGLE:
H nomain
/copy srvsamppr
P getAnswer b export
D getAnswer pi 25a varying
D question 25a const varying
D answer s 25a varying
/free
dsply question ' ' answer;
return answer;
/end-free
P getAnswer e
6. RPG srvpgm module 2 MYLIB/QRPGLESRC SRVSAMP2 type RPGLE:
H nomain
/copy srvsamppr
P chkDate b export
D chkDate pi n
D input 10a const varying
D output d
D formatParm 10a const varying
D options(*nopass)
D format s 10a varying inz('*ISO')
D sep s 1a
D sepPos s 10i 0
D haveSep s n
D standardSep s 10a varying
/free
// check for optional parameter
if %parms > 2;
format = formatParm;
endif;
// check for separators
sepPos = %check('0123456789' : input);
if sepPos > 0;
sep = %subst(input : sepPos : 1);
haveSep = *on;
endif;
if format = '*ISO';
if haveSep;
standardSep = %xlate(sep:'-':input);
output = %date(standardSep : *iso);
else;
output = %date(input : *iso0);
endif;
elseif format = '*YMD';
if haveSep;
standardSep = %xlate(sep:'/':input);
output = %date(standardSep : *ymd/);
else;
output = %date(input : *ymd0);
endif;
endif;
return *on; // it was ok
begsr *pssr;
return *off; // some error occurred
endsr;
/end-free
P chkDate e
Editors:Ted Holt
Hi Ted.
I have heard that IBM recommends limiting the number of modules in a service program to 10 to 20. The reason stated is to keep the number of static variables defined from getting too large and affecting storage in the PAG and increasing program activation time. I can see the reasoning for the activation argument but if you are creating the modules with a NOMAIN option does the static variable argument still hold?
Service programs provide a way to group related procedures into a library of sorts. For instance, you might gather string manipulation routines into a service program. Still, your question is a good one. Just how many is too many? I ran your question past Barbara Morris, of the RPG compiler development team in Toronto, and this is what she told me:
"Even with the NOMAIN keyword, an RPG module can have a significant amount of static storage. There are many internal control structures kept in static storage (for example the file control blocks, the exception handling information, many compiler-internal variables). Any variables defined in the main source section are in static storage.
Use the Display Module (DSPMOD) command, specifying DETAIL(*SIZE) to see how much static storage a module uses. You'll see something like this:
Static storage (bytes):
Current size ...... : 3984
Breaking up a service program into two service programs serves no purpose if both service programs are always used together, so the question of "how many modules" only really applies when binding unrelated modules, and there's no particularly good reason for putting unrelated modules in the same service program.
I think that the grouping of modules into service programs should be determined mainly by the function of the modules, not by some arbitrary restriction. It's an application-design problem, similar to the grouping of programs into libraries.
Reducing the number of modules by putting related procedures into the same module will have a good effect on static storage, especially for RPG, since it will reduce the total number of RPG internal structures. But I wouldn't recommend a witch-hunt against static storage unless it was a problem."
In addition to Barbara's recommendations, you should look at how you use activation groups. Using the Create Service Program (CRTSRVPGM) default of *CALLER is fine in pure ILE environments. If your service program might be activated in the default activation group, use a named activation group to allow the system to better manage memory usage. The ILE Concepts manual available at pubs/html/as400/v5r1/ic2924/books/c4156065.pdf contains a good description of activation groups.