Let’s ‘C’ more..

OK so if you have been following from the previous post and would like to take it to the next step here it is. 

This post is going to complement the last so if you have not completed the steps there now would be a good time to go out and do that. We will be here when you get back…  Welcome back… So the first post covered some initial set up of the environment to allow the use of Service Programs which are developed in ‘C’, this is a good start and a lot of the techniques needed to continue are already covered in that post. As we move forward we will cover how we need to adjust our processes to allow the compilation routines to run correctly so that the programs we have created continue to run. This will include CLP programs which will be used to automate the process plus a number of additional utilities to help within the development environment. My focus will be on using ‘C’ but if we have any input from the RPG community which fits our needs we will be sure to add it.

Before we go too much further we need to add more Service Programs and functions. As we have said, Service programs are a great place to keep functions which can be used by many programs and we have a number of those that we would like to share.

First lets consider a common header file, this allows us to store information that will be used by all of the programs such as the standard includes. Adding a #include statement simply tells the compiler it can find the information about certain functions, it does not add links or data from those files unless it needs it. So we can add a common header which has all of the basic ‘C’ functions we used all the time plus any definitions we want to be application wide etc.
ADDPFM FILE(OSLIB/H) MBR(COMMON) TEXT(‘Common Header File’) SRCTYPE(C)
Then we can add the content for the #includes used most commonly in ‘C’ plus move some of the code we have already built into the new header file. The New Header file will have the following content.
#ifndef COMMON_h
   #define COMMON_h
   #include <stdlib.h>                     // standard library
   #include <string.h>                     // string functions
   #include <stdio.h>                      // standard IO
   #include <qusec.h>                      // Error codes
   
   // 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); 
   // so we don't have to remember sizes
   #define _1KB 1024
   #define _8K _1KB * 8
   #define _32K _1KB * 32
   #define _64K _1KB * 64
   #define _1MB _1KB * _1KB
   #define _1GB ((long)_1MB * _1KB)
   #define _1TB ((double)_1GB * _1KB)
   #define _4MB _1MB * 4
   #define _8MB _1MB * 8
   #define _16MB 16773120   

   #endif    
As you can see we have moved some of the content for the Error Code Structures to the new header file plus added the standard includes we expect to use in all ‘C’ programs we develop (or at least a vast majority). We have also added some definitions for sizes, these will be used a lot in later functions. The MSGFUNC header now looks as follows. One important point to make here is the _16MB definition, IBM i/OS does not recognise the true size for 16MB user spaces, they have reduced the maximum size to the value noted so we had to adjust the size definitions accordingly.
#ifndef MSGFUNC_h
   #define MSGFUNC_h
   #include <qmhsndm.h>                        // snd msg

   //the default message queue
   #define _DFT_MSGQ "OSMSGQ    *LIBL     "


   // function declarations
   void snd_error_msg(Os_EC_t);
   #endif    
We have also updated the MSGFUNC Service Program to include the new header file so the Error_Code information can be found.
//
// 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/COMMON>                         // Common header
#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;
}             
At this point you can go ahead and recompile the QCSRC/MSGFUNC.C member to a *MODULE, no need to recreate the *SRVPGM as its not changed its signature, but if you want to go-ahead.

Next we want to create a new Service Program that will allow us to work with *USRSPC objects, we find using *USRSPC objects in ‘C’ very efficient plus many of the API’s IBM provides rely on the *USRSPC object for both input and output. Having tools to create and access them will be very beneficial for our future development exercises.

First lets create the members required to store the code, as before we will create a header file and a source member for the functions.
ADDPFM FILE(OSLIB/H) MBR(SPCFUNC) TEXT(‘User Space Functions Header’) SRCTYPE(C)
ADDPFM FILE(OSLIB/QCSRC) MBR(SPCFUNC) TEXT(‘User Space Functions’) SRCTYPE(C)
We will use the header file to store relevant information required for the source member functions. The source member will contain all of the code for the functions we create to manipulate the *USRSPC objects. At this time we are only interested in creating *USRSPC objects and getting a pointer to the start of the content. Here is the Header File we created
#ifndef SPCFUNC_h
   #define SPCFUNC_h
   #include <quscrtus.h>                    // Create UsrSpc
   #include <qusdltus.h>                    // Delete UsrSpc
   #include <qusptrus.h>                    // Pointer to UsrSpc
   #include <qusgen.h>                      // usrspc gen hdr

   // function declarations
   int Crt_Usr_Spc(char *,int);
   int Get_Spc_Ptr(char *,void *,int);
   #endif      
