PoC
PoC

Reputation: 591

Getting a return value from a called program back to main routine in rpgle

I'm trying to get experience in RPGLE and IBM i stuff and constantly learning. Since most code in the wild seems to be classic positional, I'll stick with this to get used to it. So I would rather not use /free — /end-free stuff. Incidentally, I'm doing this on an old 9401-150 with only V4R5.

TL;DR: How can I get a return value back from an external called ILE program (with it's own MAIN section, that is, it's standalone in itself) in it's own activation group (*NEW) to the callee?

I have a subfile program ready and running fine. I want to call an external program to handle requests by OPT value in the subfile. So I defined a PR in the D-Specs of the callee:

DROEDETPG         PR                  EXTPGM('ROEDETPG')
DC_MODE                               LIKE(MODE)
DC_TYP                                LIKE(TYP)         

Later, I call the Program which also works fine.

C                   SELECT
C     OPT           WHENEQ    '2'
C                   MOVE      'CHG'         MODE
C                   CALLP     ROEDETPG(MODE:TYP)
C                   ENDSL

This is the entry point in the called program:

C     *ENTRY        PLIST
C                   PARM                    C_MODE            3
C                   PARM                    C_TYP            16

Now, maybe the record I want to change is already locked. So, I'm using CHAIN(E) in the external program and get %STATUS from the PF after the CHAIN. The status value for that is 1218 and I want to get this value back to the calling program, so it may use the message line to tell the user, the record is locked and unavailable at the moment.

All I could find online is to prototype the call and define a call interface (PI) which is possibly applicable to procedures only.

So I thought of a "temp file" as I'm used to in Bash and C on Unix/Linux for that purpose. There seems to be no mktemp() equivalent, but I can create files with the same name in QTEMP. This applies to a file type *DTAARA. Unfortunately (expectable?) this file is visible only to the calling program. Maybe could create a global keyed *DTAQ with SENDERID(*YES) but maybe this is overkill?

Why don't I put the functionality in the external program into a bunch of functions and use CALLP? Well, I'm still learning. I decided to move stuff from Subroutines out of the main source to end the constant hassle with state changes involved when subroutines do stuff. When a SR does a READ, the database pointer points to another record which brings a multitude of erratic behavior when continuing the subfile stuff.

Plus, global variables (field content) get overwritten which adds more code to move content aside, save the key value for the DB, call the SR and restore variables again and do a SETLL to get back to the state where I was before. I expect this to be easier but maybe I'm still too much a rookie regarding ile rpg.

I'm open for other suggestions regarding how to avoid my underlying problem (file pointer and global vars) properly.

Upvotes: 4

Views: 7131

Answers (3)

jmarkmurphy
jmarkmurphy

Reputation: 11493

Well, your first issue is the age of your OS. If you were on a more modern platform, you could create a subprocedure with local file descriptions, and be rid of the file pointer issues like this:

dcl-proc myFileIsLocked;
  dcl-pi *n Ind;
    mode     Char(3) const;
    type     Char(16) const;
  end-pi;

  dcl-f myfile  keyed usage(*Update);
  dcl-c RECORD_LOCKED      1218;

  chain(e) (type) myfile;
  return (%status = RECORD_LOCKED);
end-proc;

However, even at v4r5, you can use a sub-procedure to accomplish your goal. Put your procedure in a service program, and define the file in the global area of the module. Like this:

For V4R5

qrpglesrc,mymodule

h NoMain

fmyfile    up   e           k disk usropn

 /copy qprotosrc,mymodule

pmyFileIsLocked   b                   export
d *n              pi              n
d mode                           3a   const
d type                          16a   const
d*
d RECORD_LOCKED   c                   1218
c*
c                   if        not %open(myfile)
c                   open      myfile
c                   endif
c*
c     type          chain(e)  myfile
c                   if        %status = RECORD_LOCKED
c                   eval      result = *On
c                   endif
c*
c                   close     myfile
c                   return    (%status = RECORD_LOCKED)
p                 e

