ILE Basics: Procedures

Article ID: 56727

In the article titled "ILE Basics: It's All About the Call" (March 27, 2008, article ID 56468), I provided an overview of ILE. In this article, I expand on one of those concepts: procedures. Procedures are the basic building block of any ILE program. The ability to deploy, call, and maintain procedures is the primary focus of the ILE environment.

Two ILE procedure types exist: Main procedures and subprocedures.

Main Procedures

All the ILE languages support main procedures. In fact, whether you realize it or not, if you're an experienced programmer, you've written main procedures. Main procedures are the "normal" part of your code. If you create a program object, the main procedure is the part of the code that runs when you call your program. If you've never written a subprocedure, all the code you've written is in the main procedure. OPM languages (e.g., RPG/400) have only a main procedure and do not support subprocedures.

The point is: Even though you might not think of your code as a "main procedure," that's what it is. You're already familiar with main procedures because they're the part of the program you've always worked in.

The following is an example of a simple RPG program and is very similar, in fact, to the types of programs we wrote back in the RPG/400 days. Everything you see in the following code is part of the "main procedure":

     FPRICES    IF   E           K DISK

     D ItemNo          s              7p 0

     C     *ENTRY        PLIST
     C                   PARM                    ItemNo
     C                   PARM                    Price

     C     ItemNo        chain     Prices
     c                   if        %found
     C                   eval      Price = PrPric
     c                   else
     c                   eval      Price = -1
     c                   endif

     C                   eval      *INLR = *ON

In this example, the main procedure is written in fixed-format RPG, but you can write a main procedure in free-format RPG or in any other ILE language. It's just the "main part" of the programs that we've been writing for decades.

When a main procedure is compiled, the system gives it the same name as the module it was created in. Therefore, I often see people refer to calling a main procedure as "calling a module." Technically, it's impossible to call a module (modules are not executable code). When you perform a bound call to something with the name of the module, what you're actually doing is calling the main procedure of that module.

ILE CL supports only main procedures (CL does not yet have support for subprocedures). In CL, everything between the PGM and ENDPGM commands is considered part of the main procedure.

The following CL code is a main procedure:

PGM  PARM(&ITEMNO &PRICE)

     DCL VAR(&ITEMNO) TYPE(*DEC) LEN(7 0)
     DCL VAR(&PRICE)  TYPE(*DEC) LEN(9 2)

     OVRDBF FILE(PRICES) TOFILE(QTEMP/PRICES) +
                         INHWRT(*YES)

     CALL PRICEPGM PARM(&ITEMNO &PRICE)

     DLTOVR PRICES

ENDPGM

ILE C is slightly different in that the main procedure in a C module is actually named "main" in the code. However, when the code is compiled, the compiler exports the main procedure under the same name as the module.

The following is a trivial example of a main procedure written in C:

#include 

int main(argc, char **argv) {

   printf("Hello World!\n");
   return 0;
}

Unfortunately, I don't know Cobol well enough to provide a sample in Cobol -- but, suffice it to say that it's just the "normal" part of your program code.

Creating an ILE Application with Main Procedures

The primary goal of ILE is to create your programs in small, reusable pieces and thereby improve productivity, make your code easier to maintain, and make your code easier to read.

With that in mind, you can create an entire ILE application made up of small, reusable pieces without using any subprocedures. In other words, you can do it with nothing but main procedures. To do that, create lots of different modules and put one procedure in each one. Then bind them all together, and you'll have an ILE program built from lots of small, reusable routines.

However, the "all main procedure" approach has some problems:

  • Each module is built from a separate source member. It quickly becomes cumbersome to build an application from lots of independent source members. Remember, if you keep your code short and simple, a reasonably complex application might have 1,000 separate procedures. Do you really want to maintain 1,000 separate source members per application?
  • Similarly, each module that accesses a file has to open it independently of the other files. So if your application is built of 1,000 modules, and 50 percent of them use the Customer Master file, you'll end up with 500 copies of the file open, and performance is soon adversely affected!

In my experience, when people try to write applications using nothing more than a main procedure in each module (i.e., a separate routine in each module), they end up writing monolithic code. Keeping track of lots of simple routines is just too cumbersome. So they end up making bigger routines so that fewer modules are needed, and presto! Suddenly they're writing monolithic code, just as they did 10 years ago.

Therefore, in my opinion, the ability to support more than one procedure per module is an absolutely crucial feature when you're writing business applications today. In ILE, you do that with subprocedures.

Subprocedures

Some of the ILE languages (e.g., RPG, C, C++) support subprocedures in addition to the main procedures I listed earlier. Subprocedures are independently callable routines in a module. Here are some of the characteristics of subprocedures:

  • They can accept parameters and return values.
  • They can have their own variables, separate from the rest of the program.
  • They can be called independently.

  • They can return values to be used in expressions.

