The cycle is still a part of RPG, and while you are not using it in your RPG programs it is still there lurking. I have always wished there was a way to be able to turn it off, and allow RPG to be linear. In recent releases this has started to happen.
With the file definitions in all free RPG it is not possible to code a file as primary or secondary, or to define matching records and level break indicators. But the cycle is still there. For example, the program below uses the cycle to read all the records from a file:
dcl-f TESTFILE keyed ; read TESTFILER ; if (%eof) ; *inlr = *on ; dsply 'End of file' ; else ; dsply FLD001 ; endif ; |
There are four records in TESTFILE, therefore, the output produced looks like:
DSPLY 1 DSPLY 2 DSPLY 3 DSPLY 4 DSPLY End of file |
So while you would expect my program of nine lines to be very small, it is not as all of the RPG cycle stuff is included too. If I could create linear programs I could create smaller program objects.
IBM i 6.1 gave us the MAIN and NOMAIN keywords for the control options/H-specs. When the object is compiled, and one of these keywords is present, none of the RPG cycle functionality is included within it.
NOMAIN should be placed at the top of the source member for external subprocedures, as when it is compiled the module produced does not contain a Program Entry Procedure, PEP, which means it is not a callable program.
ctl-opt nomain ; /define GetEmployeeName /include devsrc,testrpg_c /undefine GetEmployeeName dcl-proc GETEMPLOYEENAME export ; dcl-pi *n char(85) ; EmplNbr char(10) options(*nopass) value ; end-pi; end-proc ; |
If you are interested in learning more about coding subprocedures read the post More about subprocedures.
Coding a MAIN procedure is pretty much the same as coding any other procedure. It has a Procedure prototype (DCL-PR), Procedure interface (DCL-PI), and the Procedure (DCL-PROC) itself:
What I feel is the biggest limitation of using a MAIN is that it is not possible to return a value to the calling program.
01 ctl-opt main(Main) ; 02 dcl-pr Main extpgm('TESTRPG') ; 03 *n char(1) ; 04 end-pr ; 05 dcl-proc Main ; 06 dcl-pi *n ; 07 PassedKey char(1) ; 08 end-pi ; 09 dcl-f TESTFILE keyed ; 10 dcl-ds TestData likerec(TESTFILER) ; 11 chain Passedkey TESTFILER TestData ; 12 if (%found) ; 13 dsply TestData.FLD003 ; 14 else ; 15 dsply 'Key not found' ; 16 endif ; 17 end-proc ; |
The first thing to notice is the control option, line 1, MAIN followed by the name I am going to call the procedure Main.
When defining the Procedure prototype I must use the EXTPGM keyword, line 2, to contain the name of the program that this procedure is contained within, TESTRPG. I will be passing a one character parameter to the procedure, line 3, which I am not naming here. And the prototype ends on line 4.
The start of the Procedure is marked by the DCL-PROC, line 5. I have not bothered to give the name of the procedure in the Procedure interface, line 6, so I have to need to have *N there to indicate that I am not. On line 7 I have given the passed parameter a name, and the interface ends on line 8.
Line 9 is a file definition. If a file is defined within a procedure I have to read the file into a data structure. On line 10 I define the data structure using the LIKEREC keyword. This defines all the fields in the record format, TESTFILER, to be subfields in the data structure.
Line 11 shows the CHAIN operation using the passed parameter as the key. The retrieved record is moved into the data structure, which is given after the record format or file name. As this is only one move from the input buffer this is faster than moving the individual fields from the input buffer, although with many files the difference is negligible.
All of the subfields in the data structure automatically qualified, see line 13.
The procedure ends on line 17. Notice how there is no RETURN operation code and no *INLR.
When compiled this program will contain no RPG cycle.
The fixed format definition equivalent of this program would look like:
01 H main(Main) 02 D Main PR extpgm('TESTRPG') 03 D 1 04 P Main B 05 FTESTFILE IF E K DISK 06 D PI 07 D PassedKey 1 08 D TestData DS likerec(TESTFILER) /free 09 chain Passedkey TESTFILER TestData ; 10 if (%found) ; 11 dsply TestData.FLD003 ; 12 else ; 13 dsply 'Key not found' ; 14 endif ; /end-free 15 P E |
If you want to learn more about all free RPG you should check out the following posts:
- File definition in RPG all free
- Defining variables in RPG all free
- Defining Procedures in RPG all free
- Mixing it up with RPG all free
You can learn more about these on the IBM website:
This article was written for IBM i 7.2, and it should work with 6.1 and greater too.
I don't understand the complaint "What I feel is the biggest limitation of using a MAIN is that it is not possible to return a value to the calling program."
ReplyDeleteIf you feel that's important - don't use a program - use a service program or include the module with your program and simply use procedures. For that to be a "limitation" is, IMO, silly. It's not a limitation because there are mechanisms that allow you do exactly what you to do just that.
If you want it to be a self contained program and still return a value you can do it via parameters - just like you would with ANY program from V1 to V7.2.
Thanks for letting us know about an H-specification that help limit us using the RPG cycle in our programs.
ReplyDeleteOne other note - cleanup is now on the programmer - for example the files have to be specifically opened and closed. If the program dies before you close the files for some reason, it will generate an error message indicating the file is already opened.
ReplyDeleteIf you define the files within the procedure they are used you do not have to open or close them. The program still does that for you.
DeleteIf you get an error, for example writing a duplicate key record to the file, the file is still closed.
If the file is not defined in the procedure but at the "top" of the program, like you would have to do in IBM i 6.1, then yes you do have to open and close the file, and the file can be left open after an error.
What about global variables in programs defined with MAIN? And the impact of Activation Groups?
ReplyDeleteI've been experimenting with MAIN more and more. I try to limit global variables - but do occasionally use them. I've noticed that they retain their value when the program is called again.
This makes me think that I need a better understanding of activation groups.
To future proof your code, should add (n) no-lock to the chain. If another developer changes your F-spec to update, will needlessly be getting a record lock which could cause issue for other jobs. Yes the compiled listing will have a warning but I just ignore it.
ReplyDeleteRinger