> A single hard coded signature works just as well for backward
> compatibility as long as you don't mess with the procedure sequence.
Just to add to that (because I think this is a common point of confusion) *CURRENT/*PRV _also_
requires that you don't mess with procedure sequence.
If you make a mistake with your procedure sequence with _either_ a hard-coded signature _or_
*CURRENT/*PRV the ILE environment won't catch it.
I'm sure you already know how this works, but I suspect there are others that do not,so I'm
going to give a more detailed example:
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(finishSomething)
ENDPGM
The important thing to understand is that ILE calls things by NUMBER. It looks at the above set
of procedures, and assigns a number to each (based on the order they are listed in the binder
source). So startSomething=1, doSoemthing=2, finishSomething=3. Under the covers, your programs
will be calling them by number... call export#1, call export#2, etc.
As a side note: The sequence of the procedures in your RPG source member doesn't matter AT ALL.
The export numbers are calculated purely from the binder source (above), not from the source member.
Now you decide to add a new procedure, and WHOOPS, you add it in the middle instead of the end:
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(doSomethingMore) <--- WHOOPS! it's in the middle.
EXPORT SYMBOL(finishSomething)
ENDPGM
STRPGMEXP PGMLVL(*PRV)
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(finishSomething)
ENDPGM
IMHO, if ILE were designed correctly, the CRTSRVPGM command would detect that finishSomething
has changed from position #4 to position #3 and would warn you. But it doesn't. Existing
programs that previously called finishSomething were doing "call export#3". And unless you run
UPDPGM or UPDSRVPGM or recreate them completely, they will continue to do "call export#3" --
the problem is, export#3 is no longer the "finishSomething" routine, now it's "doSomethingMore"
and you're calling it by mistake! WHOOPS!
You're probably thinking "shouldn't I get a signature violation error?" no. you won't, because
of the *PRV group. The *PRV group will have the signature you were previously using... so it
will let you call it without error. However, *only* the *CURRENT group is used to determine
the export numbers. So the fact that finishSomething *used* to be #3 doesn't matter, the system
won't detect that it has changed, and it'll let you keep calling #3.
So using *CURRENT/*PRV didn't protect you. I'm still wondering under what situation using
*CURRENT/*PRV *would* protect you? I haven't found any situation where it would.
The *ONLY* way for this to work properly is to do this:
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(finishSomething)
EXPORT SYMBOL(doSomethingMore) <--- HURRAY! it's on the end.
ENDPGM
STRPGMEXP PGMLVL(*PRV)
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(finishSomething)
ENDPGM
This way, your existing exports aren't renumbered, finishSomething is still export#3. As long as
you know this and do it consistently, this will give you backward compatibility. But if you mess
it up, it won't catch it. So you're doing the (admittedly minor) work of copying the signature
block, and getting no protection for your efforts.
My problem is that after I've done this 500 or so times (I'm not exaggerating -- I really do this
500+ times in some srvpgms) you end up with a large, unwieldy source member. That is the part that
"drives me crazy." That's perhaps too strong of a term -- but it was deterring me from adding more
subprocedures. It didn't make sense to "add just one more procedure" because I'd have to copy the
block, so I'd end up putting it off until I had lots of them to add so I could do it all at once
and have fewer signature blocks. This wasn't good because it was holding me back.
Let's look at a hard-coded signature (my recommendation):
Back to the original example, before the change you have the following, the only difference now is
that I've added a hard-coded signature:
STRPGMEXP SIGNATURE('SRVPGM SIG 1.0 ')
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(finishSomething)
ENDPGM
(I'm careful to make my signature 16 chars long, otherwise I get annoying warning messages when I
build my srvpgm. It's not required -- I just do it to avoid the extra warning messages.
If, WHOOPS, I make a mistake (just as I did with *CURRENT/*PRV) and put it in the middle, I still
have the same problem
STRPGMEXP SIGNATURE('SRVPGM SIG 1.0 ')
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(doSomethingMore) <--- WHOOPS! it's in the middle.
EXPORT SYMBOL(finishSomething)
ENDPGM
This creates the exact same problem it did with *CURRENT/*PRV, and exactly like *CURRENT/*PRV
the system won't detect it. So I haven't lost anything by switching to a hard coded signature.
The only thing I've gained is that I don't have to copy/paste my signature block.
Just as with the *CURRENT/*PRV method, if I want to do it correctly, I have to put it on the end:
STRPGMEXP SIGNATURE('SRVPGM SIG 1.0 ')
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(finishSomething)
EXPORT SYMBOL(doSomethingMore) <--- HURRAY! it's on the end.
ENDPGM
Now i've achieved compatibility. My existing callers will work. I don't have to run UPDSRVPGM or
UPDPGM, since the export numbers didn't change, and the signature didn't change. Complete backward
compatibility. Exactly the same as the *CURRENT/*PRV solution, except that I don't have to copy the
signature block.
Now what happens if I *want* to break compatibility? Let's say I'm making a change to my service
program that I know won't be compatible, and I want make sure that every caller gets recreated.
If it doesn't get recreated, I *want* it to fail with a signature error.
With *CURRENT/*PRV, you'd do that by deleting all of the *PRV blocks. Simply eliminate every single
*PRV block so all you have is a *CURRENT block.
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(doSomethingMore)
EXPORT SYMBOL(finishSomething)
ENDPGM
Now it doesn't matter that I put doSomethingMore in the middle, because all callers are going to
be rebuilt anyway. Again, the system didn't *detect* this for me, I had to TELL IT that I wanted
to break compatibility by deleting the *PRV blocks. IMHO, that's not a very intuitive way of
telling the system I want to force callers to re-bind -- but it does work.
To do the same thing with a hard-coded signature, I just change the version number in the signature.
STRPGMEXP SIGNATURE('SRVPGM SIG 1.0a ')
EXPORT SYMBOL(startSomething)
EXPORT SYMBOL(doSomething)
EXPORT SYMBOL(doSomethingMore)
EXPORT SYMBOL(finishSomething)
ENDPGM
That's (very slightly) simpler than deleting all of the *PRV blocks, and achieves the same thing.
But to me, changing a version number is more intuitive... I'm not sure it would've occurred to me
to delete all of the *PRV blocks if someone hadn't told me -- but changing the version I might've
thought of on my own. Also, when you use DSPPGM or DSPSRVPGM on the caller, you'll be able to see
exactly which version it's using in a human-readable string.
Ugh.. I've turned this e-mail into an article. Sorry for the length.
> Does anyone have any suggested added benefits to the *CURRENT/*PRV
> technique that would suggest that we would be better to stick with it?
I've had many discussions online about this topic, and I've asked the same question that you're
asking, Kevin. "Anyone know any advantages to *CURRENT/*PRV." I've received many responses --
unfortunately, all but one of them were based on a misconception of the protection *CURRENT/*PRV
provides.
The one that did make sense pointed out something you already mentioned: you can see a history in
your source member. There's at least one shop out there that uses this for documentation purposes.
("This program program requires version 1.4 of product XYZ or later." "I know that because it's
calling procedure ABC that was added in the 6th signature block")
But, as far as compatibility or protection against mistakes, *CURRENT/*PRV doesn't provide any.
(Though, *CURRENT by itself does...)
Another point: If you look at all of the IBM srvpgms, you'll see that they hard-code all of their
signatures. (I don't know if that means it's a good thing or a bad thing... heh)
Thanks to Scott Klement
|