Let Me Out of Here!
November 10, 2004 Hey, Ted
Having read your article “Subprocedures: Better than Subroutines,” I have been inspired to use subroutines less and subprocedures more in RPG programs. However, I have come up against a problem that I can’t solve.
In many of my programs I include a clean-up subroutine that runs when the program ends, whether the program ends normally or abnormally. The subroutine includes various house-keeping tasks, sets on the LR indicator, and executes a RETURN operation. You probably know what happens when I implement such a routine in a subprocedure. Instead of returning to the calling program, control returns to the calling routine within the program.
How can I make my RPG program end from a subprocedure?
As you’ve discovered, there is no RPG op code that ends a program. I suggest you replace the RETURN operation in the shutdown subprocedure to the C exit function. Here’s a short example program to illustrate the use of the exit function.
H dftactgrp(*no) actgrp(*new) H bnddir('QC2LE') D SomeAmount s 7p 2 D SomeTypeField s 1a D Quit pr D Exit pr extproc('exit') D 3u 0 value /free if SomeAmount < *zero ; Quit() ; endif ; // ... some calcs omitted ... if SomeTypeField = 'X' ; Quit() ; endif ; // ... some more calcs omitted ... // Normal end of procedure Quit() ; /end-free * ========================================================= ; P Quit b /free // clean-up calcs go here *inlr = *on ; exit(0) ; /end-free P Quit e
Here are a few things to notice.
- You need to bind to the QC2LE binding directory in order for the compiler to find the exit routine.
- Prototype the exit function, specifying one argument–a three-digit unsigned integer.
- As far as I know, any value from zero to 255 is acceptable as the argument to exit because the system won’t do anything with the value, anyway.
To make sure that I wasn’t giving you bad advice, I asked Barbara Morris of IBM Toronto about your dilemma. She gave me quite a bit more useful information. Here it is, for the edification of us all.
Calling exit( ) behaves the same as calling CEETREC. The behavior is complex, but basically what happens is that all contiguous call-stack invocations in the same activation group as the caller end. What it actually does depends on the activation groups of the entries in the call stack. Imagine the following call stack under two scenarios.
|Procedure||Scenario 1||Scenario 2|
Suppose proc3 issues the exit( ) or CEETREC( ). In scenario 1, in which the entire application is in same activation group, all the activations, including PGMmain, would end, as desired. In scenario 2, where proc2 is in a different activation group, only proc3 would end.
If you have a cancel handler enabled, one of the parameters to the cancel handler is the value specified on the exit( ) or CEETREC( ). The cancel handler would be enabled by the top-level program/procedure in the application. The cancel handler would do the cleanup. If it wanted to pass the value back to the caller of the application, it could have access to the parameters of the top-level procedure, and pass back the exit status in one of those parameters. Or it could send a message to its caller.
If you have the cancel handler do the cleanup, a good way to handle it is to have a cleanup procedure that is called both by normal end processing and from the cancel handler. The cleanup routine should keep track of what it has already done, in case it gets cancelled the first normal time and gets called again through cancel processing.
Sending an escape message to a particular invocation is more reliable. If proc3 sends an escape to PGMmain, then activation group AG1 would end in both scenarios. But having a cancel handler is still a good idea, because the application might get ended through, say, the subsystem being ended.
Below is some code for playing around with this. I used DSPLY with an exported variable to show whether the program really was ended, since non-exported variables are subject to RPG’s LR processing. You could also use static variables in subprocedures to show whether things got cleaned up.
Create all the programs. Call EXITMAIN (uses exit), and answer some value to the DSPLYs. Call EXITMAIN again and notice the prompt values of the DSPLYs. Everything gets cleaned up.
Now change the source for EXIT1 to use AG2 instead of AG1. Repeat the double call and see how nothing really got cleaned up.
Do the same for EXITMMAIN, which uses an escape message from the subprogram and exit() at the top level to do the final cleanup. With everything being AGM1, everything gets cleaned up. With EXITM1 being AGM2, almost everything gets cleaned up, except the one program in AGM2.
1. Using exit:
* pgm EXITMAIN H actgrp('AG1') D msg s 5a export C 'exitmain' dsply msg C call 'EXIT1' C 'exitmain aft'dsply C return * pgm EXIT1 H actgrp('AG1') H*actgrp('AG2') D msg s 5a export C 'exit1' dsply msg C call 'EXIT2' C 'exit1 after' dsply C return * pgm EXIT2 H actgrp('AG1') bnddir('QC2LE') D exit pr extproc('exit') D rc 10i 0 value D msg s 5a export C 'exit2' dsply msg C callp exit(1) C 'exit2 after' dsply C return
2. Using an escape message:
* pgm EXITMMAIN H actgrp('AGM1') bnddir('QC2LE') D msg s 5a export D say s 52a D exit pr extproc('exit') D rc 10i 0 value C 'exitmmain' dsply msg C call(e) 'EXITM1' C 'exitmmain af'dsply C if %error C 'exitmmain er'dsply C callp exit(1) C endif C return * pgm EXITM1 H actgrp('AGM1') H*actgrp('AGM2') D msg s 5a export C 'exitm1' dsply msg C call 'EXITM2' C 'exitm1 after'dsply C return * pgm EXITM2 H actgrp('AGM1') D msg s 5a export C 'exitm2' dsply msg C call 'EXITM3' C 'exitm2 after'dsply C return * CLLE pgm EXITM3 (could be CLP, doesn't matter what AG) SNDPGMMSG MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA('escape + message') TOPGMQ(*SAME ('EXITMMAIN')) + MSGTYPE(*ESCAPE)