Você está na página 1de 5

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.

CSTOPS
**********************************************************************
* To Compile:
*
* 1. CRTRPGMOD MODULE(XXX/CSTOPS) SRCFILE(xxx/QRPGLESRC)
* 2. CRTSRVPGM SRVPGM(XXX/CSTOPS) EXPORT(*ALL)
*
**********************************************************************
H NoMain

FCustomer UF E K Disk

/copy *LibL/QRpgLeSrc,CstOpsPR

**********************************************************************
* Check customer subprocedure
**********************************************************************

P checkCust B Export
D checkCust PI N
D custID 8A Const

/free

SetLL custID Customer;


Return %Equal;

/end-free
P checkCust E

**********************************************************************
* Delete customer subprocedure
**********************************************************************

P deleteCust B Export
D deleteCust PI N
D custID 8A Const

/free

Delete custID Customer;


Return %Error;

/end-free
P deleteCust E

CSTDATA

**********************************************************************
* To Compile:
*
* 1. CRTRPGMOD MODULE(XXX/CSTDATA) SRCFILE(xxx/QRPGLESRC)
* 2. CRTSRVPGM SRVPGM(XXX/CSTDATA) EXPORT(*ALL)
*
**********************************************************************
H NoMain

/copy *LibL/QRpgLeSrc,CstDataPR

**********************************************************************
* Format first and last names into one name.
**********************************************************************

P formatName B Export
D formatName PI 32A
D first 15A Const
D last 15A Const
D FullName S 32A
/free

FullName = %TrimR(last) + ', ' + %TrimR(first);


Return FullName;

/end-free
P formatName E

CSTOPSPR

D checkCust PR N
D custID 8A Const

D deleteCust PR N
D custID 8A Const

CSTDTAPR

D formatName PR 32A
D first 15A Const
D last 15A Const

TESTCUST

**********************************************************************
* To Compile:
*
* CRTBNDRPG PGM(YOUR_LIB/Test_Cust) SRCFILE(YOUR_LIB/QRPGLESRC)
*
**********************************************************************
H DftActGrp(*NO) ActGrp(*CALLER) BndDir('MYBNDDIR')

/copy *LibL/QRpgLeSrc,CstDataPR
/copy *LibL/QRpgLeSrc,CstOpsPR

D custID s 8A
D found s N
D error s N
D delete s 1A
D FullName s 32A

C *Entry Plist
C Parm custID
C Parm delete

C Eval found = checkCust(custID)


C found Dsply

C If found and (delete = 'Y')


C Eval error = deleteCust(custID)
C error Dsply

C ElseIf found
C Eval FullName = formatName('Kevin ':
C 'Vandever ')
C FullName Dsply
C EndIf
C Eval *InLr = *on

Você também pode gostar