We include the headers for the API’s we will use within the programs and Service Programs, then we have pre-declared two functions, how they are called and the return value to expect. You will see from the code we produce we also return some items via passed in parameters (Get_Spc_Ptr ) so why you may ask? We define the return parameter to signify if the request completed correctly (usually returns 1) as the passed in parameter may have some value in it but that could be wrong based on if the request completed correctly. So now here is the Code for the function we have created.
//
// 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/COMMON>                         // Common header
#include <H/MSGFUNC>                        // MsgFunc header
#include <H/SPCFUNC>                        // User Space header

// function Crt_Usr_Spc()
// Purpose: To create a Userspace object.
// @parms
//      string Name
//      int size
// returns 1 on sucess

int Crt_Usr_Spc(char *SPC_Name,
                int Initial_Size) {
char Ext_Atr[10] = "USRSPC    ";            // Ext attr USRSPC
char Initial_Value = '0';                   // Init val USRSPC
char Auth[10] = "*CHANGE   ";               // Pub aut to USRSPC
char SPC_Desc[50] = {' '};                  // USRSPC Description
char Replace[10] = "*YES      ";            // Replace USRSPC
Os_EC_t Error_Code = {0};                   // Error Code struct

Error_Code.EC.Bytes_Provided = _ERR_REC;
QUSCRTUS(SPC_Name,
         Ext_Atr,
         Initial_Size,
         &Initial_Value,
         Auth,
         SPC_Desc,
         Replace,
         &Error_Code,
         "*USER     ");
if(Error_Code.EC.Bytes_Available > 0) {
   snd_error_msg(Error_Code);
   return -1;
   }
return 1;
}

// function Get_Spc_Ptr()
// Purpose: Returns a pointer to the user space, creates the userspace if
//          it does not exist.
// @parms
//      User Space name
//      holder for Pointer to return
//      Size of the space if it does not exist
// returns 1 on sucess

int Get_Spc_Ptr(char *Name,
                void *ptr,
                int size) {
Os_EC_t Error_Code = {0};                  // Error Code

Error_Code.EC.Bytes_Provided = _ERR_REC;
// set the pointer to the *USRSPC
QUSPTRUS(Name,
         ptr,
         &Error_Code);
if(Error_Code.EC.Bytes_Available > 0) {
   if(memcmp(Error_Code.EC.Exception_Id,"CPF9801",7) == 0) {
      // It does not exist so create it
      if(!Crt_Usr_Spc(Name,size,fd,debug)) {
         return -1;
         }
      // now get the pointer
      QUSPTRUS(Name,
               ptr,
               &Error_Code);
      if(Error_Code.EC.Bytes_Available > 0) {
         snd_error_msg(Error_Code);
         return -1;
         }
      }
   else {
      snd_error_msg(Error_Code);
      return -1;
      }
   }
return 1;
}             
There are some significant points to be made about this code which affect the way we build the Service programs. You will notice that within the SPCFUNC functions, we are calling the ‘snd_error_msg’ function, this means we should build the service Program that exports that functionality before we build this Service Program (You can create a Service Program with a setting to ignore missing functionality which can be linked later, however we feel that could open up a whole lot of pain down the line when things get very complicated so we chose to build in order?)  We also need to add this functionality into the Binding Directory so that other programs know what is provided. As before we will add the content to the relevant objects using CL commands. As you can see, we are already starting to get into the realms of needing a program to manage this for us, but for now we will do it manually.

First we will add the new Service Program to the Binding Directory, it does not have to exist when we add it, once created the reference will be picked up by the OS and stored in the relevant entry.
ADDBNDDIRE BNDDIR(OSLIB/OS) OBJ((SPCFUNC *SRVPGM *DEFER))
Next we will create the module so we can extract the exported functions automatically.
CRTCMOD MODULE(OSLIB/SPCFUNC) SRCFILE(OSLIB/QCSRC) SRCMBR(SPCFUNC) OUTPUT(*PRINT)  DBGVIEW(*SOURCE) REPLACE(*YES) TGTRLS(V7R1M0)
Now we can extract the module exports and store in our database file.  
RTVBNDSRC MODULE(OSLIB/SPCFUNC) SRCFILE(OSLIB/QSRVSRC)
Now that all of that is done we will need to create the new Service Program. (Note our previous post shows the wrong command as it was scraped from the RDi interface :-))
CRTSRVPGM SRVPGM(OSPGM/SPCFUNC) MODULE(OSLIB/SPCFUNC)  SRCFILE(OSLIB/QSRVSRC)  BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
If you have any problems with the creation check the job logs, one of the main issues is being able to find the *SRVPGM objects so make sure you have the OSPGM lib in your library list.

I think that’s enough for today,, We have shown how to split up the code and move code around to suit your needs, we have shown how *SRVPGM exports can be used in another *SRVPGM (it will be the same result when we create *PGM objects), we have added new concepts into the header file creation (setting the size definitions). We also have some nice new shiny code to play with in our next project.

Hope you are enjoying the exercise, again if you have any questions or requests for new code let us know and we will see what we can do.

Chris…