Tuesday, December 15, 2020

How to manipulate the value returned from a RPG procedure

new debug control option to all changing of values returned from subprocedure

Sometimes it can be hard in testing to change values returned from procedures. I don't want to change the code in the procedure. But how can I ensure that the returned value is what I want? In debug I can change the value of a variable, and this will change the change value returned if the variable is returned from the procedure. What can I do if returned value is hard coded? I cannot change that.

  return *on ;

Fortunately this is no longer an issue due to new enhancement added to RPG as part of the latest Technology Refreshes, IBM i 7.4 TR3 and 7.3 TR9.

There is a new control option value that can be inserted into the DEBUG keyword.

  ctl-opt DEBUG(*RETVAL) dftactgrp(*no) ;

The value *RETVAL allows me to change the value contained in a special value: _QRNU_RETVAL.

The only place I can change this is in debug, and only when the current break point is on the END-PROC operation code of the procedure.

Let me jump into examples so you can understand how to use this new feature.

In the first example I have a procedure that receives a single variable and returns the equivalent:

01  **free
02  ctl-opt debug(*retval) dftactgrp(*no) ;

03  dcl-s wkVar char(3) inz('0') ;

04  dcl-pr TestProc char(3) ;
05    *n char(3) ;
06  end-pr ;

07  wkVar = TestProc(wkVar) ;
08  dsply ('wkVar = ' + wkVar) ;

09  *inlr = *on ;

20  dcl-proc TestProc ;
21    dcl-pi *n char(3) ;
22      SomeVar char(3) ;
23    end-pi ;

24    return %char(%dec(SomeVar:3:0) + 1) ;
25  end-proc ;

Line 1: Why would I not use what I call RPG for i.

Line 2: The Control options includes the new DEBUG(*RETVAL).

Line 3: Definition of the variable whose value I will be passing and receiving back from the procedure.

Lines 4 – 6: The procedure prototype definition. This passes a three long character value to the procedure, line 5, and receives a three long character back, see line 4.

Line 7: I am calling the procedure, passing the value in wkVar to it, and placing the returned value into wkVar.

Line 8: I am using the Display operation code, DSPLY, to show the contents of wkVar.

The procedure is between lines 20 - 25.

Lines 21 – 23: The procedure interface.

Line 24: I have added one to the value character value in the variable SomeVar and returned the result to the calling program.

When everything has been compiled I added the following breakpoints into this example:

  1. Line 8 – Before the procedure is called
  2. Line 25 – End of the procedure

When I call the program it "breaks" at line 8. I can enter the following debug command and see the result:

ev wkvar

  WKVAR = '0  '

And onto the breakpoint at line 25, the END-PROC. I need to be at this line to use this new debug functionality.

Prior to this addition I cannot see the value of what was returned. But now I can using the _QRNU_RETVAL special value:

ev _qrnu_retval

  _QRNU_RETVAL = '1  '

I can even change the value:

ev _qrnu_retval = 'AAA'


When the Display operation is performed I see:

DSPLY  wkVar = AAA

The value returned from the procedure was what I changed it to in _QRNU_RETVAL.

A single variable is simple. I often pass a data structure to a procedure and get the equivalent returned. Let me see how I can change the values in the data structure using this method. First my example program:

01  **free
02  ctl-opt debug(*retval) dftactgrp(*no) ;

03  dcl-ds TemplateDS template ;
04    sf1 char(3) ;
05    sf2 packed(3) ;
06  end-ds ;

07  dcl-ds wkDS likeds(TemplateDS) ;

08  dcl-pr TestProc2 likeds(wkDS) ;
09    *n likeds(wkDS) ;
10  end-pr ;

11  wkDS.sf1 = 'STR' ;
12  wkDS.sf2 = 0 ;
13  wkDS = TestProc2(wkDS) ;

14  *inlr = *on ;

20  dcl-proc TestProc2 ;
21    dcl-pi *n likeds(TemplateDS) ;
22    Input likeds(TemplateDS) ;
23    end-pi ;

24    Input.sf1 = 'END' ;
25    Input.sf2 = 1 ;

26    return Input ;
27  end-proc ;

Lines 3 – 6: This is a definition of a template data structure, hence the TEMPLATE on line 3. This defines the data structure, but it cannot be used to contain data. It can be used to define other data structures by using the LIKEDS keyword.

Line 7: Defining a new data structure using the LIKEDS.

Lines 8 – 10. Procedure prototype definition. Notice that the returned value and the passed value are both defined with the LIKEDS too. I defined them using wkDS to act a "documentation". You now know which data structure I will be passing to the procedure, and which data structure I will be placing the returned data into.

Lines 11 and 12: Loading values into the data structure sub fields.

Line 13: Calling the procedure passing to it the contents of the data structure, and putting the returned data into the same data structure.

The procedure, lines 20 – 27, is fairly simple.

Lines 21 – 23: Procedure interface. Notice how Input has been defined as a data structure by using the LIKEDS.

Lines 24 and 25: Moving values into the data structure's subfields.

Line 26: This time I do not have a hard coded return. Here I am returning the contents of the data structure.

