Adding an Exit and Extry Subprocedure to Service Programs

Article ID: 55364

Using C++ to Create an Exit Procedure in RPG IV

When I first started writing service programs for RPG xTools (www.rpgxtools.com), I quickly realized that I needed the ability to call a subprocedure when the service program was loaded and again when it was unloaded. Why? This was necessary in order to create control structures (e.g., user spaces), open and close IFS files, allocate and free up memory, and delete some objects I had created.

I hunted around for an API or other mechanism to accomplish this task. The only thing that came close was a CEE API that could set a termination handler -- a subprocedure that is called when the activation group in which the service program is running is closed down. The API CEE4RAGE, and its replacement CEE4RAGE2, is used to identify a subprocedure to be called when the activation group in which a service program or program is running is closed down.

In my latest book, RPG TNT: 101 Dynamite Tips 'n Techniques with RPG IV, I documented the CEE4RAGE and CEE4RAGE2 APIs in Tip #72. The prototypes for these two APIs, from my book, are reproduced here:

     /IF NOT DEFINED(CEE4RAGE)                                    
     /DEFINE CEE4RAGE                                             
    D CEE4RAGE        PR                  extProc('CEE4RAGE')     
    D  myExitProcPtr                  *   PROCPTR CONST           
    D  fc                           12A   OPTIONS(*OMIT:*VARSIZE) 
    D CEE4RAGE2       PR                  extProc('CEE4RAGE2')    
    D  myExitProcPtr                  *   PROCPTR CONST           
    D  fc                           12A   OPTIONS(*OMIT:*VARSIZE) 
     /ENDIF                                                       

Where's the Init Routine?

The CEE4RAGEx APIs provide an easy way to set up an exit subprocedure, but what about setting up an entry subprocedure? What I wanted for RPG xTools was a way to call a specified subprocedure whenever the RPG xTools service program was loaded and whenever it was unloaded. Searching through the API documentation yielded no results and no clues, so I asked around.

One suggestion I received was from Barbara Morris, a highly respected IBMer who helped develop the RPG IV compiler. She suggested using a static C++ variable with an external procedure (or "hook") that would be the RPG IV subprocedure name I wanted to call at startup time -- something like this:

  extern "C"  { int RPGInzSrvPgm(void); }
  static int dummy_InzVar = RPGInzSrvPgm();

Using PDM option 15 to compile this two-line C++ source member (SEU source type: CPP) and bind it to my service program solves the problem. All I need to do is write a subprocedure named RPGInzSrvPgm in my service program (in RPG IV syntax), and it would have the equivalent of a *INZSR startup routine but in the form of a subprocedure.

But that wasn't good enough for me. I wanted to extend this capability to provide a single method for an Entry and an Exit routine for service programs. Having been a closet C++ programmer for about the last 15 years or so, I knew I could use C++ constructors and destructors to extend Barbara's suggestion.

I realize that most RPG IV programmers do not know nor do they care about the C or C++ language, so I won't go into too much detail. I've made the code fairly static (no changes should be necessary by you); simply type it in, compile it as a *MODULE, and then bind to it when creating your *SRVPGMs. Compile this source code once and then forget about it.

The C++ source code for a source member/module called ENTRYEXIT "Service Program Entry/Exit" follows. Type it in using SEU or WDSc exactly as shown. Make the SEU source type "CPP". If you're using WDSc, make the file extension ".CPP".


     //  MODULE: ENTRYEXIT
     //  Compile using CRTCPPMOD.
     //  Bind to any *PGM or *SRVPGM
     //  Copyright 2007 by Robert Cozzi, Jr.
     //   All rights reserved. 
     //   No warranty is expressed or implied.

     //  There is no need to modify this code.
     //   You must specify the module name on
     //   the module parm of CRTSRVPGM, for example: 
     //     e.g CRTSRVPGM SRVPGM(x) MODULE(X  ENTRYEXIT)
     //  In the RPG source code in any one of the
     //  service program's modules, create a 
     //  subprocedure named RPG_ENTRY and RPG_EXIT.
     //  Be sure to export both of them.

     //  Bob Cozzi's Entry/Exit Object for *SRVPGM's.
    extern "C"  int RPG_ENTRY(void);
    extern "C"  int RPG_EXIT(void);

