Forums >> Programming >> RPG Programming

Jump to:




bvstone

Implementing ILE for RPG 101

Posted:

Implementing ILE for RPG 101

Sometimes it's best to start from square one for a subject.  

The Integraged Language Environment (ILE) has been written about many times over the years, and even I've written about it in a couple of books and training manuals myself.

But sometimes I feel a new beginning can refresh and rejuvenate and even inspire new ideas and methods.

We all seem so excited about new technologies and that is great.  But for what we have to work with, and what our jobs require, I believe that taking full advantage of what has been given to us as a base for our root product (ie, RPG) will be much more fruitful that spending too much time on the latest and greatest that will only be replaced in months to come (tic).

Now, I'm not saying don't learn new things or experiment.  In fact, I encourage the use of new technologies.  But in the case of ILE, it's something that in the long run will not only make your job easier, but will increase your value as an IBM i programmer.

So, for this series I will be putting together a number of articles on implementing ILE into your applications starting with the most simple of functions, calculating the sum of two integers, and ending with much more complicated and useful functions.  

I will also provide my insights, ideas and tricks I've developed and learned while implementing ILE for applications written not only for our software as an ISV, but many of the applications we've created for customers.

A few terms will be used interchangeably here as well.  Function, Procedure and Subprocedure will all be used to describe a singular function inside of a module or service program.  

On that same note, Module and Service Program may be used in place of each other as well, Module being the more generic term.  When we are referring to either a Module or Service Program specifically, we'll try to make that obvious.

So, lets get started!


Last edited 06/05/2015 at 09:45:30



bvstone

Creating The Environment

Posted:

Creating The Environment

The environment I chose to create for a "Fresh Start" is a library named ILESAMPLE.

First, I created the library:

CRTLIB LIB(ILESAMPLE)

Next, I created the source physical files:

CRTSRCPF FILE(ILESAMPlE/QRPGLESRC) RCDLEN(112)

CRTSRCPF FILE(ILESAMPlE/QMODSRC) RCDLEN(112)

CRTSRCPF FILE(ILESAMPlE/COPYSRC) RCDLEN(112)

CRTSRCPF FILE(ILESAMPlE/QSRVSRC) RCDLEN(112)

The four source physical files I am created are as detailed as the following:

  • QRPGLESRC - This is where our RPG Programs will be held
  • QMODSRC - This is where the source for our RPG modules will be held
  • QCOPYSRC - This is where the source for our RPG /COPY books will be held.  This will contain mainly prototype definitions.
  • QSRVSRC - This is where the binder language for our ILE service programs will he held.

Now if you're already confused, don't be.  Things will make more sense as we go through the examples.

But, the first glaring issue would be the terms Module and Service Program.  I'm sure you've heard them before, and maybe even asked about them only to be more confused as to the difference, and when to use each one.  But, for now, just think of things this way:  

You create a Module which contains the subprocedures (or functions).  This Module can be bound "by copy" to your programs so the subprocedures within can be used.  This means a physical copy of the module is added to the program.

A Service Program is created from a Module.  Think of a Service Program as a "Stand Alone" copy of the Module.  In other words, when you use the subprocedures within a service program it's like calling a separate program on your system.  

Service Program

The call to a subprocedure for a Module and a Service Program will look exactly the same.  The only difference will be that the Service Program will be on it's own, and a Module must be "Bound" to the program.

Again, this will make more sense once we get into the gritty details, so lets get started with our first module.

 


Last edited 03/29/2015 at 15:53:42



bvstone

Our First RPG ILE Module

Posted:

Our First RPG ILE Module

For our first ILE module I wanted to keep things very simple.  That's because I'd rather you focus HOW it is done vs WHAT it is doing.

With that said, the source for our first module is as follows:

H NOMAIN                                                              
 ****************************************************************     
 * Prototypes                                                   *     
 ****************************************************************     
D #getSumInt      PR            10i 0                                 
D  Augend                       10i 0 Const                           
D  Addend                       10i 0 Const                           
 *//////////////////////////////////////////////////////////////*     
 * getSumInt - Return the sum of two integers                   *     
 *//////////////////////////////////////////////////////////////*     
P #getSumInt      B                   EXPORT                          
 *--------------------------------------------------------------*     
D #getSumInt      PI            10i 0                                 
D  Augend                       10i 0 Const                           
D  Addend                       10i 0 Const                           
 *--------------------------------------------------------------*     
 /free                                                                
                                                                      
  return (Augend + Addend);                                           
                               
 /end-free                     
P #getSumInt      E            

I called this first source member F.MATH and placed it in the source physical file QMODSRC.

Now, you probably will notice a few things about how I personally do ILE.  Not all of the methods I use are shown here, but a lot of them are:

  • I always name the module source member F.xxxxxx where xxxxxx is the descriptive name.  In this case, we'll play around with procedures that perform "Math" functions.
  • I place the module source in QMODSRC
  • I like to prefix my subprocedure names with the # character.  You don't have to do this, I choose to to make them easier to find in programs.  You'll find in later examples I also prefix the modules with #name_ where "name" is a grouping name.  (ie, #math_getSumInt would be a more descriptive name that I would use in production).  And yes, I do understand the hash tag won't work in all language sets, but this can be swapped for another character.  

These are not the only unique choices I've made over the years.  You'll notice others as we progress, but here's a short list of additional methods I use:

  • I name the /COPY member containing the prototypes P.xxxxxx and place it in the source physical file QCOPYSRC.  In this example we haven't used a /COPY member yet, but soon will.
  • If a service program will be used, I name the binder language F.xxxxxx (the same as the module) and place it in the source physical file QSRVSRC.  This is mainly because the defaults for the Create Service Program (CRTSRVPGM) command use these as defaults.

But, before we get too far ahead of ourselves, let's take a look at the module we're working with.

First, we see the NOMAIN H spec.  All this does is tell the compiler there is no main or entry module.  In other words, we can't just CALL this module, instead we will be calling the subprocedures within.

Next we see the prototype(s) for any subprocedures in the module.  In this case there is only one (so far).  Normally this would be a /COPY directive, but for this example we are all inclusive.

The prototype is used in both the module and and program to describe the function and it's parameters.  It's important to note that the names for the parameters aren't required, and are really "informational only".  But, the data type and size declarations are required as well as any keywords such as CONST, VALUE or OPTIONS.

