Today we are going to unpack how program flow actually works inside your RPG modules. The ILE RPG compiler is pretty clever. It quietly supplies some of the control logic for you, and the amount of that logic depends entirely on the type of module you decide to build.

By default an RPG module comes with the full RPG Cycle. This cycle begins with the *INIT phase and runs all the way through to the *TERM phase. The other two module types drop most of the cycle and keep only the module initialization part (which is basically the *INIT phase). The compiler also adds a few other bits of automatic logic that sit outside the cycle completely, for example the automatic opening and closing of files declared inside subprocedures.

All ILE RPG modules can contain one or more procedures. What separates the three module types is simply the nature of the main procedure (or the lack of one).

A program or a service program can be built from multiple modules. Each module can have its own main procedure. If you mark an RPG module as the program entry module then you call its main procedure with a program call. If the module is not the program entry point or if it lives inside a service program then you normally call the main procedure with a bound call.

One important rule to remember: you can only use a bound call on a cycle-main procedure. If your module contains a linear-main procedure and it is not selected as the program entry module then that procedure cannot be called from outside at all.

Here are the three types of RPG modules you will work with on IBM i.

Module with a Cycle-Main Procedure

This is the classic RPG style you probably grew up with. The module contains a cycle-main procedure plus any number of subprocedures. The cycle-main procedure includes all the full RPG cycle logic. You can call it either with a program call or with a bound call.

You do not have to code anything special to create the main procedure. Everything before the first procedure specification belongs to the cycle-main procedure.

Module with a Linear-Main Procedure

Here the module contains a linear-main procedure plus any number of ordinary subprocedures. You identify the linear-main procedure by placing the MAIN keyword on the Control specification. The main procedure itself is coded exactly like a subprocedure using Procedure specifications.

The linear-main procedure can only be called through a program call. It cannot be called using a bound call. Apart from the calling method the linear-main procedure behaves exactly like any other subprocedure. The module does not include any RPG cycle logic at all.

Module with No Main Procedure

You create this type by coding the NOMAIN keyword on the Control specification. The module contains only subprocedures and has no main procedure whatsoever. No RPG cycle logic is generated. Because it has no main procedure you cannot use this module as the program entry module of any program.

The table below gives you a quick side-by-side comparison of the three module types.

Module Type
Keyword
Cycle
Main Procedure
Initialization of global variables, opening of global files, and locking of UDS data areas
Implicit closing of global files and unlocking of data areas
Cycle-main
(none)
Yes
Implicitly defined in the main source section
When the first procedure in the module is called after the activation group is created (or again if the main procedure previously ended with LR on or ended abnormally)
When the main procedure ends with LR on or ends abnormally
Linear-main
MAIN
No
Explicitly defined with the MAIN keyword and Procedure specifications
When the main procedure is first called after the activation group is created (or if a subprocedure somehow gets called first)
Never
No main (NOMAIN)
NOMAIN
No
None (indicated by the NOMAIN keyword)
When the first procedure in the module is called after the activation group is created
Never

Cycle Module in Detail

A cycle module has a cycle-main procedure that uses the full RPG Program Cycle. The procedure is defined implicitly in the main source section. You do not need any special code to mark it. The parameters for the cycle-main procedure can be defined with a procedure interface and an optional prototype in the global definition specifications, or the old-fashioned way with a *ENTRY PLIST in the calculations.

The name of the cycle-main procedure must match the name of the module you are creating. You can use that name on the prototype and procedure interface, or you can override it with the EXTPROC keyword.

Here is a quick example. Imagine we are building a module called CHECKFILE that has a cycle-main procedure with three parameters: a file name, a library name, and an output indicator that tells us whether the file was found.

In a /COPY member called CHECKFILEC you would put the prototype:

D CheckFile PR
D   file           10a const
D   library        10a const
D   found           1N

Then in the module source itself you would write:

/COPY CHECKFILEC
D CheckFile PI
D   file           10a const
D   library        10a const
D   found           1N

C ... code using parameters file, library and found

If you prefer the traditional *ENTRY PLIST style you can still do it:

D file           S 10a const
D library        S 10a const
D found          S 1N

C    *ENTRY      PLIST
C                PARM    file
C                PARM    library
C                PARM    found

C ... code using parameters file, library and found

You can also turn the cycle-main procedure into a real program by using the EXTPGM keyword on the prototype (or directly on the procedure interface if you do not need a prototype).

Use Caution When Exporting Subprocedures in Cycle ModulesIf you build a cycle module that also exports subprocedures you need to be extra careful. The RPG cycle in the main procedure can quietly open and close files, lock and unlock data areas, and reinitialize global data at times you do not expect. An exported subprocedure can be called from outside the module before the cycle-main procedure ever runs, and then when the cycle-main procedure finally starts it can cause all sorts of surprises.

The safest approaches are:

  • Move the cycle-main logic into a subprocedure and turn the whole module into a NOMAIN module, or
  • Change the cycle-main procedure into a linear-main procedure.

If you really must mix them, make sure the cycle-main procedure is always called first. Avoid letting the cycle-main procedure run more than once (the easiest way is to never set on LR). Declare files as USROPN and handle open, close, lock, and unlock operations yourself inside the code.

Linear Module General Rules

When you specify either the MAIN or NOMAIN keyword on the Control specification the compiler builds the module without any RPG cycle logic. That means you cannot code certain things in the main source section:

  • Primary or secondary files
  • Heading, detail, or total output
  • Executable calculations (including the *INZSR subroutine)
  • *ENTRY PLIST

You can still code full-procedural files, input specifications, definition specifications, declarative calculations (DEFINE, KFLD, KLIST, PARM, PLIST), and exception output.

Important note: linear modules do not automatically close global files or unlock data areas when the module ends. You are responsible for cleaning everything up yourself.

Linear Main Module

You create this type by coding the MAIN keyword on the control specification followed by the name of the procedure you want to act as the main entry point. The module still has a program entry procedure but it runs without the RPG cycle. See the MAIN keyword documentation for the exact syntax.

NOMAIN Module

A NOMAIN module contains nothing but subprocedures. You signal this with the NOMAIN keyword on the Control specification. No cycle code is generated at all.

Tip: consider converting most of your old cycle modules to NOMAIN modules except for the one that really needs to be the program entry point. This keeps each module smaller and cleaner by removing all that unnecessary cycle overhead.

Module Initialization

Module initialization happens the very first time any procedure in the module (main or subprocedure) is called after the activation group is created.

A cycle module has one extra kind of initialization that can happen more than once. Cycle-main procedure initialization runs the first time the cycle-main procedure is called and again on later calls if the previous run ended with LR on or ended abnormally.

Global data in the module is initialized both during normal module initialization and during any cycle-main procedure initialization.

There you have it. Once you understand these three module types and when the compiler does (and does not) give you free cycle logic, you will write much cleaner, more modern ILE RPG code on IBM i.

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
>