Having discussed how to define files and variables/fields in the new RPG all free, in this post I am going to give examples of how I have code Procedures using the new definitions.
I am going to assume that you know what Procedures and Subprocedures are. If you do not, no worries, you can read IBM’s definition in the RPG manual here.
The RPG all free brings us the following changes to the way we code Procedures and Subprocedures:
- Procedure specification replaced by DCL-PROC and END-PROC.
- Procedure prototype, coded in the Definition specification (D-spec) with ‘PR" in the declaration types, replaced by DCL-PR and END-PR.
- Procedure interface, coded in the D-spec with “PI" in the declaration type, replaced by DCL-PI and END-PI.
- Procedure parameter, coded in the D-spec, replaced by the optional DCL-PARM.
Below I am going to give the three most common ways I use Procedures and Subprocedures. They are many other ways they can be used, but I don’t use them or use them infrequently.
Use a Procedure to call a program
Below is an example of how I would call the program QCMDEXC using the fixed format definition, lines 1-4, and the same in free format, lines 5-8:
* Fixed format definition 01 D QCmdExc PR extpgm('QCMDEXC') 02 D 1000 options(*varsize) const 03 D 15 5 const 04 D Cmd S 1000 //Free format definition 05 dcl-pr qCmdExc extpgm ; 06 *n char(1000) options(*varsize) const ; 07 *n packed(15:5) const ; 08 end-pr ; 09 dcl-s Cmd char(1000) ; // This is the same in either way 10 Cmd = 'CLRPFM TESTPF' ; 11 qCmdExc(Cmd:%len(Cmd)) ; |
The first thing to note is that the Procedure prototype starts with DCL-PR, line 5, and ends with END-PR, line 8. At the start of the prototype I have to give its name, in this example I am using the same name as the program I am going to call, QCMDEXC. As I have used the name of the program the EXTPGM parameter does not have to contain the name of the program. If I had decided to use a different name I would have to give the program name in the EXTPGM.
I have not named the two parameter being passed to QCMDEXC, therefore, I have to code the parameters with a *N to indicate that I am not going to give a name for it. I am also not using the DCL-PARM as it is assumed.
If I had decided to give the prototype a name different from the program’s name, name the parameters, and use the DCL-PARM the same thing would look like:
01 dcl-pr ExecuteCommand extpgm('QCMDEXC') ; 02 dcl-parm Command char(1000) options(*varsize) const ; 03 dcl-parm CommandLength packed(15:5) const ; 04 end-pr ; |
I prefer the simpler looking code I gave in the first example as, in my opinion, it is easier to understand.
Subprocedure within a program
In this next example I am using a Subprocedure like a subroutine, to validate a date. The old way would look like below. Notice how I am having to /FREE and /END-FREE more than once to accommodate the Procedure specification (P-spec).
01 H dftactgrp(*no) 02 D ValidateDate PR 03 D wkAlpha S 7 04 D wkError S N /free 05 wkAlpha = '1140214' ; 06 ValidateDate() ; . . /end-free 10 P ValidateDate B /free 11 test(de) *cymd0 wkAlpha ; 12 if (%error) ; 13 wkError = *on ; 14 else ; 15 wkError = *off ; 16 endif ; /end-free 17 P ValidateDate E |
Below is the equivalent with free definitions. The Procedure specification is replaced by the DCL-PROC and END-PROC, as there is no /FREE or /END-FREE more compact and so much easier on the eye.
01 ctl-opt dftactgrp(*no) ; 02 dcl-s wkAlpha char(7) ; 03 dcl-s wkError ind ; 04 wkAlpha = '1140214' ; 05 ValidateDate() ; . . 10 dcl-proc ValidateDate ; 11 test(de) *cymd0 wkAlpha 12 if (%error) ; 13 wkError = *on ; 14 else ; 15 wkError = *off ; 16 endif ; 17 end-proc ; |
Calling a Procedure in a Module
In this example I am going to execute a procedure, ValLineNbr, to validate that the alphanumeric line number is numeric and if the field is numeric return the line number as a number. For an explanation of how I convert alphanumeric to numeric read Validating numbers without TESTN.
This example has three parts:
- The main program that calls the procedure.
- The Procedure interface that is in a “prototype copy book" member.
- The Procedure itself.
Below is the fixed format version of the relevant parts of the main program:
01 H dfactgrp(*no) actgrp(*new) bnddir('mybnddir') 02 /define ValLineNbr 03 /include mylib/mysrc,prototype 04 /undefine ValLineNbr 05 LineNbrDs = ValLineNbr(i_LINE_NBR) ; 06 if (LineNbrDs.ErrCde = ' ') ; 07 LINE_NBR = LineNbrDs.LineNbr ; 08 else ; 09 wkErrCde = LineNbrDs.ErrCde ; 10 wkInternal = LineNbrDs.Internal ; 11 exsr SrErrors ; 12 endif ; |
And below is the free format version. Notice that there is no need for DFTACTGRP(*NO) in the Control option, line 1, as it is assumed if BNDDIR is present. The “slash define" and “slash copy" lines, 2-4, no longer need to start in column 7.
01 ctl-opt option(*srcstmt:*nodebugio) bnddir('mybnddir') ; 02 /define ValLineNbr 03 /copy mylib/mysrc,prototype 04 /undefine ValLineNbr 05 LineNbrDs = ValLineNbr(i_LINE_NBR) ; 06 if (LineNbrDs.ErrCde = ' ') ; 07 LINE_NBR = LineNbrDs.LineNbr ; 08 else ; 09 wkErrCde = LineNbrDs.ErrCde ; 10 wkInternal = LineNbrDs.Internal ; 11 exsr SrErrors ; 12 endif ; |
The Procedure prototype, PR, and the data structure are kept in a “prototype copy book" member, separate member from the main program and the procedure. This is what it looks like in fixed format:
/if defined(ValLineNbr) 01 D ValLineNbr PR 8 02 D 7 options(*nopass) value 03 D LineNbrDs DS qualified 04 D LineNbr 7P 0 05 D ErrCde 3 06 D Internal 1 07 /endif |
In free format the Procedure prototype starts with DCL-PR and ends with END-PR. For more details about how to define a data structure see here.
/if defined(ValLineNbr) 01 dcl-pr ValLineNbr char(8) ; 02 *n char(7) options(*nopass) value ; 03 end-pr ; 04 dcl-ds LineNbrDs qualified ; 05 LineNbr packed(7:0) ; 06 ErrCde char(3) ; 07 Internal char(1) ; 08 end-ds ; /endif |
And now for the Procedure itself. The fixed format version has the starting Procedure specification (P-spec), line 5, and ending P-spec, line 19. The Procedure interface is coded on lines 6 and 7.
01 H nomain 02 /define ValLineNbr 03 /include mylib/mysrc,prototype 04 /undefine ValLineNbr 05 P ValLineNbr B export 06 D ValLineNbr PI 8 07 D inLineNbr 7 options(*nopass) value /free 08 LineNbrDs.LineNbr = 0 ; 09 LineNbrDs.ErrCde = ' ' ; 10 LineNbrDs.Internal = ' ' ; 11 inLineNbr = %xlate(',':' ':inLineNbr) ; 12 13 monitor ; 14 LineNbrDs.LineNbr = %dec(inLineNbr:7:0) ; 15 on-error ; 16 LineNbrDs.ErrCde = '000' ; 17 endmon ; 18 return LineNbrDs ; /end-free 19 P E |
Free format replaces the starting P-spec with DCL-PR, line 5, and ending P-spec with END-PR, line 19. The Procedure interface is replaced by the DCL-PI and END-PI, line 6-7. And there is no /FREE and /END-FREE to clutter the code.
01 ctl-opt option(*srcstmt:*nodebugio) nomain ; 02 /define ValLineNbr 03 /copy mylib/mysrc,prototype 04 /undefine ValLineNbr 05 dcl-proc ValLineNbr export ; 06 dcl-pi *n char(8) ; 07 inLineNbr char(7) options(*nopass) value ; 08 end-pi ; 09 LineNbrDs.LineNbr = 0 ; 10 LineNbrDs.ErrCde = ' ' ; 11 LineNbrDs.Internal = ' ' ; 12 inLineNbr = %xlate(',':' ':inLineNbr) ; 13 monitor ; 14 LineNbrDs.LineNbr = %dec(inLineNbr:7:0) ; 15 on-error ; 16 LineNbrDs.ErrCde = '000' ; 17 endmon ; 18 return LineNbrDs ; 19 end-proc ; |
As I said before there are many other ways to code and use Procedures. If you have a method you would like to share please do not post it in the comments, as the formatting in that section will make it look strange. Contact me through the Contact Form, on the right, and I will send you an email with my address. If you send me your example in an email I will include it method in a new post.
You should read my other posts introducing all free RPG:
I mostly use procedures instead of subroutines now. Some of the real power of this method is to use it in conjunction with RPG modules. Modules allow you to create multiple use objects similar to copybooks without the overhead.
ReplyDeleteI think this great that IBM finally made free form for the "D" specs.
ReplyDeleteAlthough, I don't know when I will be able to experiment with the code as I have been out of work for a long time...........hardly anybody has been hiring in the Iseries world.
I am in the Chicago area where the companies exist---but work is scarce.
That's a great information to know, helpfull as always...
ReplyDeletein using /free C specs, I discovered it looks a lot better if you line it up a bit. Line up the = signs for the EVAL assignments, for example. Just as it is in CL. /Free means you can make it look like a mess, but it also means you don't have to.
ReplyDeleteThe code here is not particularly inviting, and I think that is because it is not arranged at all. It is actually much prettier and easier to read when the field descriptions line up, and I don't think this is due to strangeness. I get the same reaction from users on screens; they look better lined up.
I totally agree.
DeleteI also do programming work in Clarion9 for Windows and always try to make it look "inviting" to read, for the people who will bethere when I'm not.
So go back and use fixed format then! Problem solved!
DeleteWhining about how code is formatted when you get a lot of free tips is just so insulting! Grow up!
I disagree with "Anonymous". It is very possible to be appreciative of the advice while still offering additional ideas that are useful to other like minded people.
DeleteI am very grateful to Simon Hutchinson and others like him, but I also very much appreciate the comments from Lyne Noll and bolle1502 (if for no other reason, than the awareness that there are other people out there that value visual structure within the free environment).
Forums like this provide a free exchange of ideas, so those of Anonymous are also welcome. I just wanted to chime in, in hopes of encouraging more and not less open communication here.
Good day Mr. Simon. I am from Mexico and I want to make you a suggestion about how call a external command. I prefer to use GoCMD instead of QCMDEXC. Here is how to implement it:
ReplyDeleteD GoCmd PR 10I 0 ExtProc('system')
D cmdString * Value
D Options(*String)
* Procedure GoCmd Variants.
Dsuccess C 0
DreturnCode S 10I 0
DcmdString S 600
/free
cmdString = 'CHKOBJ OBJ(QSYS/' + %Trim(&BIB) +
') OBJTYPE(*LIB) AUT(*ALL)';
returnCode = GoCmd(cmdString);
If returnCode <> success;
*IN41 = *On;
EndIf;
/End-If
Apart of that, I was really surprising when I saw how to do a complete Free Style Program, specially in how declare pages F and D. However, I have a doubt, which release is necessary to implementing this kind of code?
I will appreciate if you could answer to me.
I going to follow your blog, that is very interesting.
Thanks for your lessons and advises.
I did discuss using the SYSTEM() API in a previous post, which you can read here.
DeleteI personally prefer to use QCMDEXC, it is not that it is better, it is just that is what I prefer. There is nothing wrong with the SYSTEM API.
As for what releases and PTFs do you need for the free form definitions I am going to do is to refer you to another post that gives the evolution of the free format and what the releases and TRs are, see here.
i had trouble creating a module and calling it to Program. Thank you Simon. Your posts improves my RPGLE skills. Bless you - J
ReplyDeleteIf a procedure does not return any thing and does not take any input. How to declare that in the program ?
ReplyDeletedo we require dcl-pr for this or can i just go ahead with dcl-proc and end-proc ?
Even if there are no parms being passed you still need a DCL-PR and DCL-PI.
DeleteI tried using a procedure without DCL-PR and DCL-PI it works :)
DeleteIt can now.
DeleteHi there... just a question.
ReplyDeleteIf I have this:
C *Entry Plist
C Parm DsDataStr
C Parm Var1
Where DsDataStr is a data structure, how can I declare this two entry parametros to convert the code to rpg all free?
Thanks a lot.
I don't think you can do.
DeleteI have always defined the "DS" as a variable in the DCL-PI, and then moved that to a DS.
If you find a way to do it please let me know.