ProvatoSys   
w w w . P r o v a t o S y s . c o m
M i c h a e l   C a t a l a n i
9 0 1 . 5 8 1 . 8 7 9 1

 

Pic    Services    Contact    Code    Tips


Creating Bindable System Commands For RPG Applications

This example builds upon the system() api example, which I suggest reading first if you haven't already.

Take a look at the application code below, specifically the section in purple.

-------------------------------------------------------------------

 h dftactgrp(*no) bnddir('CF')

  /copy *libl/qrpglesrc,Command_p

 d CPF_Error s 7a

  /free

   Command_DlyJob( 0:'04:00:00' );

   Command_StrSbs('QINTER');
 
   return;

--------------------------------------------------------------------
 

This program will wait until 4am, and then start the QINTER subsystem.  Note how neat, clean, and easily interpreted it is. This is a fully functional program, and no other code is needed. But how do we pull this off? And why aren't we using the system() api in our program to do this? 

While the system() api is nice, and is a much better alternative to QCMDEXC, an even better approach would be to create a function for each of the commands we want to use in a program. By using a good naming convention, we can easily understand what the function is doing, even if we've never seen it before. The naming convention I am using is:

Prefix Command_    (States that the function will execute a system command)
Suffix  XXXXXX      (The actual OS command name)

So function Command_DlyJob  will execute the DLYJOB command. The function Command_WrkActJob executes the WRKACTJOB command.

If we wanted to execute the WRKACTJOB command within an RPG program, we would simply code:

Command_WrkActJob();

Or, if we want to execute the WRKACTJOB command, and send the output to the printer, we can code:

Command_WrkActJob('*PRINT');

And now, a word of advice; Although I started off this section with the DLYJOB example, I would highly suggest NOT using the DLYJOB command, or any other system command that has been replaced by an api.  For instance, the sleep() and usleep() api's do a much cleaner delay, while allowing you to delay for a much shorter time than the DLYJOB command. So be careful that you don't go overkill on creating commands that should be replaced by an api.  If you are going to go through the effort of coding a function for a system command, you can just as easily code a prototype for an api call.

And now, we return you to the topic at hand.

For those of you who aren't as familiar with the ILE environment, you may be wondering where all the underlying code for our functions are located. And what happened to the Command_System function that we were originally talking about?

The code that actually creates each command string is neatly and safely tucked away in a service program that is bound to this application. Notice the "H" spec specifies BNDDIR('CF').  The CF binding directory is my own directory that contains service programs of my custom written functions. One of the service programs, called Command_S, is located in there. By using and binding to a service program, the actual application program is remarkably simply, short, and to the point. This makes it easily readable, and it's very easy to maintain.

So let's build this Service program. We first need a source member for the prototypes, which will be used by both the service program AND any application program that uses these functions.

A quick word about the naming convention for my source files and service programs.  We will need a source member for the prototypes, and source member for the procedure interfaces, a source member for the service program, and a source member for the binding source.

The prototypes, procedure interfaces, and service programs are all located in the QRPGLESRC file, with member names:

Prototype - Command_P
Procedure Interface - Command_I
Service Program - Command_S

The binding source is located in the QSRVSRC file, and is the same name as the service program: Command_S. This is so that I can compile the service program using the defaults, and not have to specify a specific binding source member.

Now let's enter the source member I named the source file Command_P.   I have included prototypes for the STRSBS, ENDSBS, SNDMSG, WRKACTJOB, and DLYJOB system commands.

--------------------------------------------------------------------------------------------

       /if defined(Command_p)
        /eof
      /endif

      /define Command_p

     d No_Error        s              7    inz(*blanks)

        //* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // Command_DlyJob  - Execute DLYJOB Command
        //
        //   Parameter 1: Delay Time (1-999999 seconds)
        //   Parameter 2: Resume Time (xx:xx:xx)optional
        //
        //    Returns the 7 digit CPF Error
        //
        //* * * * * * * * * * * * * * * * * * * * * * * * * * * *

     d Command_DlyJob...
     d                 pr             7a
     d  DlyJob_DelayTime...
     d                                6s 0 const
     d  DlyJob_ResumeTime...
     d                                8a   varying const options(*nopass)

        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_QCmdExc - Execute A System Command
        //
        //   ?Parameter 1: String that contains the command to process
        //
        //   ?Imports the Error Message
        //
        //    Returns 0-No Error  1-Null CommandString  2-CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *

     d Command_System...
     d                 pr            10i 0 ExtProc('system')
     d  CommandString                  *   value options(*String)


        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_SndMsg  - Execute SNDMSG Command
        //
        //   ?Parameter 1: Message To Send
        //   ?Parameter 2: User ID To Send Message To
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *

     d Command_SndMsg...
     d                 pr             7a
     d  SndMsg_Msg                  512a   varying const
     d  SndMsg_ToUsr                 10a   varying const

        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_StrSbs  - Execute STRSBS Command
        //
        //   ?Parameter 1: Subsystem Name To Start
        //   ?Parameter 2: Library Name Of Subsystem (optional)
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *

     d Command_StrSbs...
     d                 pr             7a
     d  StrSbs_Subsystem...
     d                               10a   varying const
     d  StrSbs_Library...
     d                               10a   varying const options(*nopass)

        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_EndSbs  - Execute ENDSBS Command
        //
        //   ?Parameter 1: Subsystem Name To End
        //   ?Parameter 2: How To End Subsystem (*CNTRLD or *IMMED)
        //   ?Parameter 3: Delay Time (*NOLIMT or 0-99999)
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *

     d Command_EndSbs...
     d                 pr             7a
     d  EndSbs_Subsystem...
     d                               10a   varying const
     d  EndSbs_EndMethod...
     d                                7a   varying const options(*nopass)
     d  EndSbs_DelayTime...
     d                               11a   varying const options(*nopass)

        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_WrkActJob - Execute DLYJOB Command
        //
        //   ?Parameter 1: Output Type (* or *PRINT)
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *

     d Command_WrkActJob...
     d                 pr             7a
     d  WrkActJob_Output...
     d                                6a   const options(*nopass)
 

