All of the examples I can find of using procedures on the IBM i are mainly in RPG, with a few in C. This made me want to demonstrate that it is possible to create procedures in CL, and to call procedures using CL programs and procedures.
This is also a time to move from using "CLP" source members to "CLLE", as there are commands that will be used in this post that are not supported by old "CLP" source or "CLP" programs.
After thinking about this for a while I have decided to give two examples of using procedures in CL:
I am sure all of you regular readers will appreciate what the examples I am giving are simple. It is how the procedure prototypes, interfaces, etc. are coded that is important, not what happens within the procedures and programs.
In RPG the values returned from a procedure are normally placed in a data structure. I am going to do the same in CL. I am not going to describe in detail how to code these in CL as I have already done so in the post Data structures in CL.
CL program calling a procedure
I thought I would start with the simplest example: have a CL program call a RPG procedure.
In my CL source there are two commands that will be new to you:
The Declare Processing options command, DCLPROCOPT, acts like RPG's Control Options, CTL-OPT. This allows me to give the activation group information and the binding directory within the source, so I will not have to remember to enter them every time I compile the program.
The Call Bound Procedure, CALLPRC, command does as its name suggests: it is used to call procedures. It does have one parameter that the regular CALL command does not, the return value from the procedure, RTNVAL.
And now to the code:
01 PGM 02 DCLPRCOPT DFTACTGRP(*NO) ACTGRP(*NEW) + BNDDIR(*LIBL/TESTBDIR) 03 DCL VAR(&PASSED) TYPE(*CHAR) LEN(25) 04 DCL VAR(&FIRST) TYPE(*CHAR) STG(*DEFINED) + LEN(2) DEFVAR(&PASSED 1) 05 DCL VAR(&SECOND) TYPE(*DEC) STG(*DEFINED) + LEN(5 2) DEFVAR(&PASSED 3) 06 DCL VAR(&THIRD) TYPE(*CHAR) STG(*DEFINED) + LEN(15) DEFVAR(&PASSED 6) 07 CHGVAR VAR(&FIRST) VALUE('BB') 08 CALLPRC PRC('PROCTEST') PARM((&PASSED *BYVAL)) + RTNVAL(&PASSED) 09 ENDPGM |
Line 2 shows the DCLPROCOPT as I described above. It will look familiar to those you who use RPG's control options.
Lines 3 – 6 define the data structure, &PASSED, and its sub fields, &FIRST, &SECOND, and &THIRD.
Line 8 contains the call to the procedure. I am passing to it the data structure. The *BYVAL is similar to RPG's value, meaning that the value of the parameter is passed rather than a copy of it, and prevents the called procedure from changing the value.
The RPG procedure is very simple too:
01 ctl-opt nomain ; 02 dcl-pr ProcTest char(25) ; 03 *n char(25) value ; 04 end-pr ; 05 dcl-ds TestData len(25) qualified ; 06 First char(2) ; 07 Second packed(5:2) ; 08 Third char(15) ; 09 end-ds ; 10 dcl-proc ProcTest export ; 11 dcl-pi *n char(25) ; 12 InData char(25) value ; 13 end-pi ; 14 TestData = InData ; 15 TestData.Third = 'THIRD' ; 16 TestData.Second = 22.48 ; 17 return TestData ; 18 end-proc ; |
Line 1 the control option indicates that there is no main procedure in this member, and when compiled this module.
Lines 2 – 4 are my Procedure prototype.
I decided to code the definition of the data structure outside of the procedure, lines 5 - 9, just because I can. I could have just as well placed it within the procedure.
Line 10 is the start of the procedure. I have to code it as export so that the returned value is passed back to the CL program.
The procedure interface is defined between lines 11 – 13. Notice that the input parameters on line 12, and the corresponding parameter in the procedure prototype, line 3, are both coded as receiving the parameter as value, which matches how the CL program passes it.
As the input parameter is passed by value I have to move its value to my data structure, line 14, before I can update the subfields, lines 16 – 16.
I return the data structure on line 17, and end the procedure on line 18.
A fixed format version of this code can be seen at the bottom of this post here.
What of the binding directory? This is where I define that the compile is to bind the RPG module, TESTRPGMOD, that contains the procedure ProcTest, to my CL program when I create it:
Work with Binding Directory Entries Binding Directory: TESTBDIR Library: MYLIB Type options, press Enter. 1=Add 4=Remove Opt Object Type Library Activation _ __________ _______ __________ __________ _ TESTRPGMOD *MODULE MYLIB |
When the RPG module and CL program have been created I call the CL program and use debug to check the value of &PASSED at the last line of the CL program:
&PASSED = 'BB ±THIRD '
&FIRST = 'BB'
&SECOND = 022.48
&THIRD = 'THIRD '
|
The sub field &FIRST contains the value I gave it in the CL program on line 7, and was not changed in the RPG procedure.
The sub fields &SECOND and &THIRD contain the values I gave them in the RPG procedures on lines 15 and 16.
CL procedure calling a procedure
Now to give a more complicated scenario. I have a RPG program that contains two procedures. The main procedure will call a CL procedure, which in turn called the second procedure in the RPG. Sounds a bit complicated? Not really, it will become a lot clearer when I show the code.
Let me start with the code for the CL procedure. Unlike RPG there can only be one CL procedure in each source member and module. It looks almost identical to the CL source I gave above when I created the CL program. The only difference is I had to remove the DCLPROCOPT command.
01 PGM 02 DCL VAR(&PASSED) TYPE(*CHAR) LEN(25) 03 DCL VAR(&FIRST) TYPE(*CHAR) STG(*DEFINED) + LEN(2) DEFVAR(&PASSED 1) 04 DCL VAR(&SECOND) TYPE(*DEC) STG(*DEFINED) + LEN(5 2) DEFVAR(&PASSED 3) 05 DCL VAR(&THIRD) TYPE(*CHAR) STG(*DEFINED) + LEN(15) DEFVAR(&PASSED 6) 06 CHGVAR VAR(&FIRST) VALUE('BB') 07 CALLPRC PRC('PROCTEST') PARM((&PASSED *BYVAL)) + RTNVAL(&PASSED) 08 ENDPGM |
When I am ready to compile the source I need to compile it as a module. Which I can either by using option 15 in PDM or by using the CRTCLMOD command.
I then add this module to a new binding directory:
Work with Binding Directory Entries Binding Directory: TESTBDIR2 Library: MYLIB Type options, press Enter. 1=Add 4=Remove Opt Object Type Library Activation _ __________ _______ __________ __________ _ TESTCLPRC *MODULE MYLIB |
Now to the RPG, which contains two procedures: Main and ProcTest. ProcTest is identical to the standalone procedure I mentioned above. If you are unfamiliar with the purpose of the Main procedure you should read Getting off the RPG cycle.
01 ctl-opt actgrp(*new) dftactgrp(*no) main(Main) bnddir('*LIBL/TESTBDIR2') ; 02 dcl-pr Main extpgm('TESTRPG') ; 03 end-pr ; 04 dcl-pr ProcTest char(25) ; 05 *n char(25) value ; 06 end-pr ; 07 dcl-pr TESTCLPRC char(25) ; 08 *n char(25) value ; 09 end-pr ; 10 dcl-ds TestData len(25) qualified ; 11 First char(2) ; 12 Second packed(5:2) ; 13 Third char(15) ; 14 end-ds ; 15 dcl-proc Main ; 16 dcl-pi *n ; 17 end-pi ; 18 TestData.First = 'AA' ; 19 TESTCLPRC(TestData) ; 20 return ; 21 end-proc ; 22 dcl-proc ProcTest export ; 23 dcl-pi *n char(25) ; 24 InData char(25) value ; 25 end-pi ; 26 TestData = InData ; 27 TestData.Third = 'THIRD' ; 28 TestData.Second = 22.48 ; 29 return TestData ; 30 end-proc ; |
The control options on line 1 are pretty straight forward, and I don't think they need any description.
What follows are the procedure prototypes for the three procedures. Lines 2 and 3 for the Main procedure, lines 4 – 6 for ProcTest, and lines 7 – 9 for TESTCLPRC.
The data structure which is used to pass the parameters between the procedures is defined on lines 10 – 14. As this is not in a procedure it is available to both Main and ProcTest.
The procedure Main starts at line 15, and is followed by the procedure interface definition. Even if there are no parameters passed to a RPG procedure their procedure interface still must be defined.
The CL procedure is called on line 19. Notice that the procedure name is the same as the module that contains it.
Line 20 has the return that exits the Main procedure, and the end of the procedure is on line 21.
Lines 22 – 30 are the procedure ProcTest.
A fixed format version of this code can be seen at the bottom of this post here.
To execute all of this I call the program TESTRPG. If I place a debug breakpoint on the return, line 20, I can see that the data structure has been changed by both TESTCLPRC and ProcTest.
&PASSED = 'BB ±THIRD '
&FIRST = 'BB'
&SECOND = 022.48
&THIRD = 'THIRD '
|
The sub field &FIRST contains the value I gave it in the CL procedure on line 6, and was not changed in the RPG procedure.
The sub fields &SECOND and &THIRD contain the values I gave them in the ProcTest procedures on lines 27 and 28.
You can learn more about these on the IBM website:
This article was written for IBM i 7.2, and should work for earlier releases too.
Fixed format version of first RPG procedure
01 H nomain 02 D ProcTest PR 25 03 D 25 value 04 D TestData DS 25 qualified 05 D First 2 06 D Second 5P 2 07 D Third 15 08 P ProcTest B export 09 D ProcTest PI 25 10 D InData 25 value /free 11 TestData = InData ; 12 TestData.Third = 'THIRD' ; 13 TestData.Second = 22.48 ; 14 return TestData ; /end-free 15 P E |
Fixed format version of second RPG member containing two procedures
01 H actgrp(*new) dftactgrp(*no) 02 H bnddir('*LIBL/TESTBDIR2') 03 D ProcTest PR 25 04 D 25 value 05 D TestClle PR 25 06 D 25 value 07 D TestData DS 25 qualified 08 D First 2 09 D Second 5P 2 10 D Third 15 /free 11 TestData.First = 'AA' ; 12 TESTCLPRC(TestData) ; 13 *inlr = *on ; /end-free 14 P ProcTest B export 15 D ProcTest PI 25 16 D InData 25 value /free 17 TestData = InData ; 18 TestData.Third = 'THIRD' ; 19 TestData.Second = 22.48 ; 20 return TestData ; /end-free 21 P E |
I Am A COBOL Guy - I Never Learned RPG.
ReplyDeleteBack In The Day, The AS400 CL Was 1 Of My Expertises.
Cheers.
Query: TESTCLPRC(TestData) ; --> but the CL procedure doesnt have PGM PARM to accept this TestData? - J
ReplyDeleteQuery 2: RPRLE procedure can have different input and return variables.
ReplyDeleteLooks like CLLE module can get values and return only via the same PGM PARM variable. There cannot be a separate return variable in CLLE module. - J
so as a good practice the procedures PR's are sometimes kept inside a copybook and not declared in the procedure itself to leverage not having to specify them in every pgm that calls the procedure. So in this case, is there a way to work with that, OR does the PR HAVE to be in the procedure?
ReplyDeleteThe code above is just for example, therefore, I may not do things this way in my "live" code.
DeleteI agree I do tend to put all the DCL-PR's in a "copybook" so that they can be copied into the source I need to use them in. I use the DEFINE/UNDEFILE compiler directives to only include the bits of code I desire.
The Compiler binds only procedures you have used ... there is no need for "define/undefine"
Delete