Let’s ‘C’

I have been programming ‘C’ on the IBM i for many years in total deference to RPG. I have had to delve into RPG a couple of times to help out at a couple of clients but have never really done anything of any substance. For me ‘C’ can do everything that I need on the IBM i plus it allows me to work on other platforms and the skills I have help me use other languages such as PHP and JavaScript for developing the web based interfaces for IBM i applications.

I would like to get more people to see the value and ease of programming on the IBM i  and hopefully encourage others to step outside the RPG camp where appropriate. I say this because while RPG is a great language for the IBM i it doesn’t allow you to move your skills to other platforms. However IBM i does have an Ace up its sleeve, its called ‘ILE’ (Integrated Language Environment) which allows people to share code across many languages in the same program. That is where we hope to spend a lot of the time over the coming months showing how we can leverage the ILE to our advantage. You never know maybe we will get some RPG programmers to add code to our project.

So the plan it to develop a suite of tools that people can use within their businesses that are based on good development practices and utilise many of the features the IBM i has to offer. Many of these tools are ones that we have created in response to an internal need as we have developed our products. We will try to make them as robust as possible but in the end we expect others to build in much of the checking once we have supplied the foundations. The first objective is to show how to use Service Programs and some simple programs that will allow them to be automatically rebuilt when required. We will also look at using UIM as the 5250 interface for menu’s and panel groups as we believe this provides a better set of standards for the interfaces.

So the first project is going to build out the environment and set up the basics required for moving forward, our plan is to publish the code in GitHub but the process of moving the code to GitHub is not going to be covered as we feel that is an entire project on its own. We hope that others will get involved and add to the code once we get this set up but our experience has been that this is unlikely.

So the first this we need to do is create the development environment, we are going to be using RDI but you can use what ever tooling you wish. I would suggest against SEU very strongly as the code will make editing in that environment very challenging so pick up on one of the free editors out there such as NotePadd++ (with Liams plugins) or his latest ILE Editor?

We are going to use OSLIB as our source library and OSPGM as the target for the program objects etc. You can create what ever naming convention you wish. Within that we are going to create 5 source files, H for the C headers, QCLSRC for the CL source, QCMDSRC for the Command source, QCSRC for the C source and QUIMSRC for the UIM interface source plus the default message queue.
CRTLIB LIB(OSLIB) TEXT(‘Open Source Library’)
CRTMSGQ MSGQ(OSLIB/OSMSGQ) TEXT(‘Default Message Queue’)
CRTSRCPF FILE(OSLIB/H) RCDLEN(150) TEXT(‘C Source Header’)
CRTSRCPF FILE(OSLIB/QCLSRC) RCDLEN(150) TEXT(‘CL Source File’)
CRTSRCPF FILE(OSLIB/QCMDSRC) RCDLEN(150) TEXT(‘CMD Source File’)
CRTSRCPF FILE(OSLIB/QCSRC) RCDLEN(150) TEXT(‘C Source File’)
CRTSRCPF FILE(OSLIB/QUIMSRC) RCDLEN(150) TEXT(‘UIM Source File’)
CRTLIB LIB(OSPGM) TEXT(‘Open Source Program Library’)

We feel that before we start this part of the project we should explain a little bit more about Service Programs and why we are going to use them.  A Service program is a collection of ‘functions’ (sub routines) that reside in a compiled object. The compiled object cannot be run as we would run a normal (C,CLP or RPG etc) program because it has no entry point. Every function we will develop will be an entry (start) point where as in a ‘normal’ program we have a known entry point that is called every time the program is invoked (in ‘C’ programs its the main() function).. These service programs export some or all of the functions to allow other programs to use them (we generally export all but you can have some functions which are not allowed to be used outside of the service program). For a program to use these functions we have to tell the compiler that while it does not have all of the code for these functions as it generates the program object we have a link to that code which the program will bind to. This link has a ‘signature’ which describes how the function will be access and the parameters it requires etc.  This provides us with a multi use object that many programs can access as a single instance, thereby reducing the size of our programs and memory requirements. It also means we have one copy of code to update should that functionality need to be changed.

