I am trying to understand how a service program helps to reduce re-compilation effort. I read articles saying that unlike a module that is "bound by copy" into a program object, a service program is "bound by reference". Is this the reason why programs using the service program does not require re-compilation for changes to reflect?
If the program only has a reference to the service program, isnt it kind of a dynamic binding??
Also, what is the thing with a service program signature. When does it really change?
You have several questions here, so let me look at binding. There are two different concepts here: bind by copy vs. bind by reference, and static vs. dynamic binding. The first only deals with where the code resides. Is it in the program (bind by copy), or somewhere external to the program itself (bind by reference). In both cases the call jumps to some other location to execute the code, so there is a reference or pointer being used in both cases, but in the bind by copy, the code is actually copied into the program object as well. The advantage of bind by reference as you opined, is that in the bind by reference scenario, the service program can be modified without recompiling all of the callers, and the new code immediately becomes available to any program that makes use of the service program.
Next, the difference between static and dynamic binding. According to computer science definitions, static binding happens at compile time, while dynamic binding happens at runtime. There is always a reference with both types. The question is when is that reference resolved. That said, procedure calls are static, and program calls are dynamic. Let's start with the basics:
**free
ctl-opt Main(testcallp);
dcl-pr Prog ExtPgm('PGMA');
parm1 Char(10) const;
parm2 Char(10) const;
end-pr;
dcl-proc testcallp;
callp Prog('1': '2');
end-proc;
This is a dynamic call. PGMA is not resolved at compile time, but at run time when the callp
is executed. It may look like PGMA is resolved statically because it is only resolved once, but in fact, PGMA doesn't even have to exist when this code is compiled. Try it, the code will compile into a program object, but when that program is executed it can't find PGMA. So, dynamic binding must be happening.
Let's try it again with a procedure call:
**free
ctl-opt DftActGrp(*No) ActGrp(*New)
Main(testcallp);
dcl-pr Proc ExtProc('PROCA');
parm1 Char(10) const;
parm2 Char(10) const;
end-pr;
dcl-proc testcallp;
callp Proc('1': '2');
end-proc;
This time the code compiles without syntax errors, but no program object is created because it failes on the bind step. The procedure PGMA cannot be found. Static binding. But wait, the RPG docs say you can call a procedure dynamically by using procedure pointers.
Let's try it:
**free
ctl-opt DftActGrp(*No) ActGrp(*New)
Main(testcallp);
dcl-pr Proc ExtProc(Procp);
parm1 Char(10) const;
parm2 Char(10) const;
end-pr;
dcl-pr Proca ExtProc('PROCA');
parm1 Char(10) const;
parm2 Char(10) const;
end-pr;
dcl-s Procp Pointer(*proc) Inz(%paddr(Proca));
dcl-proc testcallp;
callp Proc('1': '2');
end-proc;
Once again the program compiles with no syntax errors, but gets hung up in the binding step because the procedure PROCA does not exist. Static binding. Even if we can change the actual procedure called at run time by swapping out the procedure that Procp points to, that procedure must exist at compile time because all of the procedures are statically bound into the program at compile time.
As for the binder source recommendations, I would only add a single recommendation to Barbra's answer, and that is to always add modules to service programs, and only reference service programs from program objects.
Note: There are API's that will let you resolve a procedure and bind it at run time, but those are well beyond the scope of this post.