class Entry_Exit
{
public:
   int m_nEntry;
   int m_nExit;

// Construction
public:
    Entry_Exit(void);   // standard constructor
   ~Entry_Exit(void);
};

  Entry_Exit::Entry_Exit(void) {
        m_nEntry = RPG_ENTRY();
  }

  Entry_Exit::~Entry_Exit() {
        m_nExit = RPG_EXIT();
  }

  static  Entry_Exit  My_Entry_Exit;

As I mentioned, enter this source code into SEU or WDSc and compile it using PDM option 15 (CRTCPPMOD). This will create a *MODULE object that you need to save and use when building your own service programs.

What's in This C++ Code?

Let me explain a little bit about what's going on in the C++ code. If you are not interested in knowing the details of the C++ source code, you can skip this part and jump ahead to the next section.

The first two lines of code are as follows:

    extern "C"  int RPG_ENTRY(void);
    extern "C"  int RPG_EXIT(void);

This is how you identify an external subprocedure in C++. The word "extern" means the subprocedure is declared somewhere else (not in this source member), and it is followed by the prototype for the subprocedure. The equivalent prototypes in RPG IV syntax would be similar to the following:

     D RPG_entry       PR            10I 0 extProc('RPG_ENTRY')
     D RPG_exit        PR            10I 0 extProc('RPG_EXIT')

These prototypes identify the subprocedure that will be called at program startup and program exit. The RPG IV prototypes must export the exact upper/lowercase subprocedures names as identified by the extern statement in the C++ code. That's how they're connected at "bind time."

C++ names are case-sensitive (as are all ILE subprocedure names); however, RPG IV tends to fold everything to upper case automatically unless it is enclosed in quotes. To make things easier, the C++ names are in all upper case. In fact, in the RPG IV source code, the EXTPROC keyword and parameter are optional since RPG_entry and RPG_exit are exported as subprocedures with their names automatically converted to RPG_ENTRY and RPG_EXIT.

You must code the implementation for the subprocedures named RPG_ENTRY and RPG_EXIT and export them using the EXPORT keyword on the Procedure specification. These subprocedures have no parameters and a return value of a 10i0 (four-byte integer). However, the C++ code as presented here does not currently use that return value for anything, so returning 0 or 1 or any other number is acceptable.

The next two C++ statements are as follows:

class Entry_Exit
{

This declares a class named Entry_Exit. A C++ class is sort of a hybrid between a data structure "template" and a subprocedure. A class is similar to a data structure in RPG IV; however, C++ classes can contain both data (subfields) and "methods" (subprocedures).

The left bracket following the class name starts the block of code that defines the class itself; a right bracket (shown later) identifies the end of the class definition. Brackets are used everywhere in C and C++; they are also used to indicate the start and end of functions, structures, and blocks of code.

The next few C++ statements declare the members of the class. In this case, we have two member variables (subfields) and two methods (subprocedures) as follows:

// Member variables
public:
   int m_nEntry;
   int m_nExit;

// Construction/Destruction
public:
    Entry_Exit(void);   // standard constructor
   ~Entry_Exit(void);
};  // end of C++ Class definition

These final few lines complete the class definition. The first "public:" tag indicates that the member variables (subfields) are exposed and may be modified and read from outside of the class itself. The default is "private:" and blocks outside access to these variables. The two member variables are four-byte integers. They will be set to whatever value is returned by the RPG_ENTRY and RPG_EXIT subprocedures.

Other than storing the returned values, they are not used for any particular purpose, but I wanted to illustrate a simple "member variable" for this class in case you have to modify the code later.

The second "public:" tag isn't really necessary but is often used for clarity. It specifies that the methods (subprocedures) used by this class are also public. All classes have two default methods: (1) a constructor and (2) a destructor.

The constructor is automatically called when a class is created. In RPG IV terms, it would be similar to having a subprocedure called automatically when a data structure declared with the LIKEDS keyword is initialized at program startup. Perhaps a good analogy is that a constructor is similar to the *INZSR subroutine that is called when a program is evoked. The constructor can be used to initialize member variables (subfield) and perform tasks that may be needed before the rest of the program takes over. The difference is that the constructor, in practice, is usually limited in scoped to manipulating member variables of the class.

