Service Program Signature Violations
Maybe I'm missing something obvious ... is there a way to make RPG programs ignore signature violations in service programs?
QEZ Solution: New procedure new service program.
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.
There was a good discussion on binder source earlier this year. Search the archives. However, read on ...
I'm working on a bunch of service programs, and every time I add a procedure to my service program, I have to recompile all the callers.
I'll bet you are creating the service program with EXPORT(*ALL). The signature is calculated from the exported items -- procedures and data. If you cheat and use EXPORT(*ALL) when building the service program you will get a new signature whenever you:
- Add a new procedure
- Add a new data item with EXPORT
- Change the name of a procedure
- Change the name of a data item with EXPORT
- Change the order of exported procedures or data
A new signature will require that you recompile the calling programs and service programs unless you manage the signature creation.
If you define and use binding source you are controlling the EXPORTed items and therefore controlling the signature -- you can also specify the desired signature explicitly.
The binding source contains a STRPGMEXP command, one or more EXPORT commands, and an ENDPGMEXP command. Note that these commands are not runnable -- they merely provide a convenient mechanism for defining things (like a tag language).
Here is a working example. I design a service program called STRINGS which will contain string handling functions. I initially write three procedures which will be publicly accessible (i.e., callable). They are toUppercase(), toLowercase(), and centre(). I may also have some support procedures which I do not want exposed. They are only for the use of the service program and not to be invoked by any caller. Let us presume I have a getStringLength() private procedure.
I define my public interface using binding source.
STRPGMEXP PGMLVL(*CURRENT) EXPORT SYMBOL(toUppercase) EXPORT SYMBOL(toLowercase) EXPORT SYMBOL(centre) ENDPGMEXP
Note that the symbol can be a procedure name or a data item name. Note also that the procedure names used here are not enclosed in apostrophes which means the will be interpreted as uppercase names. That is OK because my service program is written in RPG IV which is not case sensitive. If I were writing the service program in C then I must ensure the symbol name is exactly the same as the name in the module and use apostrophes as appropriate. Also note that the getStringLength() function is not mentioned in the binding source.
There is a tool in QUSRTOOL called GENBNDSRC which is useful for creating the first set of binding source but since it insists on sorting the EXPORT statements is completely bloody useless for subsequent changes.
If I store the binding source in member STRINGS in file QSRVSRC then when I create my service program the binding source will be processed automatically.
I compile the programs that use these string functions and life in the IT department grinds slowly on.
Some time later I discover the need for an additional string function for a new program I am creating. I need a SOUNDEX function so I write one and add it to my STRINGS service program. I need to make the getSoundex() function part of the public i�terface. If I simply add it to the existing export block then I will create a new current signature thus breaking any existing programs that use STRINGS. I must follow certain steps to ensure I don't break things.
I edit the binding source and I duplicate the export block and change one of the blocks to PGMLVL(*PRV). I then add my new procedure the end of the *CURRENT block.
STRPGMEXP PGMLVL(*CURRENT) EXPORT SYMBOL(toUppercase) EXPORT SYMBOL(toLowercase) EXPORT SYMBOL(centre) EXPORT SYMBOL(getSoundex) ENDPGMEXP STRPGMEXP PGMLVL(*PRV) EXPORT SYMBOL(toUppercase) EXPORT SYMBOL(toLowercase) EXPORT SYMBOL(centre) ENDPGMEXP
Any new procedures MUST, repeat MUST go at the end of the current block. (The real code can go anywhere in the service program itself.)
Now when I create the STRINGS service program it gets two signatures created. A current one used by any new programs I compile, and a previous one which mathces the one used by the existing programs. Magic happens! Any existing programs that use STRINGS see the previous signature and thus the previous view of the service program exports.
The ILE Concepts manual has a very good discussion on this stuff. You should read it thoroughly before creating binding source.
It is also possible to define your own signature.
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('MY_SIG') EXPORT SYMBOL(toUppercase) EXPORT SYMBOL(toLowercase) EXPORT SYMBOL(centre) ENDPGMEXPM
This is a little dangerous because the system will assume I know what I am doing but it does allow me to be a little more flexible in building a service program in increments. Let us presume I am building the STRINGS service program in stages. My first build use the binding source in the previous paragraph. Other developers start using it. I then add the SOUNDEX function. I don't want another signature because, conceptually, the SOUNDEX function is part of the current version of the service program. I change the binding source as follows:
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('MY_SIG') EXPORT SYMBOL(toUppercase) EXPORT SYMBOL(toLowercase) EXPORT SYMBOL(centre) EXPORT SYMBOL(getSoundex) ENDPGMEXP
This has the same effect as the first example but only one signature was created. I am accepting the responsibility for ensuring that the exports are in the correct order rather than the system (this is similar to having a zero signature but much more flexible).
If I am careless and I define the binding source as
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('MY_SIG') EXPORT SYMBOL(getSoundex) EXPORT SYMBOL(toUppercase) EXPORT SYMBOL(toLowercase) EXPORT SYMBOL(centre) ENDPGMEXP
then I have just broken all existing programs that use STRINGS. When an existing program calls toUppercase() it will actually invoke getSoundex(). When it invokes toLowercase() it wil actually invoke toUppercase(). Oops!
Sometimes this behaviour is useful. The ILE Concepts manual has an example showing how to change a rate calculation function for new programs but not upset existing programs. I wouldn't do this sort of thing the way that manual describes but it is interesting in an illustrative way.
One last example. The literate among you will have noticed that I spell centre the correct way. However my cousins (thrice removed) in the American branch office would spell it center thus my service program will drive them insane by generating binding errors telling them the center procedure cannot be found. One solution would be to use an RPG IV prototype to map center() to centre(). A similar thing could be done for C. However, that doesn't help COBOL programmers. So I add a new procedure to STRINGS called center() which simply invokes centre() to do the real work. I update the binding source accordingly.
STRPGMEXP PGMLVL(*CURRENT) EXPORT SYMBOL(toUppercase) EXPORT SYMBOL(toLowercase) EXPORT SYMBOL(centre) EXPORT SYMBOL(getSoundex) EXPORT SYMBOL(center) ENDPGMEXP STRPGMEXP PGMLVL(*PRV) EXPORT SYMBOL(toUppercase) EXPORT SYMBOL(toLowercase) EXPORT SYMBOL(centre) EXPORT SYMBOL(getSoundex) ENDPGMEXP STRPGMEXP PGMLVL(*PRV) EXPORT SYMBOL(toUppercase) EXPORT SYMBOL(toLowercase) EXPORT SYMBOL(centre) ENDPGMEXP
Do you see the process? It is quite straightforward if you follow the rules. Now, everyone is happy. Although given the atrocious spelling of most programmers they are just as likely to spell centre both ways in any given program.
Binding source does not help with parameter interface changes. One can only hope that will be a future extension. It would be really good to have overloaded procedures and use binding source to define the parameter types and number. Until then we must content ourselves with OPTIONS(*NOPASS) or *OMIT for extending parameter lists and resign ourselves to the fact that we can't pass different data types in the same parameter via prototyped calls.
Copied from a post by Simon Coulter in RPG400-L QEZ Solution Dan Sylvester