Next we see the Procedure Beginning (specified by the "B" following the procedure name).  Also, we see an EXPORT keyword.  This is used if we want to export the function to be used by any other applications other than itself.  (There are actually times where you won't want to export a procedure, but we will cover that later).

Next is the Procedure Interface (PI).  This is similar to the prototype and yes, seems redundant.  But, this actually sets up the parameters to be usable in the procedure itself.   In this case the parameter names are required as these names are how you will access the values of the parameters in the actual procedure.

Finally we have the Procedure End (specified by the "E" following the procedure name).

You may have noticed the two parameters to the procedure use the keyword CONST.  This means that the values will be "read only" and cannot be changed within the procedure.  It also means that you can pass the data as a "value" (ie, the number 10 instead of a variable with the value 10).  If the parameters were both input and output, then you would omit this keyword and any change made to the parameter would be passed back to the calling program.  You can also use the VALUE keyword which acts in a similar method as CONST.  There has been much discussion about these keywords so instead of me getting into which is "better", you can decide that for your self after reading all that is available (or just as much as you want) from a internet search.

Now, there's no reason to have a procedure to find the sum of two numbers.  But, hopefully the simplicity of this will allow you to focus on the procedure definition and use itself instead of what the procedure does.

To create this module we would issue the following command:

CRTRPGMOD MODULE(ILESAMPLE/F.MATH) SRCFILE(ILESAMPLE/QMODSRC)

Next, we will see how to use this module and procedure in a program.


Last edited 04/03/2015 at 06:36:19



bvstone

Using Our First RPG ILE Module

Posted:

Using Our First RPG ILE Module

Now that we have created our first ILE module, we will want to use it!

Below is the source code for a program named MATHTEST that we placed in QRPGLESRC:

 ****************************************************************                 
 * Prototypes                                                                     
 ****************************************************************                 
D #getSumInt      PR            10i 0                                             
D  Augend                       10i 0 Const                                       
D  Addend                       10i 0 Const                                       
 ****************************************************************                 
D Sum             S             10i 0                                             
 ****************************************************************                 
 /free                                                                            
                                                                                  
  Sum = #getSumInt(20:21);                                                        
                                                                                  
  *INLR = *ON;                                                                    
 /end-free                                                                        

As you can see, this program is very short and simple.  All we are doing is getting the sum of 20 plus 21.

You will see that in order to use our new function #getSumInt we need to define the prototype in the D-Spec our application.  You've probably noticed that the prototype we using is exactly the same as the prototype that we used in the F.MATH module source, and you would be right.  This is a case where we just copied the source from one member to another.  Not the best practice, but one we are all used to.  Later we'll show how to use /COPY books to update this.

Now, to compile this program will take two steps.  

  • Create a module named MATHTEST
  • Create a program named MATHTEST.  This program will consist of two modules, MATHTEST and F.MATH.

The first command we will run to create the MATHTEST module is as follows:

CRTRPGMOD MODULE(ILESAMPLE/MATHTEST) SRCFILE(ILESAMPLE/QRPGLESRC)

Once our module is created, we now will create a program from the two modules, MATHTEST and F.MATH.

CRTPGM PGM(ILESAMPLE/MATHTEST) MODULE(ILESAMPLE/MATHTEST ILESAMPLE/F.MATH)

What we have just done is created a program from two separate modules.  This method is called "Bind By Copy".  This means that a copy of the module is actually "bound" to the program.  If we were to update the module, for example adding a new subprocedure, our MATHTEST program would still contain a copy of the old F.MATH module until it is recompiled at which time the new copy of the module would be bound to the program.  

If we were to run the following command:

DSPPGM PGM(ILESAMPLE/MATHTEST) DETAIL(*MODULE)

We would see in the *MODULE detail the two modules that create this program:

                         Display Program Information 
                                                                Display 3 of 7 
Program  . . . . . . . :   MATHTEST      Library  . . . . . . . :   ILESAMPLE  
Owner  . . . . . . . . :   BVSTONE    
Program attribute  . . :   RPGLE      
Detail . . . . . . . . :   *MODULE    


Type options, press Enter. 
  5=Display description   6=Print description 

                                         Creation  Optimization  Debug 
Opt  Module      Library     Attribute   Date         Level      Data 
     MATHTEST    ILESAMPLE   RPGLE       03/26/15  *NONE         *NO        
     F.MATH      ILESAMPLE   RPGLE       03/26/15  *NONE         *NO        

We can now call our program just as we would any other program.

It's interesting to note that for a while we've been doing this same process, but only with one module.  When we compile RPGLE programs using option 14 (or CRTRPGPGM) in the background the system first creates a module with the program name in library QTEMP, then creates a program with that single module.  The module is then removed from the system as it is no longer needed.   Try running the DSPPGM command above on a "normal" RPGLE program.

If we debug our program we can see how things work by adding a break point on the call to the #getSumInt procedure:

                            Display Module Source 

Program:   MATHTEST       Library:   ILESAMPLE      Module:   MATHTEST   
     1        **************************************************************** 
     2        * Prototypes                                                     
     3        **************************************************************** 
     4       D #getSumInt      PR            10i 0                             
     5       D  Augend                       10i 0 Const                       
     6       D  Addend                       10i 0 Const                       
     7        **************************************************************** 
     8       D Sum             S             10i 0                             
     9        **************************************************************** 
    10        /free                                                            
    11                                                                         
    12         Sum = #getSumInt(20:21);                                        
    13                                                                         
    14         *INLR = *ON;                                                    
    15        /end-free                                                        
                                                                        Bottom 
Debug . . . eval sum

F3=End program   F6=Add/Clear breakpoint   F10=Step   F11=Display variable 
F12=Resume       F17=Watch variable   F18=Work with watch   F24=More keys 
SUM = 41                                                                       

While a simple example, this should show you just how easy it is to create reusable code with RPG and ILE.  

But, with every lesson that starts at the lowest level, before we start creating things for production we should start applying "best practice" methods.  

I only put best practice in quotes since those can vary from person to person, but I will share what I feel are best practices and have served me well for over 15 years.  And trust me, I have some code out there I wish I could go and and update, as I'm sure we all do.  So this is really going to go through all my mistakes and bring us close to the point where we should be now.

Next... /COPY members for prototypes.


Last edited 03/29/2015 at 16:04:17



bvstone

Using /COPY Members for Prototypes

Posted:

Using /COPY Members for Prototypes

One thing you most likely noticed (if not, hopefully you read the mention of it) is that the prototype definitions for our first subprocedure is copied both in the module source as well as the program source where the module is used.

This is a perfect place to make use of /COPY members.  

Remember in the first step we created a source physical file named QCOPYSRC?  Well, this is where we're going to place our /COPY members containing the prototypes.  

So, first let's simply copy the member F.MATH from QMODSRC to QCOPYSRC, but we are going to name the member P.MATH (the "P" stands for "Protoype").

CPYSRCF FROMFILE(ILESAMPLE/QMODSRC) TOFILE(ILESAMPLE/QCOPYSRC) FROMMBR(F.MATH) TOMBR(P.MATH)

Now, we can edit the P.MATH member so it contains only the following source (You'll notice that I also added a description that wasn't there before):

 *//////////////////////////////////////////////////////////////*         
 * getSumInt - Return the sum of two integers                   *         
 *//////////////////////////////////////////////////////////////*         
D #getSumInt      PR            10i 0                                     
D  Augend                       10i 0 Const                               
D  Addend                       10i 0 Const                               

We can now also go back and edit our F.MATH module to the following:

H NOMAIN                                                                 
 ****************************************************************        
 * Prototypes                                                   *        
 ****************************************************************        
 /COPY QCOPYSRC,P.MATH                                                   
 *//////////////////////////////////////////////////////////////*        
 * getSumInt - Return the sum of two integers                   *        
 *//////////////////////////////////////////////////////////////*        
P #getSumInt      B                   EXPORT                             
 *--------------------------------------------------------------*        
D #getSumInt      PI            10i 0                                    
D  Augend                       10i 0 Const                              
D  Addend                       10i 0 Const                              
 *--------------------------------------------------------------*        
 /free                                                                   
                                                                         
  return (Augend + Addend);                                              
                                                                         
 /end-free                                                               
P #getSumInt      E  

You'll see under the commented heading of "Prototypes" that we removed the prototype definition for our subprocedure and instead used the /COPY directive so that during compile time the source from the P.MATH member will be pulled in automatically.

Now, lets update or program MATHTEST as well:

  ****************************************************************             
  * Prototypes                                                                 
  ****************************************************************             
  /COPY QCOPYSRC,P.MATH                                                        
  ****************************************************************             
 D Sum             S             10i 0                                         
  ****************************************************************             
  /free                                                                        
                                                                               
   Sum = #getSumInt(20:21);                                                    
                                                                               
   *INLR = *ON;                                                                
  /end-free                                                                    

Again, we simply replaced the prototype definition with the /COPY directive.

Now, we can recreate the F.MATH module, create the MATHTEST module, then create the MATHTEST program:

CRTRPGMOD MODULE(ILESAMPLE/F.MATH) SRCFILE(ILESAMPLE/QMODSRC)

CRTRPGMOD MODULE(ILESAMPLE/MATHTEST) SRCFILE(ILESAMPLE/QRPGLESRC)

CRTPGM PGM(ILESAMPLE/MATHTEST) MODULE(ILESAMPLE/MATHTEST ILESAMPLE/F.MATH)

Now, both our module code and our program code is cleaned up so that it does not contain redundant prototypes.

Next we will look at prefixing the procedure names to make them more descriptive.

 


Last edited 04/03/2015 at 06:20:42



bvstone

Prefixing Procedure Names

Posted:

Prefixing Procedure Names

Before we go any further I'd like to share another practice I use.

When looking through source code and coming upon a procedure it will be obvious since I always prefix my procedure names with a hash tag (#).  But, in the past few years I've also added a prefix to each procedure name so I know which module or service program it is from.

For example, our first procedure we've written is #getSumInt.  This name is pretty unique as it stands, but what if we start using more generic names like #getValue or #setValue?  Because each module will need to have unique procedure names, that's where the prefix comes in.

Examine the update /COPY source for our module, P.MATH:

 *//////////////////////////////////////////////////////////////*             
 * #math_getSumInt - Return the sum of two integers             *             
 *//////////////////////////////////////////////////////////////*             
D #math_getSumInt...                                                          
D                 PR            10i 0                                         
D  Augend                       10i 0 Const                                   
D  Addend                       10i 0 Const                                   

You'll notice we added a prefix of #math_ to our procedure name.  

The same goes four our module source F.MATH:

H NOMAIN                                                                    
 ****************************************************************           
 * Prototypes                                                   *           
 ****************************************************************           
 /COPY QCOPYSRC,P.MATH                                                      
 *//////////////////////////////////////////////////////////////*           
 * #math_getSumInt - Return the sum of two integers             *           
 *//////////////////////////////////////////////////////////////*           
P #math_getSumInt...                                                        
P                 B                   EXPORT                                
 *--------------------------------------------------------------*           
D #math_getSumInt...                                                        
D                 PI            10i 0                                       
D  Augend                       10i 0 Const                                 
D  Addend                       10i 0 Const                                 
 *--------------------------------------------------------------*           
 /free                                                                      
                                                                            
  return (Augend + Addend);                                                 
                              
 /end-free                    
P #math_getSumInt...          
P                 E           

And finally, our program MATHTEST:

 ****************************************************************      
 * Prototypes                                                          
 ****************************************************************      
 /COPY QCOPYSRC,P.MATH                                                 
 ****************************************************************      
D Sum             S             10i 0                                  
 ****************************************************************      
 /free                                                                 
                                                                       
  Sum = #math_getSumInt(20:21);                                        
                                                                       
  *INLR = *ON;                                                         
 /end-free                                                             

You can choose to do this or not.  But, what I find is when I'm looking through source code and I see a procedure being called, with the appropriate prefix I'll know right away which module or service program it is being called from.

Next, we'll look into creating a Service Program from our module and look into the pros and cons of using Modules or Service Programs.


Last edited 04/03/2015 at 06:34:30



bvstone

Creating Our First Service Program

Posted:

Creating Our First Service Program

Now that we are comfortable with a simple module, lets look at it's big brother, the Service Program.

A service program can be created using a couple different ingredients, but the one thing that is required is a module.  An optional ingredient is Binder Language, but we'll touch on that later.  You can also include additional modules and or other service programs, but for now, lets look at creating a service program named F.MATH from our single module, also named F.MATH.

Creating a service program uses the Create Service Program (CRTSRVPGM) command.  On this command you can specify a list of modules, service programs, and/or a Binding Directory which is nothing more than a list of modules and/or service programs.  Our example will be quite simple though, since we are only interested in converting our F.MATH module into a service program:

CRTSRVPGM SRVPGM(ILESAMPLE/F.MATH) MODULE(ILESAMPLE/F.MATH) EXPORT(*ALL)

Examining our command we will so two obvious pieces.  First is the name of the service program we are creating (F.MATH), and second is the name of a module that the service program will be created from (F.MATH).

We will also see that we are specifying EXPORT(*ALL).  I'll be the first to say that in production using this is a no-no.  The "why" will make more sense when we get into Binding Directories, Binder Language and Signatures but for now it will service the purpose.  But please, do NOT use this keyword in production.

To understand why it's not a good idea, let's first examine what the EXPORT keyword does.

                      Create Service Program (CRTSRVPGM)      
          
Type choices, press Enter. 

Service program  . . . . . . . . > F.MATH        Name
  Library  . . . . . . . . . . . >   ILESAMPLE   Name, *CURLIB
Module . . . . . . . . . . . . . > F.MATH        Name, generic*, *SRVPGM, *ALL
  Library  . . . . . . . . . . . >   ILESAMPLE   Name, *LIBL, *CURLIB...
               + for more values                 
                                     *LIBL       
Export . . . . . . . . . . . . . > *ALL          *SRCFILE, *ALL
Export source file . . . . . . .   QSRVSRC       Name, QSRVSRC
  Library  . . . . . . . . . . .     *LIBL       Name, *LIBL, *CURLIB
Export source member . . . . . .   *SRVPGM       Name, *SRVPGM
Text 'description' . . . . . . .   *BLANK

We see that the EXPORT keyword accepts 2 values, *SRCFILE and *ALL.  What this does is tell the system which procedures we want to export to make available for use in our service program.  When we say *ALL this tells the system that each procedure coded with the EXPORT keyword (remember that?) will be exported.  So that means that for our service program the #math_getSumInt procedure will be exported and available in our F.MATH service program.

We can view the available procedures using the DSPSRVPGM command:

DSPSRVPGM SRVPGM(ILESAMPLE/F.MATH) DETAIL(*PROCEXP)

                      Display Service Program Information 
                                                                 Display 1 of 1 
 Service program  . . . . . . . . . . . . :   F.MATH     
   Library  . . . . . . . . . . . . . . . :     ILESAMPLE  
 Owner  . . . . . . . . . . . . . . . . . :   BVSTONE    
 Service program attribute  . . . . . . . :   RPGLE      
 Detail . . . . . . . . . . . . . . . . . :   *PROCEXP   

                               Procedure Exports: 

 Procedure Name                                                          ARGOPT 
 #MATH_GETSUMINT                                                         *NO    

Up next, we will look at using the *SRCFILE option and Binder Language for the EXPORT keyword on the CRTSRVPGM command.

 


Last edited 04/03/2015 at 07:44:52



bvstone

Service Program Binder Language Part 1 - Exporting Procedures

Posted:

Service Program Binder Language Part 1 - Exporting Procedures

In the previous article we saw how to create a service program from a module.  In this example we told the system to simply export all of the available procedures in the module.

In this article we will see how instead of using EXPORT(*ALL) we will use Binder Language to tell the system which procedures to export.  

If you recall in the beginning we created a source physical file named QSRVSRC.  This is where we are going to store our Binder Language.  We like to name the Binder Language member the same as the source module.  So in this case, it will be named F.MATH and look like the following:

             STRPGMEXP                              
             EXPORT     SYMBOL(#math_getSumInt)     
             ENDPGMEXP                              

We will see that we have source that starts with STRPGMEXP (Start Program Export List) and ends in ENDPGMEXP (End Program Export List).  Between these two keywords we will place the names of any procedures we wish to export from our module to be available for use in our newly created service program.

There can be more than one grouping of exported procedures for a service program between the STRPGMEXP and ENDPGMEXP keywords with different procedures being exported.  Each grouping will (or should) have it's own unique Signature.  This will be covered later, but just be aware that each Binder Language source member can contain from one to many different export groupings.

To create our service program we will use the following command:

CRTSRVPGM SRVPGM(ILESAMPLE/F.MATH) MODULE(ILESAMPLE/F.MATH) EXPORT(*SRCFILE) SRCFILE(ILESAMPLE/QSRVSRC) SRCMBR(*SRVPGM)

Take notice that instead of EXPORT(*ALL) we are using EXPORT(*SRCFILE).  It is in this example only so we see the difference between the above, specifying EXPORT(*SRCFILE), and the previous method of creating a service program using EXPORT(*ALL).

The defaults for SRCFILE and SRCMBR also default nicely so that if we place our binder language in QSRVSRC and name it the same as the module source, we really only need to specify the following shorter command and take advantage of the system defaults:

CRTSRVPGM SRVPGM(ILESAMPLE/F.MATH) MODULE(ILESAMPLE/F.MATH)

Next we will take a look at what Service Program Signatures are and how they can be your friend, or your worst enemy.

 


Last edited 11/04/2019 at 08:43:40



bvstone

Service Program Binder Language Part 2 - Signatures

Posted:

Service Program Binder Language Part 2 - Signatures

Up until this point we've stayed away from the topic of Service Program Signatures mainly because it is something that can get confusing and interfere with learning the basics of programming with ILE.

But, before we go any further it is best we cover this subject.  If you choose the route of using Service Programs in place of or along with modules, it will be something you will want to understand.

When you create a service program that service program gets what is called a Signature.  A signature is a unique identifier of a service program that is created based upon the procedures that are exported from a module.

When you create a program that uses a Service Program that program will take the most current signature from the service program and store it in the program description.  That way when it goes to call a procedure from that service program it can do a check on the signature. 

Signatures can either be generated by the system, or you can provide your own signatures in your Binder Language on the STRPGMEXP label.

When you use the CRTSRVPGM command and specify EXPORT(*ALL) (which we won't do in production, right?) or leave the STRPGMEXP label blank without any other keywords the system will generate a signature for our service program.

If we edit our F.MATH binder language and prompt the STRPGMEXP label we will see the following keywords or parameters are available:

                      Start Program Export List (STRPGMEXP)    
           
 Type choices, press Enter. 
   
                                                  
                            Additional Parameters                               
  
 Program level  . . . . . . . . .   *CURRENT      *CURRENT, *PRV
 Signature level check  . . . . .   *YES          *YES, *NO
 Signature  . . . . . . . . . . .   *GEN

 Comment  . . . . . . . . . . . . 

First we see the Program Level (PGMLVL) keyword which defaults to *CURRENT.  This means that when we create this service program that this particular grouping of exports will be the "current" list.

Next we see the Signature Level Check (LVLCHK).  This is similar to a level check we're all probably familiar with when it comes to files.  This is used so that when a program uses a service program it checks the signature it was assigned when it was created to make sure it matches one of the signatures of the service program.  If not, it will throw an error.

The third option is the Signature (SIGNATURE).  This allows us to specify our own signature or the special value of *GEN which will let the system generate the signature.  I've actually found that specifying my own signatures in the form of versions (ie, v1.0, v1.1, v2.2) as the signature tends to work well and be a little more descriptive.  But, it's your choice as to how you want to do this.

Lets now take a look at the signature of our F.MATH service program and how it's referenced in our MATHTEST program.

DSPSRVPGM SRVPGM(ILESAMPLE/F.MATH) OUTPUT(*) DETAIL(*SIGNATURE)

When we execute this command we will be shown the signature(s) of our service program:

                     Display Service Program Information 
                                                                Display 1 of 1 
Service program  . . . . . . . . . . . . :   F.MATH     
  Library  . . . . . . . . . . . . . . . :     ILESAMPLE  
Owner  . . . . . . . . . . . . . . . . . :   BVSTONE    
Service program attribute  . . . . . . . :   RPGLE      
Detail . . . . . . . . . . . . . . . . . :   *SIGNATURE 

                                 Signatures: 

00E3D5C9D4E4E2E3C5C76DC8E3C1D47B 

The signature we see here is actually a hex representation of a characters signature.  We can also use the F11 function key to display the "text" version of our signature.

                     Display Service Program Information 
                                                                Display 1 of 1 
Service program  . . . . . . . . . . . . :   F.MATH     
  Library  . . . . . . . . . . . . . . . :     ILESAMPLE  
Owner  . . . . . . . . . . . . . . . . . :   BVSTONE    
Service program attribute  . . . . . . . :   RPGLE      
Detail . . . . . . . . . . . . . . . . . :   *SIGNATURE 

                                 Signatures: 

 TNIMUSTEG_HTAM# 

Interestingly in this case the signature is the single procedure name exported from the module, only backwards.

Let's go back into our F.MATH Binder Language source now and actually specify our own signature:

             STRPGMEXP  SIGNATURE('v1.0')                
             EXPORT     SYMBOL(#math_getSumInt)          
             ENDPGMEXP                                   

The signature in this case will be more descriptive and tell us that this is version 1 of our service program.

We now can recreate the service program:

CRTSRVPGM SRVPGM(ILESAMPLE/F.MATH) MODULE(ILESAMPLE/F.MATH)

And then again, view the signatures for our service program:

DSPSRVPGM SRVPGM(ILESAMPLE/F.MATH) OUTPUT(*) DETAIL(*SIGNATURE)

We now see the signature value:

                     Display Service Program Information 
                                                                Display 1 of 1
Service program  . . . . . . . . . . . . :   F.MATH     
  Library  . . . . . . . . . . . . . . . :     ILESAMPLE  
Owner  . . . . . . . . . . . . . . . . . :   BVSTONE    
Service program attribute  . . . . . . . :   RPGLE      
Detail . . . . . . . . . . . . . . . . . :   *SIGNATURE 

                                 Signatures: 

A5F14BF0404040404040404040404040 

If we press the F11 function key we will see the text representation of our new signature:

                     Display Service Program Information 
                                                                Display 1 of 1 
Service program  . . . . . . . . . . . . :   F.MATH     
  Library  . . . . . . . . . . . . . . . :     ILESAMPLE  
Owner  . . . . . . . . . . . . . . . . . :   BVSTONE    
Service program attribute  . . . . . . . :   RPGLE      
Detail . . . . . . . . . . . . . . . . . :   *SIGNATURE 

                                 Signatures: 

v1.0             

Next, we will update our program to use the F.MATH service program and Bind By Reference in place of the F.MATH module using Bind By Copy.


Last edited 04/07/2015 at 14:47:28



bvstone

Using Our First RPG ILE Service Program

Posted:

Using Our First RPG ILE Service Program

Now that we have taken the step of creating a service program from the F.MATH module, let's "bind" it to our MATHTEST program.

The first step is the same as if we were binding the F.MATH module.  We create the MATHTEST module:

CRTRPGMOD MODULE(ILESAMPLE/MATHTEST) SRCFILE(ILESAMPLE/QRPGLESRC)

Next, we use the Create Program (CRTPGM) command again, but this time instead of specifying the F.MATH module, we specify the F.MATH service program created in the previous section:

CRTPGM PGM(ILESAMPLE/MATHTEST) MODULE(ILESAMPLE/MATHTEST) BNDSRVPGM(ILESAMPLE/F.MATH)

We now have a program named MATHTEST that is created from one module, and will reference our F.MATH service program instead of attaching a physical copy of it to the program.

Lets verify that using the following command:

DSPPGM PGM(ILESAMPLE/MATHTEST)

When we get to the *MODULE detail of the program object, we see that yes, just the MATHTEST module is bound to this program:

                          Display Program Information 
                                                                 Display 3 of 7 
 Program  . . . . . . . :   MATHTEST      Library  . . . . . . . :   ILESAMPLE  
 Owner  . . . . . . . . :   BVSTONE    
 Program attribute  . . :   RPGLE      
 Detail . . . . . . . . :   *MODULE    


 Type options, press Enter. 
   5=Display description   6=Print description 

                                          Creation  Optimization  Debug 
 Opt  Module      Library     Attribute   Date         Level      Data 
      MATHTEST    ILESAMPLE   RPGLE       04/13/15  *NONE         *NO      
 

Now, if we view the *SRVPGM information we see that we have bound (by reference, not copy) the F.MATH service program:

                          Display Program Information 
                                                                 Display 4 of 7 
 Program  . . . . . . . :   MATHTEST      Library  . . . . . . . :   ILESAMPLE  
 Owner  . . . . . . . . :   BVSTONE    
 Program attribute  . . :   RPGLE      
 Detail . . . . . . . . :   *SRVPGM    


 Type options, press Enter. 
   5=Display 

      Service 
 Opt  Program     Library     Signature 
      F.MATH      ILESAMPLE   A5F14BF0404040404040404040404040 
      QRNXIE      QSYS        D8D9D5E7C9C540404040404040404040 
      QLEAWI      QSYS        44F70FABA08585397BDF0CF195F82EC1 

Another thing to notice is that next to the F.MATH service program we show a signature.  This signature needs to match the signature of the actual F.MATH service program object or the program will crash hard, similar to if you were using a file and there was a level check. 

You're probably thinking at this point "but I like just using option 14 to compile... isn't there an easier way?"

There is an easier way!  And that is by using Binding Directories which will be covered next.


Last edited 04/13/2015 at 14:41:27



bvstone

Implementing a Binding Directory

Posted:

Implementing a Binding Directory

Most of us on the IBM i are familiar with the concept of a Library List and how they work.  

When using modules or service programs a similar concept can be used that makes compiling programs much easier.  These are known as Binding Directories.

A Binding Directory is nothing more than a list of modules and/or service programs.  When you compile your program and that program needs to bind to a module and/or service program you specify a binding directory (or more than one) on the CRTxxxPGM command.  The system will then look through the modules and service programs listed in the binding directory for modules and service programs to bind to your program.

Lets see this in action.  Lets first create a binding directory using the Create Binding Directory (CRTBNDDIR) command:

CRTBNDDIR BNDDIR(ILESAMPLE/MYBNDDIR) TEXT('My First Binding Directory')

Once the binding directory is created, we can work with it using the WRKBNDDIR command:

WRKBNDDIR BNDDIR(ILESAMPLE/MYBNDDIR)

You should be presented with a screen similar to the following:

                         Work with Binding Directories 
             
 Type options, press Enter. 
   1=Create   4=Delete   5=Display   9=Work with binding directory entries 
   13=Change description 

      Binding 
 Opt  Directory   Library     Text 

      MYBNDDIR    ILESAMPLE   My First Binding Directory

If we use option 9 next to our binding directory we are presented with the following screen:

                      Work with Binding Directory Entries 
             
 Binding Directory:   MYBNDDIR       Library:   ILESAMPLE  

 Type options, press Enter. 
   1=Add   4=Remove 

                                                   --------Creation--------- 
 Opt     Object         Type        Library        Date           Time 


   (No binding directory entries for this binding directory.)                 

Because we just created our binding directory there are no entries in it.  Remember, an "entry" will simply be a module or service program.

Lets add our F.MATH module to this binding directory by using option 1 on the first blank entry.  Specify F.MATH for the Object, *MODULE for the Type and ILESAMPLE for the library.  Once added, we should see it in the list:

                     Work with Binding Directory Entries 
            
Binding Directory:   MYBNDDIR       Library:   ILESAMPLE  

Type options, press Enter. 
  1=Add   4=Remove 

                                                  --------Creation--------- 
Opt     Object         Type        Library        Date           Time 

        F.MATH         *MODULE     ILESAMPLE      04/06/15       13:04:37   

Now that we have our F.MATH module in our binding directory, lets try creating our program MATHTEST again, but this time instead of specifying a module or service program to bind to it, let's instead specify our new binding directory.  What's different is in this case we'll get to use the Create Bound RPG Program (CRTBNDRPG) command instead of the sequence of CRTMOD for each module, then CRTPGM to bind the modules into a program:

CRTBNDRPG PGM(ILESAMPLE/MATHTEST) SRCFILE(ILESAMPLE/QRPGLESRC) DFTACTGRP(*NO) BNDDIR(ILESAMPLE/MYBNDDIR)

Once this command is completed, use the DSPPGM command to display your MATHTEST program to verify that it's bound to the F.MATH module and not the F.MATH service program.

DSPPGM PGM(ILESAMPLE/MATHTEST) DETAIL(*MODULE)

Pretty neat, huh?  This is much easier than issuing all the CRTMOD commands and then CRTPGM.  

Now, let's add the F.MATH service program to our binding directory:

                     Work with Binding Directory Entries 
            
Binding Directory:   MYBNDDIR       Library:   ILESAMPLE  

Type options, press Enter. 
  1=Add   4=Remove 

                                                  --------Creation--------- 
Opt     Object         Type        Library        Date           Time 

        F.MATH         *MODULE     ILESAMPLE      04/06/15       13:04:37   
        F.MATH         *SRVPGM     ILESAMPLE      04/07/15       14:38:27  
 

Now, issue the CRTBNDRPG command again to recreate our MATHTEST program and lets see what happens.  Yep, that's right... it failed.  Here is the messages you most likely will see in your job log:

CRTBNDRPG PGM(ILESAMPLE/MATHTEST) SRCFILE(ILESAMPLE/QRPGLESRC) DFTACTGRP(*NO) BNDDIR(ILESAMPLE/MYBNDDIR)                                             
Definition supplied multiple times for symbol '#MATH_GETSUMINT'.           
Program MATHTEST in library ILESAMPLE not created.                         
Compilation failed. Program MATHTEST not created in library ILESAMPLE.

What this error is telling us is that it tried to create the MATHTEST program but it found more than one reference to the #math_getSumInt() procedure.  Because it was using our binding directory it would have found it both in the F.MATH module and the F.MATH service program.  

So, how do we fix this?

The answer will depend on which path you choose for the type of binding you and your shop will use,  Bind by Copy (using Modules) or Bind By Reference (using Service Programs).  Once that decision is made, you'll remove either the module or service program from your binding directory.

For the sake of this series on ILE, let's take the Bind By Reference path and remove the F.MATH module from our binding directory.  Once that is done we can issue the CRTBNDRPG command again and create our program.  Then we can use the DSPPGM command to verify that the F.MATH module is not bound to our program, but the F.MATH service program is.  Later in this series we will go back and look at the benefits and uses of each type of binding and where we've found it's best to use each and under which circumstances.

The last step in our adventure using Binding Directories is taking advantage of using an H-Spec to define the binding directory we want to use.  We simply add one line of code to the top of our MATHTEST program:

H DFTACTGRP(*NO) BNDDIR('MYBNDDIR')                                      
 ****************************************************************        
 * Prototypes                                                            
 ****************************************************************        
 /COPY QCOPYSRC,P.MATH                                                   
 ****************************************************************        
D Sum             S             10i 0                                    
 ****************************************************************        
 /free                                                                   
                                                                         
  Sum = #math_getSumInt(20:21);                                          
                                                                         
  *INLR = *ON;                                                           
 /end-free                                                               

This H-Spec defines some compiling options required when using a binding directory.  This means that we can use this simplified command to create our program:

CRTBNDRPG PGM(ILESAMPLE/MATHTEST) SRCFILE(ILESAMPLE/QRPGLESRC)

You'll notice the DFTACTGRP and BNDDIR parameters can be left off as the H spec is used for those values.  And yes, this means we can even use option 14 next to our program in PDM to compile!

Next, we will look at adding a new procedure to our service program and how that will affect the service program signature.


Last edited 04/21/2015 at 08:17:41



bvstone

Adding a New Procedure to our Module or Service Program

Posted:

Adding a New Procedure to our Module or Service Program

Now comes the time when we will add a new procedure (function) to our module, and then service program.

I always start with the module source itself (instead of the prototype source).  This is because during the creation of the new procedure things may change and that could change the prototype definition.  Here's an example:

     H NOMAIN
      ****************************************************************
      * Prototypes                                                   *
      ****************************************************************
      /COPY QCOPYSRC,P.MATH
      *//////////////////////////////////////////////////////////////*
      * #math_getSumInt - Return the sum of two integers             *
      *//////////////////////////////////////////////////////////////*
     P #math_getSumInt...
     P                 B                   EXPORT
      *--------------------------------------------------------------*
     D #math_getSumInt...
     D                 PI            10i 0
     D  Augend                       10i 0 Const
     D  Addend                       10i 0 Const
      *--------------------------------------------------------------*
      /free

       return (Augend + Addend);

      /end-free
     P #math_getSumInt...
     P                 E
      *//////////////////////////////////////////////////////////////*
      * #math_getDiffInt - Return the difference of two integers     *
      *//////////////////////////////////////////////////////////////*
     P #math_getDiffInt...
     P                 B                   EXPORT
      *--------------------------------------------------------------*
     D #math_getDiffInt...
     D                 PI            10i 0
     D  Minuend                      10i 0 Const
     D  Subtrahend                   10i 0 Const
      *--------------------------------------------------------------*
      /free

       return (Minuend - Subtrahend);

      /end-free
     P #math_getDiffInt...
     P                 E                       

You'll see that in our updated module source we have added a new procedure named #math_getDiffInt which will return the difference of two numbers.

The next step will be updating our /COPY member (P.MATH) with the prototype of our new subprocedure.  I find it's easiest to copy the top portion of the procedure source and simply remove everything except the comments (if needed) and the Procedure Interface (PI) section, then changing the PI to PR.

      *//////////////////////////////////////////////////////////////*
      * #math_getSumInt - Return the sum of two integers             *
      *//////////////////////////////////////////////////////////////*
     D #math_getSumInt...
     D                 PR            10i 0
     D  Augend                       10i 0 Const
     D  Addend                       10i 0 Const
      *//////////////////////////////////////////////////////////////*
      * #math_getDiffInt - Return the difference of two integers     *
      *//////////////////////////////////////////////////////////////*
     D #math_getDiffInt...
     D                 PR            10i 0
     D  Minuend                      10i 0 Const
     D  Subtrahend                   10i 0 Const                

As you can see, the PI section of the module source and the PR section of the prototype are nearly identical.  By copying the PI section for our prototype, this helps cut down on possible keying errors.

Now that we have our new procedure we need to recreate the F.MATH module:

CRTRPGMOD MODULE(ILESAMPLE/F.MATH) SRCFILE(ILESAMPLE/QMODSRC)

Now that we have a new module you may be wondering, is that going to affect anything that is using the F.MATH module or service program?  At this point, the answer is no.  Right now we simply have an updated F.MATH module that includes the new procedure.  

If we wanted our programs to be able to use it, as before, we have two options.  To bind by copy (using the new module) or bind by reference (by updating the service program).

Any program that is using the bind by copy (module) option simply needs to be recompiled so it can grab a new copy of the F.MATH module.  Simple enough, right?  

But, any program that is using bind by reference (service program) needs a couple extra steps before the new procedure is available:  

  • Update our binder language to export the new procedure (Binder Language articles part 1 and part 2)
  • Recreate (or update) our F.MATH service program so that it is using the new version of the F.MATH module
  • Compile our program

When we update our binder language we want to make sure that when we do, we keep the old signature in tact so that any programs that use the updated F.MATH service program will continue to function.  Remember that when we create a program that uses a service program, that program makes note of the current signature of that service program.  

The other option (which we'll call the "brute force" method) would be to recreate the service program with only one signature (the new one that won't match any signature referenced by a program using the service program) and then recompiling every program that uses the service program to force it to get the latest signature.  (whew.. that was a mouthful!)  While this is an option, it isn't always the best, so lets focus on the method that will keep everything working.

Step One - Update The Binder Language

We want to update our binder language so that our newest version of our F.MATH service program will export our new procedure. 

             STRPGMEXP  SIGNATURE('v2.0')                          
             EXPORT     SYMBOL(#math_getSumInt)                    
             EXPORT     SYMBOL(#math_getDiffInt)                   
             ENDPGMEXP                                             
                                                                   
             STRPGMEXP  PGMLVL(*PRV) SIGNATURE('v1.0')             
             EXPORT     SYMBOL(#math_getSumInt)                    
             ENDPGMEXP                                             

The first thing we'll notice is that we have two export blocks now.  The old on (v1.0) also has been changed so that the program level (PGMLVL) keyword is *PRV which will tell the system that this signature is from a previous version of the service program.  The default for this keyword is *CURRENT.  Remember, the current signature is the one any program created will reference.  The previous signature(s) is/are there only for programs that haven't been recompiled since the update.

Step Two - Update the Service Program

When updating our service program we have two options:  delete the old one and create the new one using the Create Service Program (CRTSRVPGM) command or use the Update Service Program (UPDSRVPGM) command.

Following is the command to use for the UPDSRVPGM command:

UPDSRVPGM SRVPGM(ILESAMPLE/F.MATH) MODULE(ILESAMPLE/F.MATH) EXPORT(*SRCFILE) SRCFILE(ILESAMPLE/QSRVSRC) SRCMBR(*SRVPGM)

Once this is done the first thing we'll notice is the service program now has two signatures (but, only one *CURRENT signature)

                     Display Service Program Information 
                                                                Display 1 of 1
Service program  . . . . . . . . . . . . :   F.MATH     
  Library  . . . . . . . . . . . . . . . :     ILESAMPLE  
Owner  . . . . . . . . . . . . . . . . . :   BVSTONE    
Service program attribute  . . . . . . . :   RPGLE      
Detail . . . . . . . . . . . . . . . . . :   *SIGNATURE 

                                 Signatures: 

v2.0             
v1.0            
 

Step Three - Create a New Program

We had our test program MATHTEST before, but for the sake of understanding signatures I wanted to do things a little differently.  So, copy your MATHTEST source to a new program named MATHTEST2 and update it so it looks like the following:

H DFTACTGRP(*NO) BNDDIR('MYBNDDIR')                                                
 ****************************************************************                  
 * Prototypes                                                                      
 ****************************************************************                  
 /COPY QCOPYSRC,P.MATH                                                             
 ****************************************************************                  
D Sum             S             10i 0                                              
D Diff            S             10i 0                                              
 ****************************************************************                  
 /free                                                                             
                                                                                   
  Sum = #math_getSumInt(20:21);                                                    
  Diff = #math_getDiffInt(44:25);                                                  
                                                                                   
  *INLR = *ON;                                                                     
 /end-free                                                                         

Once done we can create our new program.

CRTBNDRPG PGM(ILESAMPLE/MATHTEST2) SRCFILE(ILESAMPLE/QRPGLESRC)

At this point we have the following:

  • A service program with 2 signatures (v1.0 and v2.0)
  • A program named MATHTEST that uses the F.MATH service program and referencing signature v1.0 (and still works without recompiling)
  • A program named MATHTEST2 that uses the F.MATH service program and references the current signature of v2.0

Next we will purposely cause a signature error just so that we are aware of what could happen if things are done wrong. 

 


Last edited 04/23/2015 at 09:28:49



bvstone

Forcing a Signature Error (optional)

Posted:

Forcing a Signature Error (optional)

This next section will be optional since when we are done, we will end up with the same setup as with the previous post.

The purpose of this is to show you what a signature error will look like when it occurs.  But, in order to do that, we need to first change our binder language and then recreate our service program so that it contains only the latest signature.  In other words, we need to purposely do something wrong (which we'd never do normally).

So, first lets copy the member F.MATH in source physical file QSRVSRC to a new member called F.MATHERR (so we know this binder language will cause an error).  Then, let's remove the reference to the previous signature .  When we're done, the source for our F.MATHERR member should look like this:

             STRPGMEXP  SIGNATURE('v2.0')                       
             EXPORT     SYMBOL(#math_getSumInt)                 
             EXPORT     SYMBOL(#math_getDiffInt)                
             ENDPGMEXP                                          

Now, let's update our F.MATH service program using the F.MATTERR binder language so that when done, only one signature will be available.

UPDSRVPGM SRVPGM(ILESAMPLE/F.MATH) MODULE(ILESAMPLE/F.MATH) EXPORT(*SRCFILE) SRCFILE(ILESAMPLE/QSRVSRC) SRCMBR(F.MATHERR)

Once done, if we display the details of our service program we can verify that yes, only one signature has been exported:

                      Display Service Program Information 
                                                                 Display 1 of 1
 Service program  . . . . . . . . . . . . :   F.MATH     
   Library  . . . . . . . . . . . . . . . :     ILESAMPLE  
 Owner  . . . . . . . . . . . . . . . . . :   BVSTONE    
 Service program attribute  . . . . . . . :   RPGLE      
 Detail . . . . . . . . . . . . . . . . . :   *SIGNATURE 

                                  Signatures: 

 v2.0             

Now, if we leave things as they are, our program MATHTEST2 should run without issue since when it was created, it used the v2.0 signature.  Go ahead and run that program to verify.

Program MATHTEST was compiled when only signature v1.0 was exported.  What happens if we call that program now that our F.MATH service program doesn't export the v1.0 signature?

                        Additional Message Information 
            
Message ID . . . . . . :   MCH4431       Severity . . . . . . . :   40 
Message type . . . . . :   Escape                                   
Date sent  . . . . . . :   04/29/15      Time sent  . . . . . . :   10:02:21   
                                                                               
Message . . . . :   Program signature violation.                               
Cause . . . . . :   The source program MATHTEST specifies a signature          
  X'A5F14BF0404040404040404040404040' which is not supported by service        
  program F.MATH.                                                              
Recovery  . . . :   The service program interface has changed. Re-bind source  
  program MATHTEST.                                                            

As we can see, our program crashes and reports a signature violation error.

How could we fix this?  There really are two ways:

  1. When we update a service program that will export more or different procedures than previously, if all possible retain the old signature as type *PRV while also exporting the new signature
  2. If option 1 is not possible, then we will simply need to update our service program and then recompile any program that use our new service program so that it will use the new signature

In our case we're choosing option 1, so lets update our F.MATH service program and put it back to the state it was previously when it had exported 2 signatures:

UPDSRVPGM SRVPGM(ILESAMPLE/F.MATH) MODULE(ILESAMPLE/F.MATH) EXPORT(*SRCFILE) SRCFILE(ILESAMPLE/QSRVSRC) SRCMBR(*SRVPGM)                    

Once this is done we can verify both signatures exist in the service program and that both programs MATHTEST and MATHTEST2 will run without issue.

Next, we will look at the difference between Global and Local Variables when using subprocedures.

 


Last edited 04/29/2015 at 10:11:14




Reply




© Copyright 1983-2024 BVSTools
GreenBoard(v3) Powered by the eRPG SDK, MAILTOOL Plus!, GreenTools for Google Apps, jQuery, jQuery UI, BlockUI, TinyMCE and running on the IBM i (AKA AS/400, iSeries, System i).