There are a lot of other requirements for using Service Programs effectively especially when it comes to changing the functionality provided by the Service Program, we will cover that as we move forward and add functionality to the Service Programs.  

All applications need to send messages to the user so the first Service Program we are going to create is associated with that. It will have a number of ‘functions’ within it that can be called from other ‘linked programs’.  We will develop and number of programs which will ease the build of the service programs in the future, so while they are of limited use at the moment we will see the benefits of them later. Now we can start adding a couple of new members to the files which will hold the source code that will eventually be used to build the service program completely. It is important that we set the source source type correctly as it will ensure the compiler is correctly identified etc. 
ADDPFM FILE(OSLIB/QCSRC) MBR(MSGFUNC) TEXT(‘Message Function’) SRCTYPE(C)
ADDPFM FILE(OSLIB/H) MBR(MSGFUNC) TEXT(‘Message Functions Header’) SRCTYPE(C)
Now lets add some source to those files.
// QCSRC/MSGFUNC                                                                              
// Copyright (c) 2018 Chris Hird                                                 
// All rights reserved.                                                          
//                                                                               
// Redistribution and use in source and binary forms, with or without            
// modification, are permitted provided that the following conditions            
// are met:                                                                      
// 1. Redistributions of source code must retain the above copyright             
//    notice, this list of conditions and the following disclaimer.              
// 2. Redistributions in binary form must reproduce the above copyright          
//    notice, this list of conditions and the following disclaimer in the        
//    documentation and/or other materials provided with the distribution.       
//                                                                               
// Disclaimer :                                                                  
// This code is distributed in the hope that it will be useful,        
// but WITHOUT ANY WARRANTY; without even the implied warranty of                
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                             

#include <H/MSGFUNC>                        // MsgFunc header 

// function snd_error_msg()
// Purpose: Forward an Error Message to the message queue.
// @parms
//      Error Code Structure
// returns void

void snd_error_msg(Os_EC_t Error_Code) {
int data_len = 0;
char Msg_Type[10] = "*INFO     ";           // msg type
char Msg_File[20] = "QCPFMSG   *LIBL     "; // Message file to use
char Msg_Key[4] = {' '};                    // msg key
char QRpy_Q[20] = {' '};                    // reply queue
Os_EC_t E_Code = {0};                       // error code struct

Error_Code.EC.Bytes_Provided = _ERR_REC;
data_len = Error_Code.EC.Bytes_Available - 16;  // BA + BP +msgid +rsvd
QMHSNDM(Error_Code.EC.Exception_Id,
        Msg_File,
        Error_Code.Exception_Data,
        data_len,
        Msg_Type,
        _DFT_MSGQ,
        1,
        QRpy_Q,
        Msg_Key,
        &E_Code);
if(E_Code.EC.Bytes_Available > 0) {
   // if we get an error on sending the message send it
   snd_error_msg(E_Code);
   }
return;
}                                 
This is a pretty simple function, we are going to pass in a single parameter which is a structure of type ‘Os_EC_t’ and it will be referenced in this function as Error_Code. We then set up the parameters to call the API QMHSNDM to send that message onto a message queue. Not all of the parameters are set here, some are defined in the Header File which is linked to this file using the #include statement at the top of the file below the comments section we have added. So lets go ahead and add the source for that file.
#ifndef MSGFUNC_h
   #define MSGFUNC_h
   #include <qusec.h>                          // Error Code Structs    
   #include <qmhsndm.h>                        // snd msg               
   
   //the default message queue
   #define _DFT_MSGQ "OSMSGQ    *LIBL     "
   // error code structure with 1KB message data
   typedef _Packed struct  Os_EC_x {
                           Qus_EC_t EC;
                           char Exception_Data[1024];
                           } Os_EC_t;
   // define size        
   #define _ERR_REC sizeof(struct Os_EC_x);                                        
   
   // function declarations
   void snd_error_msg(Os_EC_t);   
   #endif          