The destructor is the complement of the constructor. It is automatically called when the class instance is being destroyed — typically this happens when the program is ending or when the activation group in which the program or service program is running is destroyed. There are other ways to delete or free up data in C and C++, but I won't get into those techniques in this article.

There is no equivalent of the destructor in RPG IV, but the CEE4RAGE API performs a similar function.

Everything in the C++ code up to this point is similar to prototyping in RPG IV. That is, it really isn't executable code -- just the declaration of the definition of the class.

The body or implementation of the class is where the executable code resides. Our Entry_Exit class is a very simple class definition; its implementation is defined as follows:

  Entry_Exit::Entry_Exit(void) {
        m_nEntry = RPG_ENTRY(); // Call Entry subproc
  }

  Entry_Exit::~Entry_Exit() {
        m_nExit = RPG_EXIT();  // Call Exit subproc
  }

This code implements two member functions (subprocedure-like functions in C++ syntax). The first one is the constructor, and the second is the destructor. The constructor always has the same name as the class itself, so Entry_Exit is the constructor name. The destructor also has the same name as the class; however, that little squiggly thing (the grave accent) prefixes the destructor's name, since even in C++ you can't have two identically named subprocedures.

Going back to the original source I posted at the start of this article, the last line of C++ code appears as follows:

  static Entry_Exit  My_Entry_Exit;

This declares an actual instance of the Entry_Exit class. This is similar to declaring a new data structure using the LIKEDS keyword. The instance name is My_Entry_Exit. It contains the STATIC keyword to ensure that the new variable is created in static storage. This causes it to be created/initialized when the program is loaded. A contrived but similar statement in RPG IV might look like this:

     D My_Entry_Exit    DS                  LikeDS(Entry_Exit) STATIC

If we look at the constructor once more, we see that the function named RPG_ENTRY() is called. Since the constructor is automatically called upon instantiation of the class, and the class (in this example) is instantiated when the program is loaded, the constructor is called when the program is initially called. Therefore, the RPG_ENTRY subprocedure in our service program is called when a program using this service program is initially called.

Likewise, the destructor contains a call to RPG_EXIT(). Since the destructor is automatically called when the program containing the instance of the class is destroyed or unloaded from memory (in ILE, this mean the activation group is destroyed), our RPG_EXIT() function is called when the service program containing it is unloaded.

Connecting It to RPG IV

So how do we connect this C++ module with RPG IV and have it automatically call our own Entry and Exit subprocedures?

The first step is to create two subprocedures: one named RPG_ENTRY() and another one named RPG_EXIT(). In RPG IV, this is incredibly easy:


     P RPG_ENTRY        B                   EXPORT
     D RPG_ENTRY        PI            10I 0
      /free
             // TODO: Insert start up code here.
             return 1;
      /end-free
     P RPG_ENTRY        E

     P RPG_exit        B                   EXPORT
     D RPG_exit        PI            10I 0
      /free
             // TODO: Insert cleanup code here.
                return 1;
      /end-free
     P RPG_exit        E

Add these two subprocedures to a source member that is used to create a service program. Then create the service program, binding it with the ENTRYEXIT module.

Although destroying an activation group will do some cleanup for you (it was a key reason why IBM created activation groups), using the exit subprocedure gives you the ability to (for example) delete user spaces, free up allocated memory, close files on the IFS, and write completion information to files and logs before closing down, as well as delete other things you created during the program or job that are no longer need.

Compiling

To compile the ENTRYEXIT source member, use the CRTCPPMOD command as follows:

CRTCPPMOD  MODULE(ENTRYEXIT) OUTPUT(*PRINT)

The OUTPUT(*PRINT) option is required because, as I like to say, "C programmers don't check their code, they just compile it." So by default no compiler listing is produced. Using the OUTPUT(*PRINT) parameter forces a compiler listing. If you don't care about the compiler listed, omit this parameter from the CRTCPPMOD command.

To connect a service program to the ENTRYEXIT module, explicitly specify it on the MODULE parameter of the SRCSRVPGM command. To compile an RPG-based service program that uses the ENTRYEXIT module, issue the following two commands:

