Escolar Documentos
Profissional Documentos
Cultura Documentos
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.
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.
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.
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
/end-free
P checkCust E
**********************************************************************
* Delete customer subprocedure
**********************************************************************
P deleteCust B Export
D deleteCust PI N
D custID 8A Const
/free
/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
/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 ElseIf found
C Eval FullName = formatName('Kevin ':
C 'Vandever ')
C FullName Dsply
C EndIf
C Eval *InLr = *on