First we check that its not already been included by another include statement (#ifndef), if its not we define this file to the compiler, then we include the API headers we need from the QSYSINC/H files installed when you install the IBM i C Compiler. You will notice that this is different to the include statement we used in the previous source file, the user includes (ones we create) have to be defined as the file/member in capitals as shown, the OS includes can be lower case and only need the name plus the .h extension.

So far so good! we now have some code which we want to use to create the Service Program so we should go ahead and create the *MODULE object ready to be included in the Service program. Be sure to add the OSLIB into the library list before you crate the module otherwise it will not find the header file.  Here is the command we used to generate the *MODULE. Change as required to meet your needs.
CRTCMOD MODULE(OSLIB/MSGFUNC) SRCFILE(OSLIB/QCSRC) SRCMBR(MSGFUNC) OUTPUT(*PRINT) OPTION(*EVENTF) DBGVIEW(*SOURCE) REPLACE(*YES) TGTRLS(V7R1M0)
Now when you look in the OSLIB you should see a nice new module called MSGFUNC. If not check your source code and fix up any errors you find.

Next step will be to define how the functionality is to be exported from the Service Program, for that we are going to use a Binding directory which will be used by the compiler to find any objects which its needs to bind as part of the program generation process. We will call our Binding Directory Object OS and will generate the binding directory in the source library OSLIB and will add the entry which defines how the MSGFUNC Service Program is to be defined.
CRTBNDDIR BNDDIR(OSLIB/OS) TEXT(‘OS Binding Directory’)
ADDBNDDIRE BNDDIR(OSLIB/OS) OBJ((MSGFUNC *SRVPGM *DEFER))
Now if you look in your library you will see a new object (PDM allows you to work with the object if you have it installed). and within that object you will see a single entry which provides the link to the service program. You can use the IBM DIRE commands to view the content if not running PDM. As we get more complex environments we will need to manage the Service Programs and allow them to be created in a way that allows old programs generated with old functionality to be run against the same object which has newer functionality added. This means we have some rules to follow which make that possible otherwise we would have to rebuild all of the programs which use a particular Service Program when the interface to it changes (add a new function or change the way existing functions are called). For that we will utilise the ability to store existing data about the functions being exported etc in a source file, as we move forward and change the service program we can use that information to add additional signatures the service Program will accept. 

First we create the source file which will accept the data from the module exports. We create all of our source files with a record length of 150, however this file only needs 92 byte record lengths as the retrieval process only export 92 byte records.  
CRTSRCPF FILE(OSLIB/QSRVSRC) RCDLEN(150) TEXT(‘Service Program Exports’)
Now we will extract the information from the *MODULE we just created and add it to the Source file.
RTVBNDSRC MODULE(OSLIB/MSGFUNC) SRCFILE(OSLIB/QSRVSRC)
You can now view the content of the source file which should be something similar to the following.
STRPGMEXP PGMLVL(*CURRENT)
/********************************************************************/
/*   *MODULE      MSGFUNC      OSLIB        03/13/18  12:02:21      */
/********************************************************************/
  EXPORT SYMBOL("snd_error_msg")
ENDPGMEXP  
Now lets go ahead and create the service program.
CRTSRVPGM SRVPGM(OSPGM/&N) MODULE(OSLIB/&N) SRCFILE(OSLIB/QSRVSRC) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
You should now see an object in the OSPGM library called MSGFUNC with an attribute of *SRVPGM. Take a look at the object description etc and you should see that we have exported the procedure ‘send_error_msg’.

That’s enough for this post, next we will expand on the basics we have here to add more Service Programs and some Programs that will use the functionality exported from them. Not exciting at all but it provides the basics which we will build on in future posts.

If you have any comments or questions about the above or simply want to join in the fun and add some additional code such as RPG into the mix let me know via the normal channels. Also if you have any suggestions about the kind of functionality you would like to see added let me know and we will take a look..

Chris..