Find out how to support multiple signatures and what to be aware of when changing service programs.
I initially intended on just doing a three-part series that worked its way toward binding directories, but I feel as though I need to cover more on the binding language before I can move on. So this follow-up article will discuss support for multiple signatures and things that you should be cautious of when using the binding language.
In my previous article, "Using Binding Language with Your Service Programs," I covered the syntax of the binding language and explained how you can use it to make your life easier by keeping a signature from changing so you don't have to recompile all of the programs that are using the service program.
Offering a way to work around the signature may give the impression that signatures are a bad thing, but that's not the case at all. Having the signature change every time the service program is recompiled is the safest way to make sure that all of your programs work as they are expected to when the service program changes.
When you specify the signature, you are telling the programs that are using the program that you know what you're doing and the calling programs don't need to worry about changes that are happening to your service program. By specifying the signature, you're making a promise to the calling programs that everything will work as was specified when the program was compiled with the signature at that time and that the order of the procedures has not changed within the service program.
The Code
I will be reusing the code from a previous article, "How to Create, Compile, and Use Service Programs," that translates special HTML characters within a string. Here's the RPG code for the main program:
D/COPY MyLib/QCOPYSRC,MCPSRVPGM
D inBytes S 100A
D outBytes S 100A
D displayBytes S 52A
D posi S 10I 0
C*
/free
inBytes = '<b>''Tom&&Jerry''</b>';
displayBytes = 'Original: ' + %trim(inBytes);
DSPLY displayBytes;
outBytes = htmlEncode(inBytes);
displayBytes = 'Enc: ' + %trim(outBytes);
DSPLY displayBytes;
outBytes = htmlDecode(outBytes);
displayBytes = 'Dec: ' + %trim(outBytes);
DSPLY displayBytes;
*inlr = *ON;
/end-free
Below is a simple test program called MCPSRVPGM, which is made up of two procedures: htmlEncode and htmlDecode. Here is the code for the htmlEncode procedure with the copy file for the procedures on the first line:
D/COPY MyLib/QCOPYSRC,MCPSRVPGM
**********************************************************************
* PROCEDURE NAME: htmlEncode
* INPUT: Source String
* OUTPUT: String (HTML Encoded)
**********************************************************************
P htmlEncode...
P B EXPORT
D htmlEncode...
D PI 65535A varying
D* Passed Parameter List
D argIn 65535A const varying
D* Local Variables
D svPosi S 10I 0
D svOut S 65535A
D svOut2 S 65535A
/free
svOut = argIn;
svOut = %scanRpl('&': '&': svOut);
svOut = %scanRpl('"': '"': svOut);
svOut = %scanRpl('''': ''': svOut);
svOut = %scanRpl('<': '<': svOut);
svOut = %scanRpl('>': '>': svOut);
return svOut;
/end-free
P htmlEncode E
And here is the source code for the htmlDecode procedure, which does the opposite of the encode procedure with a little less logic than the encode version:
**********************************************************************
* PROCEDURE NAME: htmlDecode
* INPUT: Source String (HTML Encoded)
* OUTPUT: Decoded String
**********************************************************************
P htmlDecode B EXPORT
*
D htmlDecode PI 65535A varying
D argIn 65535A const varying
* LOCAL VARIABLES *
D svOut S 65535A varying
/free
svOut = argIn;
svOut = %scanRpl('"': '"': svOut);
svOut = %scanRpl(''': '''': svOut);
svOut = %scanRpl('<': '<': svOut);
svOut = %scanRpl('>': '>': svOut);
svOut = %scanRpl('&': '&': svOut);
return svOut;
/end-free
P htmlDecode E
To keep the signature from changing, we specify the following binder source:
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('MCPSRVPGM')
EXPORT SYMBOL('htmlDecode')
EXPORT SYMBOL('htmlEncode')
ENDPGMEXP
The binder source will specify the signature to be MCPSRVPGM. You can refer to my previous article for a detailed explanation of the program logic. Here is the output of the program when you run it:
DSPLY Original: <b>'Tom&&Jerry'</b>
DSPLY Enc: <b>'Tom&&Jerry'</b&g
DSPLY Dec: <b>'Tom&&Jerry'</b>
To recap, the main program is used to test the procedures in the service program. Each of the procedures will either encode or decode special HTML characters in a string. You can see that the original string is output, then it is encoded, and it finishes up by decoding back to the original string.
Now we're going to change the order of the procedures.
Changing the Order of Procedures
When you specify a signature for a service program, you're saying that everything that was in existence at the time it was used will still remain in the same procedure order it was in when you compiled the calling program the first time. So what happens if you change the order of the procedures?
You can change the physical order of the procedures within the RPG code and everything will be fine because you compile the service program specifying the binder language source, and that source will specify what order the procedures are accessed in. But if you change the order in the binder language as follows, then you'll encounter a problem:
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('MCPSRVPGM')
EXPORT SYMBOL('htmlEncode')
EXPORT SYMBOL('htmlDecode')
ENDPGMEXP
Notice how htmlEncode is now first and htmlDecode is now second. Now, if you were to recompile your service program, the expected order of procedures has changed, but the signature remains the same (MCPSRVPGM).
Now if you call your main RPG program, it will run without complaints, but your output would look like this:
DSPLY Original: <b>'Tom&&Jerry'</b>
DSPLY Enc: <b>'Tom&&Jerry'</b>
DSPLY Dec: <b>'Tom&&Jerry'</b&g
The main program hasn't changed at all, and it is referring to the correct procedure names. And the service program has recompiled and is working properly. The problem is that when the main program was compiled the procedures were in a different order, and because we specified that the signature did not change, the main program just uses the service program the way it was when the main program was compiled.
This is not good and could be a disaster. In a situation where the order of the procedures changes, you must change the signature to ensure that your programs will function properly. Otherwise, you'll have to be diligent to make sure that you manually recompile all programs that are affected. But with the use of a different signature, you can ensure that your service programs will be used properly.
Adding a New Procedure
OK, let's put our binder language back to its original order so we can eliminate the ordering problem. Now we are going to add a new procedure to the service program. I'm going to create a simple procedure that will add a percentage to an incoming number and return the results.
**********************************************************************
* PROCEDURE NAME: addTax
* INPUT: Cost before Tax
* OUTPUT: Cost including Tax
**********************************************************************
P addTax B EXPORT
*
D addTax PI 9S 2
D argCost 9S 2 const
/free
return argCost * 1.06;
/end-free
P addTax E
On a side note, if you were to prototype your addTax procedure using the extProc keyword, you could set the case sensitivity for the procedure name and use this in your binder language source as follows:
D addTax PR 9S 2 extProc('addTax')
D argCost 9S 2 const
I made a follow-up comment about this in the forums on my previous article. It was brought to my attention by a fellow author that I've always been a fan of, but I wanted to mention it again for everyone who may not have seen the comments posted.
After we add the code to the service program source and add the prototype to the QCOPYSRC file, we can add the export symbol to the binder language source as follows:
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('MCPSRVPGM')
EXPORT SYMBOL('htmlDecode')
EXPORT SYMBOL('htmlEncode')
EXPORT SYMBOL('addTax')
ENDPGMEXP
At this point, you could recompile your service program with the same signature and still not compile your main program. And if you call your main program, you will see that there is no signature violation and the program executes properly. This is because we added the procedure to the end of the list of procedures, and the locations of the first two procedures did not change.
Changing the Order with Mismatched Parameter Types
Now it's time for the fun part. When we previously changed the order of our procedures, the parameters were exactly the same. What happens if we switch the order of htmlEncode and addTax, which have different parameters? There's one way to find out. Here's what our new binder language source would look like:
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('MCPSRVPGM')
EXPORT SYMBOL('htmlDecode')
EXPORT SYMBOL('addTax')
EXPORT SYMBOL('htmlEncode')
ENDPGMEXP
With our new binder language source, we happily recompile our service program but still do not recompile our main RPG program. We call our main program and you guessed it: Kaboom!
In a case like this, a kaboom is a good thing. Can you imagine if a call to the wrong procedure for calculating tax when printing invoices went undetected? A program blow-up would be much more appreciated than an uncaught problem with billing.
In the following figure, you can see the error. I also put the program into debug with a breakpoint inside of the addTax procedure so that you can see the garbage that was passed in.
Figure 1: This is the execution error and the value of the bad parameter in the debugger. (Click images to enlarge.)
My point here is that, even though the procedure names and parameters are completely different, the main program will still try to use it without complaints…until the program blows up.
Assigning a New Signature
I strongly recommend that you do not change the order of the procedures, but if you ever do, then the solution to this problem would be to change the signature on your service program and recompile all of your programs. This will ensure that you are using the correct code.
STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('MCPSRVPGM16NOV11')
EXPORT SYMBOL('htmlDecode')
EXPORT SYMBOL('addTax')
EXPORT SYMBOL('htmlEncode')
ENDPGMEXP
Adding the date to the signature is not a bad idea to ensure that you have a unique signature and also to record the last time the signature changed. The signature can be 16 characters in any format you prefer, so I indulged in some Navy nostalgia with the old military date format.
Now if you recompile the service program, and still not recompile the main program, you'll get the old, familiar signature violation error that we've been looking for.
More Options
For such a small language, there sure is a lot to talk about, and I didn't even cover it all here. There are also ways to have the keys automatically generated based on the PGMLVL of *CURRENT and *PRV. You can also support multiple signatures at the same time, which your shop may be interested in if that kind of support is required. To reduce the complexity of maintenance, in my experience, I normally wouldn't support more than one signature at a time, but those capabilities are there if you need them.
Signature Violations Exist for a Reason
Signature violations are not a bad thing and exist for a good reason. If you were to change the order of your procedures or some attributes to your parameter list, then you will want to associate a new signature to the service program. Otherwise, your procedure could be called using the parameters incorrectly, producing unpredictable results.
Coming Soon
This was an extension to the second article in an intended three-part series on using service programs. In my next article, I'll close the series with a discussion on binding directories.
References
"How to Create, Compile, and Use Service Programs"
"Using Binding Language with Your Service Programs"
LATEST COMMENTS
MC Press Online