CRTRPGMOD MODULE(MYSRVPGM) 
CRTSRVPGM  SRVPGM(MYSRVPGM) MODULE(MYSRVPGM ENTRYEXIT) ACTGRP(*NEW)

It is unfortunate that you can't put the ENTRYEXIT module name into a binding directory and put that binding directory name on the Header specification of the RPG IV source — well you can, but it doesn't work.

One of the best things about binding directories is that the binder is a so-called "smart binder." That is, only the modules and service programs listed in the binding directory containing subprocedures (or exported fields) explicitly or indirectly called from the main module are included in the resulting *PGM or *SRVPGM object. This allows you to put a bunch of stuff into a binding directory, and only that which is needed is actually included into the resulting program object. This gives you much learner programs.

Since there is no direct call to a function in the ENTRYEXIT *MODULE, placing it in a binding directory will not cause it to be included. Therefore, the RPG_ENTRY and RPG_EXIT routines would never be performed. So always make sure you explicitly specify the ENTRYEXIT module name on the MODULE parameter when you want to use it.

Sounds like we need a *FORCEBIND attribute on binding directories to force inclusion of the modules, even when their subprocedures are not directly referenced by other modules. I would really love that for creating service programs. Today, I have to list all 10,000 module names on the module parameter when I create a service program (exaggerating a bit on the 10,000 module issue, but you get the point).

Recommendations

With *SRVPGMs, there is no *INZSR subroutine available; therefore, the RPG_ENTRY routine is beneficial for performing all the things you might normally do in the *INZSR subroutine.

With regular RPG programs, the *INZSR subroutine is available, so the advantage of RPG_ENTRY is substantially reduced or nearly eliminated. This is a good thing because, as it turns out, using the RPG_ENTRY method does not work properly with *PGM objects.

My observation is that although RPG_ENTRY is called and runs correctly when used in a *PGM object, any changes to variables or file opens that are performed are lost when the actual program's Detail calcs start running.

For example, the field INVNBR might be initialized to 1001 by the compiler, as follows:

     D  invNbr                        7P 0  Inz(1001)

Then in an RPG_ENTRY subprocedure, the INVNBR field is set to some other value, as follows:

     P RPG_ENTRY        B                   EXPORT
     D RPG_ENTRY        PI            10I 0
      /free
             invNbr = 70816;
             return 0;
      /end-free
     P RPG_ENTRY        E

During debug, if you set a breakpoint at the first line of the RPG_ENTRY subprocedure and look at INVNBR, it will be equal to 1001. Then after you assign the new value, 70816 in this example, the value of INVNBR is now 70819.

However, when the Detail calcs of this program start running, the field INVNBR is set to 1001, not 70189. I also tried to perform a file open to a User Control Open (USROPN) file. The file was open, as it displayed as being open on the Open File List of the WRKJOB menu; however, the %OPEN built-in function and READ operations all failed to recognize that the file was already open.

So I use RPG_ENTRY and RPG_EXIT with *SRVPGMs, but for *PGM objects, I use only RPG_EXIT and avoid RPG_ENTRY.

I recommend using the RPG_EXIT routine to clean up anything that isn't automatically cleaned up by ILE during destruction of the activation group. This includes things such as closing IFS files; deleting data queues, user spaces, work files; and deallocating memory. (Yes, I know memory is cleaned up by ILE when the activation group is closed down. But I come from the school of cleaning up after yourself.)

Caveat: Remember that these routines are called when the service program is initially loaded and when it is unloaded. Service programs stay in memory until their activation group is destroyed. If the service program is running in a *NEW activation group (sometimes called a dynamic activation group), the entry/exit subprocedures will be called each time the service program is loaded. If, however, the service program is running in a named activation group (also known as a persistent activation group), then the entry/exit subprocedures are called only once (under normal conditions), regardless of the number of times your program is started or ended in the job. Of course, this is scoped to the job, so each user job using the service program will cause the entry/exit subprocedures to be called.

As of today, sadly, there is no way to always call a startup and exit subprocedure whenever a program or service program is evoked -- but the technique illustrated in this week's RPG Coder gives us some level of function that we previously did not have.

ProVIP Sponsors

ProVIP Sponsors