|
The Basics of ILE Service Programs
by Kevin Vandever
[The code for this article is available for download.]
In the article "Calling RPG Native Methods from Java,"
I explain how to call native methods from a local Java application, using the Java Native Interface. Native
methods are just a hopped up version of ILE service programs that are defined in, and called from, a
Java application. However, there may be some out there who don't really know what a service program is,
let alone a native method, so I thought I would introduce the basics to get you started.
A service program is a collection of subprocedures that perform a set of services. These subprocedures are
packaged as modules, which are then bound together to form a service program. A service program is a
type of OS/400 object, labeled *SRVPGM. Unlike its counterpart, *PGM, a service program cannot be
called directly; only the subprocedures within the service program can be called, once the service program
has been statically bound to one or more *PGMs or *SRVPGMs. One of the beauties of service programs is
that they have multiple entry and exit points. You can think of a service program as many subroutines
bunched together and made available to the outside world. Or, if you're a Java buff, you can loosely think
of a service program as a Java class, with the subprocedures representing Java methods. In fact, that is how
JNI views service programs and how it implements calls to service programs in the form of native methods.
The other nice thing about service programs is that they are very lightweight, because they do not include
the normal startup expense and logic cycle code that a traditional *PGM does. Let's build a service
program, and I'll show you what I mean.
The Source Code
I've included two source members (CstOps and CstData), as well as their
respective prototype copybooks (CstOpsPR and CstDataPR), which I am going to use to
build my service program. Each of these source members contains one or more subprocedures. I am not
going to cover subprocedures in detail, because Ted Holt did that nicely in "Subprocedures: Better than
Subroutines." However, I will mention a couple of things related to subprocedures and how they
become modules and, finally, service programs.
First of all, a module is also an OS/400 object. It is labeled as *MODULE. A *MODULE cannot be called
dynamically, as you might call a traditional *PGM. Instead, it must be either directly bound to a *PGM or
used alone, or combined with other *MODULES, to create a *PGM or *SRVPGM. The decision on
whether you bind to a module directly or use it as a building block to create a service program depends on
how often you plan to call it and how many programs it will be called from. I will cover binding and
binding decisions in a future article. For now, understand that the *MODULE is a component used to create
service programs.
Take a look at the H-spec in either the CstOps or CstData source member. You will notice the word
NoMain. This tells the compiler that the module contains no program entry procedure (PEP) to call. In
plain English, it means that this module cannot be called directly. This is not required to create a module or
even a service program, but if you plan on creating modules to bind them together into a service program, it
is better to use NoMain modules, because service programs will ignore PEPs that are part of modules
bound inside a service program. The other advantage of using NoMain is that the RPG logic cycle code is
not included in the compiled object, and that means smaller objects and faster job startup times. Next, I will
discuss multiple entry points.
If you take a look at CstOps, you will notice two subprocedures, checkCust and deleteCust. These are both
entry points into this module, meaning that either of these subprocedures can be called from outside this
module, provided you define them to be exported. The CstData module contains one procedure, and
therefore has only one entry point, the call to the formatName subprocedure.
The Steps to Create Modules
Now it's time to create the modules. I separated the individual tasks into two modules because I wanted to
show you how service programs are created from multiple modules, but I have an additional motive. In the
CstOps module, I have written subprocedures that require access to the customer file. The getCust and
deleteCust tasks both require access to CUSTOMER. If I were to write a subprocedure to return customer
information from the customer file, I would add that subprocedure to this module, because the customer file
is already a part of this module. In that way, I organize all tasks that need to access the customer file in one
module. For other customer file-related tasks that don't need access to the customer file, I will add them to a
separate module or modules. In this case, I've written a subprocedure that formats the first and last names
into a standard format, for display purposes. Maybe your shop always displays names a certain way; well,
why not write that code once and use it everywhere? Of course, you may use your own organizational
strategy; this is just one potential solution. To create the CstOps and CstData modules, type the following
commands from the command line or use option 15 from PDM:
CRTRPGMOD MODULE(YOUR_LIB/CSTOPS) SRCFILE(XXX/QRPGLESRC)
CRTRPGMOD MODULE(YOUR_LIB/CSTDATA) SRCFILE(XXX/QRPGLESRC)
Now that I have two modules, I am ready to create the service program. I have created two RPG modules,
but you don't have to stick to RPG. If you have a mixture of expertise in your shop, each person could code
in his area of expertise, and then the modules could be combined in a service program. For example,
suppose that there were other customer-related tasks written in COBOL or C, you could combine them with
the RPG modules and create one service program that contains all the customer-related tasks. To create the
service program for this example, use the following command (Note: There is no PDM command to create
service programs, because they are not created from source files; rather, they are generated from
*MODULE objects):
CRTSRVPGM SRVPGM(YOUR_LIB/CUSTSTUFF) MODULE(CSTOPS CSTDATA)
EXPORT(*ALL)
Try This at Home
There you have it! A brand spankin' new service program at your disposal. Notice both module names are
entered in the module parameter. Also notice that I stated EXPORT *ALL. This allows any subprocedure
that I defined with the keyword EXPORT within the program interface to actually get exported to the rest
of the world. There are other issues surrounding exporting that I will cover in another article. For now, just
know that you can choose whether or not to export the subprocedure for use outside of the module.
To use this service program, you have to bind it to any programs or service programs that plan to call the
individual subprocedures. There are a couple of ways to bind service programs. One way is to specifically
bind it to each program or service program that will use it when you compile that program. However, I
recommend adding the service program to a binding directory (*BNDDIR), and binding to that directory
instead. A binding directory contains no ILE magic; it is simply a storage space for modules and service
programs and allows you to bind multiple service programs and modules with one entry in the compile
options. To create a binding directory use the following command:
CRTBNDDIR BNDDIR(YOUR_LIB/MYBNDDIR)
Once created, you can use the WRKBNDDIR command to add and remove entries to and from the binding
directory. I have included the source code (Test_Cust), which you can use to test both
the binding of a service program and the calling of a service program's subprocedures. Instead of entering
compile options each time, I compile using option 14 in PDM; I entered them in my H-spec. That way, you
don't have to worry about them ever again. The three required entries are the default activation group
(DFTACTGRP) set to *NO; the activation group, which I set to *CALLER, but can be a named *NEW
activation group; and the binding directory containing my service programs and modules. Now compile this
program using option 14, as you have done a billion times before, and you have a *PGM that is bound to
our service program via the binding directory. You can call the program from the command line using the
traditional CALL command. My program accepts an 8-btye customer ID and a flag, to determine if I should
delete the customer. I have also included the DDS for the CUSTOMER file, for you convenience. Give her
a whirl and see what happens.
So Much More, So Little Time
This article was meant to give you a basic understanding of service programs. In upcoming issues, I will
explain binding in more detail, including the binding of modules and service programs, how to bind them,
and the advantages and disadvantages of each technique. I will also provide a more advanced look at
service programs that will talk about exporting, the binder language, and signatures. At some point, I will
also cover activation groups and explain what they are and why to use them. I will then provide you more
complicated, real-world ILE applications that demonstrate the flexibility and strength of ILE. The goal is to
provide you a one-stop shop for all your ILE needs. In the meantime, feel free to ask questions, suggest
topics, or just plain shoot the breeze.
Kevin Vandever is a lead IT engineer at Boise Cascade Office Products in Itasca, Illinois, and a co-
editor of Midrange Programmer, OS/400 Edition. He can be reached at kvandever@itjungle.com.
|