-----------------------------------------------------------------------------------

 

 

And here's the procedure interface's, which is in a source file called Command_I:

 

 


      /if defined(Command_i)
        /eof
      /endif

      /define Command_i

     d CPF_Error       s              7a   Import('_EXCP_MSGID')
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_DlyJob  - Execute DLYJOB Command
        //
        //   ?Parameter 1: Delay Time (1-999999 seconds)
        //   ?Parameter 2: Resume Time (xx:xx:xx)optional
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
     p Command_DlyJob...
     p                 b                   Export
     d Command_DlyJob...
     d                 pi             7a
     d  DlyJob_DelayTime...
     d                                6s 0 const
     d  DlyJob_ResumeTime...
     d                                8a   varying const options(*nopass)
     d ReturnCode      s             10i 0
     d CommandString   s          32767a

      /free
       if %parms = 1;
         CommandString = 'DLYJOB DLY(' + %char(DlyJob_DelayTime) + ')';
       else;
         CommandString = 'DLYJOB RSMTIME(''' + %trimr(DlyJob_ResumeTime)+''')';
       endif;

         return  Command_CheckError(Command_System(%trimr(CommandString)));

      /end-free
     p                 e


        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_SndMsg  - Execute SNDMSG Command
        //
        //   ?Parameter 1: Message To Send
        //   ?Parameter 2: User ID To Send Message To
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *

     p Command_SndMsg...
     p                 b                   Export
     d Command_SndMsg...
     d                 pi             7a
     d  SndMsg_Msg                  512a   varying const
     d  SndMsg_ToUsr                 10a   varying const

     d ReturnCode      s             10i 0
     d CommandString   s          32767a

      /free
         CommandString = 'SNDMSG MSG(''' + %trimr(SndMsg_Msg) + ''') +
                          TOUSR(' + %trimr(SndMsg_ToUsr) + ')';
         ReturnCode = Command_System(%trimr(CommandString));
         return  Command_CheckError(Command_System(%trimr(CommandString)));

      /end-free
     p                 e

        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_StrSbs  - Execute STRSBS Command
        //
        //   ?Parameter 1: Subsystem Name To Start
        //   ?Parameter 2: Library Name Of Subsystem (optional)
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *

     p Command_StrSbs...
     p                 b                   Export
     d Command_StrSbs...
     d                 pi             7a
     d  StrSbs_Subsystem...
     d                               10a   varying const
     d  StrSbs_Library...
     d                               10a   varying const options(*nopass)

     d ReturnCode      s             10i 0
     d CommandString   s          32767a

      /free
         CommandString = 'STRSBS SBSD(';
         if %parms > 1;
           CommandString = %trim(CommandString) + %trim(StrSbs_Library);
         else;
           CommandString = %trim(CommandString) + '*LIBL';
         endif;
         CommandString = %trim(CommandString) + '/' + %trim(StrSbs_Subsystem)+
                         ')';
         return  Command_CheckError(Command_System(%trimr(CommandString)));

      /end-free
     p                 e

        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_EndSbs  - Execute ENDSBS Command
        //
        //   ?Parameter 1: Subsystem Name To End
        //   ?Parameter 2: How To End Subsystem (*CNTRLD or *IMMED)
        //   ?Parameter 3: Delay Time (*NOLIMT or 0-99999)
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *

     p Command_EndSbs...
     p                 b                   Export
     d Command_EndSbs...
     d                 pi             7a
     d  EndSbs_Subsystem...
     d                               10a   varying const
     d  EndSbs_EndMethod...
     d                                7a   varying const options(*nopass)
     d  EndSbs_DelayTime...
     d                               11a   varying const options(*nopass)

     d ReturnCode      s             10i 0
     d CommandString   s          32767a

      /free
         CommandString = 'ENDSBS SBS(';
         CommandString = %trim(CommandString) + %trim(EndSbs_Subsystem) +')';
         if %parms > 1;
           CommandString = %trim(CommandString) + ' OPTION(' + %trim(
                           EndSbs_EndMethod) + ')';
         endif;
         if %parms > 2;
           CommandString = %trim(CommandString) + ' DELAY(' + %trim(
                           EndSbs_DelayTime) + ')';
         endif;

         return  Command_CheckError(Command_System(%trimr(CommandString)));

      /end-free
     p                 e

        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_WrkActJob - Execute DLYJOB Command
        //
        //   ?Parameter 1: Output Type (* or *PRINT)
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
     p Command_WrkActJob...
     p                 b                   Export
     d Command_WrkActJob...
     d                 pi             7a
     d  WrkActJob_Output...
     d                                6a   const options(*nopass)
     d ReturnCode      s             10i 0
     d CommandString   s          32767a

      /free
         CommandString = 'WRKACTJOB';

         if %parms = 1;
           CommandString = %trim(CommandString) + ' OUTPUT(' + %trim(
                          WrkActJob_Output) + ')';
         endif;
         return  Command_CheckError(Command_System(%trimr(CommandString)));
      /end-free
     p                 e
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
        //
        // ?Command_CheckError
        //
        //   ?Parameter 1: Output Type (* or *PRINT)
        //
        //    Returns the 7 digit CPF Error
        //
        //?* * * * * * * * * * * * * * * * * * * * * * * * * * * *
     p Command_CheckError...
     p                 b                    export
     d Command_CheckError...
     d                 pi             7a
     d ReturnCode                    10i 0  const

      /free
         if ReturnCode = 0;
           return No_Error;
         else;
           return CPF_Error;
         endif;

      /end-free
     p                 e  
                                                                                                                                                    

In the code for each function, you will notice that I am building the command string for each command. Here's the code snippet for the Command_WrkActJob function:

         CommandString = 'WRKACTJOB';

         if %parms = 1;
           CommandString = %trim(CommandString) + ' OUTPUT(' + %trim(
                           WrkActJob_Output) + ')';
         endif;

Each of the functions does something similar to this, but each is different based upon the number of parameters the command can accept.

On each command, I call a function called Command_CheckError. This function's main purpose is to check the ReturnCode and determine if we need to pass the CPF_Error field, or a blank field. 

         if ReturnCode = 0;
           return No_Error;
         else;
           return CPF_Error;
         endif;

Basically, this code gets invoked by each function.  If the ReturnCode of the call o the system() api is 0, then we return as blank error id.  If the ReturnCode is not 0, then we pass the CPF_Error field, which contains the message id of the error.  We have to do this test because the system will not automatically blank out the CPF_Error field. if no error was encountered. So we have to check the ReturnCode from the system() api, and either return the CPF_Error field or the No_Error field.

If the Return_Code was not 0, then I return the value of CPF_Error, which will contain the error id that we imported in from _EXCP_MSGID. (Notice that the import is at the top of our procedure interface code.

The source for the service program is a whopping 3 lines of code. Notice that we bind to QC2LE directory, because our service program contains calls to apis, ie: the system() api, which is located here. 
 

 h nomain bnddir('QC2LE')
/copy *libl/qrpglesrc,System_p
/copy *libl/qrpglesrc,System_i
And here's the binder source, which I have in a source file named System_S in the QSRVSRC file.
STRPGMEXP PGMLVL(*CURRENT)
  EXPORT SYMBOL(Command_DlyJob)
  EXPORT SYMBOL(Command_StrSbs)
  EXPORT SYMBOL(Command_EndSbs)
  EXPORT SYMBOL(Command_SndMsg)
  EXPORT SYMBOL(Command_WrkActJob)
ENDPGMEXP 

Now, all we have to do is to create the program module, and then create the service program. Then we need to add this service program to our binding directory.

CRTRPGMOD MODULE(XXXXXXXXXX/COMMAND_S) SRCFILE(XXXXXXXXXX/QRPGLESRC) DBGVIEW(*ALL)

Replace the XXXXXXXXXX with the library name where the source files are. You will also need this library in yourlibrary list so that the /copy members will be located.

CRTSRVPGM SRVPGM(XXXXXXXXXX/COMMAND_S)

You will then need to add this service program to a binding directory using the ADDBNDDIRE command.

By creating a service program of system commands, you only need to create each command once.  Applications can bind to this service program, and the resulting code is very simple to understand, easy to maintain, and extra-ordinarily clean.