Time to compile this program, start debug, and add break points:

  • Line 13 – So I can see the contents of wkDS before the procedure is called
  • Line 14 – I can see the contents of wkDS after the data has been returned from the procedure
  • Line 27 – End of the procedure
  • Here is the contents of the data structure before I call the procedure, line 13:

    ev wkds
      EVAL wkds
      WKDS.SF1 = 'STR'
      WKDS.SF2 = 000.

    Next break point is at the end of the procedure, line 27. Here I can see the values in the Input data structure:

    ev input
      EVAL input
      INPUT.SF1 = 'END'
      INPUT.SF2 = 001.

    The same values are also in _QRNU_RETVAL:

    ev _qru_retval
      EVAL _qrnu_retval
      _QRNU_RETVAL.SF1 = 'END'
      _QRNU_RETVAL.SF2 = 001.

    If I want to change the values of the data structure subfields I just qualify the subfields with the special value's name:

    ev _qrnu_retval.sf1 = 'NOT'
      EVAL _qrnu_retval.sf1 = 'NOT'
      _QRNU_RETVAL.SF1 = 'NOT' = 'NOT'
    ev _qrnu_retval.sf2 = 2
      EVAL _qrnu_retval.sf2 = 2
      _QRNU_RETVAL.SF2 = 2 = 002.
    ev _qrnu_retval
      EVAL _qrnu_retval
      _QRNU_RETVAL.SF1 = 'NOT'
      _QRNU_RETVAL.SF2 = 002.

    The final breakpoint, line 14, allows me to see if the changed values are in the data structure:

    ev wkds
      EVAL wkds
      WKDS.SF1 = 'NOT'
      WKDS.SF2 = 002.

    It contains the data that I changed in _QRNU_RETVAL.

    My last example shows how to change the values in an array returned from a procedure.

    01  **free
    02  ctl-opt debug(*retval) dftactgrp(*no) ;
    03  dcl-s Array_1 char(2) dim(5) ;
    04  dcl-pr TestProc2 char(2) dim(5) ;
    05    *n char(2) dim(5) ;
    06  end-pr ;
    07  Array_1 = %list('1':'2':'3':'4':'5') ;
    08  Array_1 = TestProc3(Array_1) ;
    09  *inlr = *on ;
    20  dcl-proc TestProc3 ;
    21    dcl-pi *n char(2) dim(5) ;
    22      Array_2 char(2) dim(5) ;
    23    end-pi ;
    24    Array_2(1) = 'A' ;
    25    return Array_2 ;
    26  end-proc ;

    Line 3: Definition of my array.

    Lines 4 – 6: The procedure prototype definition. Notice how the passed parameter, line 5, and the returned value, line 4, are both defined as arrays.

    Line 7: This is a new feature introduced with these TRs, the %LIST BiF. This makes loading an array so easy!

    Line 8: Call the procedure, and place the returned data into the same array.

    In the procedure, lines 20 – 26.

    Line 24: Change the first element of the array.

    Line 25: Return the contents of the array.

    I compiled the program, started debug, and placed the following break points:

  • Line 8 – I can see the values in the array before the data is passed to the procedure
  • Line 9 – Here I can view the contents of the array returned from the procedure
  • Line 26 – End of the procedure
  • When I called the program and the when the first break point happened I can:

    ev array_1
      EVAL array_1     
      ARRAY_1(1) = '1 '
      ARRAY_1(2) = '2 '
      ARRAY_1(3) = '3 '
      ARRAY_1(4) = '4 '
      ARRAY_1(5) = '5 '

    Next break point is at the end of the procedure. Here I can see that the contents of Array_2 and _QRNU_RETVAL are the same:

    ev array_2
      EVAL array_2
      ARRAY_2(1) = 'A '
      ARRAY_2(2) = '2 '
      ARRAY_2(3) = '3 '
      ARRAY_2(4) = '4 '
      ARRAY_2(5) = '5 '
    ev _qrnu_retval
      EVAL _qrnu_retval
      _QRNU_RETVAL(1) = 'A '
      _QRNU_RETVAL(2) = '2 '
      _QRNU_RETVAL(3) = '3 '
      _QRNU_RETVAL(4) = '4 '
      _QRNU_RETVAL(5) = '5 '

    I am going to change the second element:

    ev _qrnu_retval(2) = 'B'
      EVAL _qrnu_retval(2) = 'B'
      _QRNU_RETVAL(2) = 'B' = 'B '
    ev _qrnu_retval
      EVAL _qrnu_retval
      _QRNU_RETVAL(1) = 'A '
      _QRNU_RETVAL(2) = 'B '
      _QRNU_RETVAL(3) = '3 '
      _QRNU_RETVAL(4) = '4 '
      _QRNU_RETVAL(5) = '5 '

    Onto the last breakpoint, and I see that my change to the second element are in the returned array:

    ev array_1 
      EVAL array_1
      ARRAY_1(1) = 'A '
      ARRAY_1(2) = 'B '
      ARRAY_1(3) = '3 '
      ARRAY_1(4) = '4 '
      ARRAY_1(5) = '5 '

    As I have shown _QRNU_RETVAL can be a variable, data structure, and even an array depending upon the returned parameter type. I will certainly be using this to help me debug procedures to ensure the value they return. This is good addition to my programming toolset.


    You can learn more about the RPG DEBUG control option from the IBM website here.


    This article was written for IBM i 7.4 TR3 and 7.3 TR9.

    No comments:

    Post a Comment

    To prevent "comment spam" all comments are moderated.
    Learn about this website's comments policy here.

    Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.