|
Thank you Simon. Your explanation cleared up a lot of confusion for me. There are still many questions in my mind but a lot of what has seemed redundant to me is now washed away. How does one keep track of all these functions and keep them indexed and available so the consumer/programmer can quickly find them? What schema has proven effective? Are there recommended groupings? Is there a hierarchy that seems to have worked well? --------------------------------------------------------- Booth Martin http://www.MartinVT.com Booth@xxxxxxxxxxxx --------------------------------------------------------- -------Original Message------- From: RPG programming on the AS400 / iSeries Date: Monday, October 27, 2003 2:11:15 AM To: RPG programming on the AS400 / iSeries Subject: Re: Starting out with sub-procedures On Monday, October 27, 2003, at 01:30 PM, Booth Martin wrote: > The question remains: do I need more than a /COPY statement and the > callp > line in the main program(s)? > For a prototyped call to a *PGM object that is all you need to do. Include the prototype and call the name of the prototype. Thus: /define LONG_PROC_NAMES /COPY rpgleinc,qcmdexc C CALLP QcaRunClCmd( 'WRKSPLF' : 7 ) For a prototyped procedure call the only other thing you need to know is where to find the procedure because you must bind your program to the module or service program that contains the procedure. Some documentation explaining limitations, side effects, valid data ranges, etc. is helpful but only needed for complex procedures. That sort of information should be in the /COPY member containing the prototypes as part of the comment block for each prototype. The consumer includes the appropriate /COPY member, invokes the procedure, and binds to the service program or module. Thus: H BNDDIR('QC2LE') /COPY rpgleinc,stdlib C EVAL rc = system( 'WRKSPLF' ) > My primary interest is in avoiding knowing any more about a procedure > than > the function and the parm(s). I have a strong desire to NOT know who is > maintaining the code, etc. I don't really even want to look inside the > code. > It is none of my business. None whatsoever. That is the whole point of procedures (at least those procedures intended for general consumption via the public interface of a service program). All that the consumer of the function needs to know should be apparent from the prototype. For example, call function A passing X, Y, and Z and receive R as a return value. The consumer does not, and should not, care how the procedure does its stuff--only that it does. Procedures used in this way are black-box building blocks from which a more complex application can be built. Private procedures (i.e., those that exist within a program or those that are not part of the public interface of a service program) serve the same purpose but it can be argued that subroutines also satisfy this requirement. Procedures have additional benefits when compared to subroutines: 1) Support for parameters (or arguments depending on your perspective) 2) Locally scoped variables 3) Return values These benefits become apparent only when you engineer software rather than simply cobbling code together and thus are often apparent only programmers who appreciate the craft of programming. I would venture that many RPG programmers do not and programming for them is simply a means to an end. It pays them more money than they could earn doing anything else and that keeps a roof over their head and food on their table. By defining a formal interface to a routine I can ensure that the only way data gets into the procedure is via the interface--no side effects. By using locally scoped variables I can ensure that my procedure does not inadvertently change the value of a variable used elsewhere in the program (global values notwithstanding)--no side effects. By using return values I can make my code more elegant. For example, EVAL R = functionA( X, Y, Z ) is cleaner and easier to read than CALLP functionA( R, X, Y, Z) With the first form I **KNOW** R is changed by the function. As a result I can presume X, Y, and Z are input values and thus probably not changed by the function. The prototype will tell me for certain. With the second form I don't know whether any of the values are changed nor whether all of them are changed. Perhaps the convention is that the first value is the return value but perhaps not. I would use the first form when a function returns a single value and the second form when the function must return multiple discrete values. In general I would prefer to return multiple value in a data structure even though returning large character values is less efficient. This allows me to define procedures with a single return type even if that return type is a composite structure. Perhaps a worked example is in order? Presume I want to write a function that returns an uppercase version of an input string. Presume also that I do not want to impose artificial constraints on the size of the string the function will handle. Since RPG IV imposes a limit on the maximum size of a character string (32K prior to 510 and 64K from 510 on) I design my function to accept the address of an input value and the address of an output location. Since I do not want to impose length restrictions I must also include variables for the length of the data. Since this function returns the same amount of data it received (just uppercased) we can require that the input length and output lengths are the same. Here is one such prototype: D toUpperCase PR D inString * VALUE D outString * VALUE D stringLen 10I 0 VALUE The function (or procedure) that implements the prototype would look like: P toUpperCase B EXPORT D toUpperCase PI D inString * VALUE D outString * VALUE D stringLen 10I 0 VALUE * Uppercasing stuff goes here P toUpperCase E Given: D name S 10 INZ('booth') D upperName S 10 The caller would code: CALLP toUpperCase( %ADDR(name) : %ADDR(upperName) : %LEN(%TRIM(name)) ) Now this prototype is ugly from an RPG IV perspective. It forces the caller to deal with pointers and determining the length of large character variables using %TRIM is inefficient. So I create a wrapper for this function that handles the length aspect automatically and allows the caller to specify variable names rather than addresses. First I rename the ugly function (using a suffix of _p for pointer). D toUpperCase_p... D PR D inString * VALUE D outString * VALUE D stringLen 10I 0 VALUE and I define a new uppercase function: D toUpperCase PR OPDESC D inString 32767 D outString 32767 The function that implements this prototype would look like: P toUpperCase B EXPORT D toUpperCase PI OPDESC D inString 32767 OPTIONS(*VARSIZE) D outString 32767 OPTIONS(*VARSIZE) D stringLen S 10I 0 * Call IBM API to determine the string lengths from the operational descriptors * Complain if the lengths are not the same or just use the shorter length * Convert the input string to uppercase C CALLP toUpperCase_p( %ADDR(inString) : %ADDR(outString) : C stringLen ) P toUpperCase E See how we provided a cleaner interface but still reused the _p version for the implementation. The implementation uses the ugly interface but the consumer gets to use a cleaner interface. Now the caller can code: CALLP toUpperCase( name : upperName ) which is much nicer than the first version but it is not really apparent which variables are input to the function and which are output. The variable names help but we can improve the interface further by changing the prototype to: D toUpperCase PR 32676 OPDESC D inString 32767 OPTIONS(*VARSIZE) The function that implements this prototype would look like: P toUpperCase B EXPORT D toUpperCase PR 32676 OPDESC D inString 32767 OPTIONS(*VARSIZE) D stringLen S 10I 0 * Call IBM API to determine the string length from the operational descriptor * Convert the input string to uppercase C CALLP toUpperCase_p( %ADDR(inString) : %ADDR(outString) : C stringLen ) C RETURN %SUBST(outString : 1 : stringLen) P toUpperCase E allowing the caller to code: EVAL upperName = toUpperCase( name ) which is the nicest way possible. It is blindingly obvious what is input to the function and what is output. It does have the minor disadvantage of being less efficient than the previous versions due to it causing 32K to be copied on to the stack and then copied into the receiving variable. I would rather pay the minor performance cost and have nice looking code. If I really find the performance an issue then I can always call the _p version directly. This process can be continued with number of variants. For example: D toUpperCase_v... D PR 32767 VARYING D inString 32767 VARYING D toUpperCase_s... D PR 32767 D inString * VALUE OPTIONS(*STRING) The caller can either invoke these special variants directly or prototypes can be used to "hide" the real function. For example, the /COPY member containing the prototypes might look like: /if defined(USE_VARYING_STRINGS D toUpperCase PR 32767 VARYING D EXTPROC(toUpperCase_v) D inString 32767 VARYING /elseif defined(USE_C_STRINGS) D toUpperCase PR 32767 D EXTPROC(toUpperCase_s) D inString * VALUE OPTIONS(*STRING) /elseif defined(USE_POINTERS) D toUpperCase PR EXTPROC(toUpperCase_p) D inString * VALUE D outString * VALUE D stringLen 10I 0 VALUE /else D toUpperCase PR 32676 OPDESC D inString 32767 /endif Thus all callers invoke toUpperCase() but the function really invoked depends on the type of data being processed. The caller does need to know what data they will be processing in order to define the proper prototypes and they cannot use different underlying methods in the same program without performing some contortions. Obviously it would be much better if the compiler could determine which function to call based on the data type of the parameters and that leads us nicely into the requirement for overloaded procedures. In the real versions of these procedures I would allow expressions to be specified on the input values but that would just complicate things here. The binding source for the service program would export the _p, _s, _v versions in addition to the plain version. Does this help at all or are you more confused than ever? Regards, Simon Coulter. -------------------------------------------------------------------- FlyByNight Software AS/400 Technical Specialists http://www.flybynight.com.au/ Phone: +61 3 9419 0175 Mobile: +61 0411 091 400 /"\ Fax: +61 3 9419 0175 \ / X ASCII Ribbon campaign against HTML E-Mail / \ --------------------------------------------------------------------
As an Amazon Associate we earn from qualifying purchases.
This mailing list archive is Copyright 1997-2024 by midrange.com and David Gibbs as a compilation work. Use of the archive is restricted to research of a business or technical nature. Any other uses are prohibited. Full details are available on our policy page. If you have questions about this, please contact [javascript protected email address].
Operating expenses for this site are earned using the Amazon Associate program and Google Adsense.