Many people find it easiest to think of subprocedures as "subroutines with parameters." In other words, subprocedures are small pieces of code (like subroutines), except that you can call them and pass parameters to them.

Other people prefer to think of procedures as "sub-programs," because they work very much like a program-within-a-program.

     FPRICES    IF   E           K DISK

     D PRICEPGM        PR
     D   ItemNo                       7p 0       const
     D   Price                        9p 2
     D PRICEPGM        PI
     D   ItemNo                       7p 0       const
     D   Price                        9p 2

     D GetPrice        PR             9p 2
     D   ItemNo                       7p 0       const

      /free

          Price = GetPrice(ItemNo);
          *inlr = *on;

      /end-free

      ///////   S U B P R O C E D U R E   ///////

     P GetPrice        B
     D GetPrice        PI             9p 2
     D   ItemNo                       7p 0       const
     D retval          s              9p 2
      /free
           chain ItemNo Prices;
           if %found;
             retval = prPric;
           else;
             retval = -1;
           endif;
           return retval;
      /end-free
     P                 E

In the preceding code, everything before the first P-spec is part of the main procedure. Everything between the beginning P-spec and the ending P-spec is part of the GetPrice subprocedure.

You can have as many subprocedures as you want in an ILE program. In RPG, you do that simply by adding more P-specs to the program.

RPG Subprocedure Specs, Variables, and Files

When you define a subprocedure in RPG, it starts and ends with a P-spec. It can also contain D-specs to define parameters, prototypes, data structures, constants, and variables. Finally, a subprocedure can contain C-specs, or free-format calculations.

In V5R4 and earlier releases, you cannot have F-specs in a subprocedure. In V6R1, this has changed, and you can now declare files in a subprocedure. You can never define I-specs, O-specs, or compile-time table data inside a subprocedure.

When you declare something inside a subprocedure (whether it's a variable, data structure, constant, or file), it's only available to that subprocedure. This behavior is useful because it makes reusing a subprocedure easier.

The GetPrice() procedure from the preceding example has a variable defined in it named retval. This variable exists only within that subprocedure. It's created in memory when the procedure starts and is deleted when the procedure ends. Other areas of the program cannot refer to this retval variable; it's available only to the GetPrice() subprocedure.

If another routine in the program (either the main procedure or a different subprocedure) were to define a variable named retval, it would be a separate variable from the one in the GetPrice() routine. It's really no different from the way we've always been able to declare variables in separate programs. If two different programs declare a variable with the same name, they're considered completely separate, independent variables. If you think of a subprocedure as a "sub-program," it's easy to understand how a subprocedure can also have its own variables independent from the other routines in the program.

The exception to this rule is when the main procedure declares a variable. Variables declared in the main procedure are considered "global" variables and are available throughout the module. Subprocedures can use these global variables, as long as they don't already have their own variable with the same name. If a subprocedure has a variable with the same name as a global variable, the subprocedure "sees" its own variable and not the global one.

Another feature of subprocedures is that they can return values to be used in an expression. If you look at the prototype for the GetPrice() procedure, you'll see that it defines its return value as a packed 9,2. For reference, here's the prototype of the GetPrice() routine, with the return value highlighted in red:

     D GetPrice        PR             9p 2
     D   ItemNo                       7p 0       const

When you pass a variable as the factor2 value of the RETURN opcode, it copies it to a temporary variable of the size/type that you define for the return value. Then, it inserts that temporary variable into an expression. To call a procedure like this one that returns a value, you include it in an EVAL statement.

eval Price = GetPrice(ItemNo);

The preceding code takes the value specified on the RETURN statement in the procedure and assigns it to the variable named Price. Using this technique, you can write your own code that works very much like RPG's built-in functions (BIFs). Pretty cool, eh?

Subprocedures (aka "Functions") in C/C++

Writing a subprocedure in C/C++ is also very easy. Indeed, any C function that isn't named 'main' is considered a subprocedure.

Since subprocedures in C are ordinary functions, which are taught to all C programmers as part of their introduction to the language, I won't go into more detail here. Suffice it to say that C functions are considered "procedures" in ILE lingo.

More Information

Subprocedures are really ILE's most basic building block. The rest of the ILE concepts involve helping call, deploy, and maintain the subprocedures. That's about all there is to know about subprocedures. Here are some links to related articles:

ILE Basics: It's All About the Call
http://systeminetwork.com/article/ile-basics-its-all-about-call

Prototypes: Beyond Call/Parm
http://systeminetwork.com/article/prototypes-beyond-callparm

ProVIP Sponsors

ProVIP Sponsors