qprotosrc,mymodule

dmyFileIsLocked   pr              n
d mode                           3a   const
d type                          16a   const

qsrvsrc,mymodule

STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('myModule')
/********************************************************************/
/*  ------ DO NOT CHANGE THE ORDER OF THESE EXPORTS!!! ---------    */
/*  ------ ADD NEW SYMBOLS TO THE END OF THE LIST ONLY ---------    */
/********************************************************************/
  EXPORT SYMBOL(myFileIsLocked)
ENDPGMEXP

Then create the module and service program like this:

CRTRPGMOD  MODULE(MYMODULE) SRCFILE(QRPGLESRC)
CRTSRVPGM  SRVPGM(MYMODULE) SRCFILE(QSRVSRC) BNDDIR(MYMODULE) +
            STGMDL(*INHERIT)

Note the naming. I name my service program the same as the module, as well as the service source and binding directory. I always create a binding directory for each service program. This avoids name conflicts later when the service program is updated. I also create a common binding directory for building programs that use the service programs. But I digress. This service program specific binding directory may be empty if the service program does not depend on any others. That isn't a problem. At some point you will likely expand the service program, and then you may very well need to add something to your binding directory.

After this is all built, you can then simply

c                   if        myFileIsLocked('CHG': 'TYPE')
c*                    do something
c                   endif

Upvotes: 2

Player1st
Player1st

Reputation: 1605

I know you said no /free or **FREE but I'm going to ignore that because as far as I'm concerned, the fixed format stuff can go burn in the hellfire from whence it was spawned. You are welcome to translate the following code back to fixed format on your own. In order to properly do this, you should simply be passing in a variable to the program that will contain the error.

MYPGM.RPGLE

**FREE
/Include MYHDR
Dcl-Pi *N;
    ErrorRet Char(7); // or whatever type you want to return
End-Pi;

ErrorRet = 'RFE1234';
*InLR = *On;
Return;

MYHDR.RPGLE

**FREE
Dcl-Pr MyPgm ExtPgm('MYPGM');
    ErrorRet Char(7); // or whatever type you want to return
End-Pr;

CALLINGPGM.RPGLE

**FREE
/Include MYHDR

Dcl-S Error Char(7) Inz;

MyPgm(Error);
// Error should now contain 'RFE1234'
...

Upvotes: 5

Mike
Mike

Reputation: 1324

Because parameters in ILE are passed by reference, one easy way to get one or more return values back from an external program is to just define them in your prototype and your called programs parameters. Coming from other programming environments, this is a little strange to me as I have been brainwashed into BYVAL not BYREF for so many years now, but really that is all a function return value ever is, a value on the stack that gets passed back to the caller. Some languages would define these as out parameters, but they are actually inout that you are treating like an out.

 DROEDETPG         PR                  EXTPGM('ROEDETPG')
 DC_MODE                               LIKE(MODE)
 DC_TYP                                LIKE(TYP)
 DC_ERR                        7        



 C     *ENTRY        PLIST
 C                   PARM                    C_MODE            3
 C                   PARM                    C_TYP            16
 C                   PARM                    C_ERR             7

One advantage to this method is that rather than return one value as RPGLE subprocedures do and functions do in most programming languages, you can return as many values as you need for a given task without defining a data structure to hold them all in.

QTEMP is a great library to use, but I think creating a file is overkill for what can be done with simple parameter passing. I do think you may have some misconceptions about QTEMP, though. It is defined on a per-job basis, so if you write something there (in a file or in a userspace object, which can be more convenient than a file if you are comfortable with pointers), it will absolutely be there for other programs running in that job to find until the job ends or it is explicitly deleted. Experiment a little to confirm this, and if necessary ask a separate question, because it really should be able to be used as shared storage for multiple programs.

Upvotes: 4

Related Questions