Programming on IBM i: sources, ILE, RPG and SQL Chapter 2 of 9

ILE: modules, programs and service programs

Reviewed on Verified on IBM i 7.5

Log in to save

Until the early nineties a program was a single block: one source, one compile, one *PGM object holding everything. That model is called OPM, Original Program Model, and on many systems there is still code running inside it.

ILEIntegrated Language Environment — changed the unit of work. You no longer compile a program: you compile modules, and modules are bound together into a program or a service program.

The three objects

  • Module (*MODULE) — the result of a compile. It does not run: it is a brick.
  • Program (*PGM) — one or more modules bound together, with an entry point. This is what you call.
  • Service program (*SRVPGM) — one or more modules exposing procedures to others, not called directly. The equivalent of a shared library.
CRTRPGMOD MODULE(PRODLIB/CALCS) SRCFILE(PRODLIB/QRPGLESRC)
CRTPGM    PGM(PRODLIB/INVOICES) MODULE(PRODLIB/INVOICES PRODLIB/CALCS)
CRTSRVPGM SRVPGM(PRODLIB/UTILS) MODULE(PRODLIB/UTILS) EXPORT(*ALL)

CRTBNDRPG does both in one move: it compiles the module and binds it straight into a program. It is convenient, and it is why you see so many ILE programs made of a single module — ILE in form and OPM in substance.

Why separating pays

The difference shows when one function is needed by twenty programs.

In the old model you wrote it as a separate program and invoked it with CALL. That works, but a CALL is dynamic: it costs on every invocation, the compiler does not check the parameters, and you cannot return a value — only change parameters.

With ILE you write it as a procedure inside a service program and call it like any function. The binding is static: the compiler checks the number and type of parameters, a return value exists, and the cost of the call is an order of magnitude lower.

**FREE
dcl-pr calcNetAmount packed(11:2) extproc('CALC_NET_AMOUNT');
  amount packed(11:2) const;
  rate packed(5:2) const;
end-pr;

netAmount = calcNetAmount(total: 22);

Tip

this is the modernisation with the best ratio of result to risk. It does not touch existing code, does not change data, does not require a rewrite: you extract into a procedure the logic currently copied across ten programs, and from then on you fix it in one place. The modernisation chapter picks up from here.

The binding directory

Listing modules and service programs by hand on every compile soon becomes unmanageable. A binding directory (*BNDDIR) is a list: declare it once and the compiler takes what it needs from it.

CRTBNDDIR BNDDIR(PRODLIB/APPBND)
ADDBNDDIRE BNDDIR(PRODLIB/APPBND) OBJ((PRODLIB/UTILS *SRVPGM))

In the source it is declared once:

ctl-opt bnddir('PRODLIB/APPBND');

The signature

A service program has a signature, computed from the list of procedures it exports. Programs using it carry that signature inside them, and at start-up the system checks that the two match.

Warning

adding a procedure to a service program and recompiling changes the signature. Every program bound to that service program stops starting with a signature error, and they all have to be recompiled. On a large application that is not an afternoon: it is a release.

You avoid it by declaring the exports in binder language — a source listing the exported procedures and keeping them in a stable order — and adding new ones at the end without touching the old. Done from day one it costs ten minutes; done later, you pay once with a general recompile and never again.

Activation groups

Here is the part that does the most damage, because it is invisible until it is not.

An activation group is the space a program lives in while it runs: its static variables, the files it opened, its commitment control, its error handling. It is chosen at compile time, and there are three possibilities.

  • ACTGRP(*NEW) — every call creates a new group, destroyed on return. Clean, and expensive: files are reopened every time.
  • ACTGRP(*CALLER) — the program runs in its caller's group. The right choice for a service program, which should live wherever its users live.
  • ACTGRP(NAME) — a group with a name we chose, shared by the whole application. The right choice for programs.

Warning

an activation group does not close itself when the program ends. If the program returns without *INLR on, the group stays alive along with its variables and open files — useful when you want it, and a memory leak when you do not know about it. RCLACTGRP ACTGRP(NAME) closes a named group, but on an active job it should be used knowing what you are throwing away.

Important

the activation group also decides the scope of commitment control. Two programs in different groups have different transactions, and a COMMIT in one does not close what the other did. When a transaction commits halfway, this is nearly always the cause and hardly ever the commit code.

An OPM program, finally, always runs in the default activation group, which belongs to the system and cannot be reclaimed. Mixing OPM and ILE in the same call chain works, and is normal in an old application, but it is the point where the behaviour of errors and transactions stops being predictable.

What to look at in an application you inherit

DSPPGM PGM(PRODLIB/INVOICES) DETAIL(*MODULE)
DSPSRVPGM SRVPGM(PRODLIB/UTILS) DETAIL(*PROCEXP)

Three questions tell you nearly everything: are the programs ILE or OPM, are they made of one module or several, and which activation group do they run in. If the answer is "ILE, one module, ACTGRP(*NEW)", you are looking at OPM code compiled with a newer compiler — which works perfectly well, and has none of the advantages ILE was built for.

Back to the guide index

← Back to blog

Comments

No comments yet. Be the first to comment!

You need an account to comment. Log in · Sign up