Will there always be RPG and COBOL programmers? Learn how to access files natively using C and C++. Your application’s future may depend on it.
Google "Most popular programming languages in the world," peruse the top results, and you'll find that C and C++ programming languages are consistently among the top five languages listed. For COBOL and RPG, you have to dig a little deeper. On one site, programming language popularity based upon search engine hits is reported monthly. As of August 2014, COBOL had slipped to 29th, (down from 27th in July) and RPG was lumped in with an alphabetized list of those that ranked from 51 through 100, down from 47th in July. By the time you read this, who knows where they will be.
While languages such as Java and PHP are becoming popular on the IBM i platform, the majority of the mission-critical back-end applications are written in COBOL or RPG. But you don't hear of many recent computer science graduates who are excited about these languages. Are C and C++ a good alternative for IBM i programming? Because they're ILE languages, C and C++ play well with COBOL and RPG, allowing for applications to start making use of these technologies directly.
One of the first things you'll want to know with C/C++ programming on IBM i is how to access database files. There are several SQL-based approaches, but if you're not ready to fully embrace SQL, you can still use the C "native" I/O interfaces, which are more closely aligned with the native file access provided in RPG and COBOL.
The C/C++ native I/O interfaces are documented in the ILE C/C++ Runtime Library Functions document, which can be found here for OS 7.1. The native file I/O functions all begin with _R, such as _Ropen, _Rclose, _Rreadf, and so on. However, what's hardly mentioned in this documentation is how to best provide your C program with the structural layout of database records and keys for processing within the program. You can do this manually, declaring a C structure (struct), but this is tedious and at risk of becoming out of date if the file format is changed. Historically, this was done with the #pragma mapinc facility, but more recently, IBM also provides the GENCSRC command. The pragma causes a C/C++ header source file member to be dynamically generated in QTEMP/QACYXTRA during the compile when the pragma is encountered, which can then be immediately included into your program with the #include directive. The GENCSRC CL command is more flexible. It is run separately from the compile step and generates the C/C++ header source file in any location you choose, which can be either a traditional source physical file or an IFS stream file. The generated header file can then be included in your program normally using the #include directive. Other than this, both methods provide the same capabilities for mapping files, so the following descriptions are provided in terms of the GENCSRC command.
Let's consider a simple employee file, defined using the following DDS:
UNIQUE
A R MYFORMAT TEXT('MY FILE OF FIELDS')
A EMPNBR 10S 0 TEXT('EMPLOYEE NUMBER')
A EMPNAME 50 TEXT('EMPLOYEE NAME')
A SEX 1 TEXT('SEX')
A SALARY 10P 2 TEXT('SALARY (DOLLARS)')
A POSITION 9B TEXT('POSITION CODE')
A HIREDATE Z TEXT('HIRE DATE')
A COMMENTS 2000 COLHDG('EMPLOYEE COMMENTS') -
TEXT('EMPL COMMENTS') VARLEN -
ALWNULL
K EMPNBR
After creating the file with CRTPF, we use the following GENCSRC command to generate a C/C++ header file:
GENCSRC
OBJ('/QSYS.LIB/MYLIB.LIB/MYFILE.FILE')
SRCFILE(MYSRCLIB/GENCSRC)
SLTFLD(*BOTH *KEY *NULLFLDS *LVLCHK)
ONEBYTE(*CHAR)
#ifdef __cplusplus
#include <bcd.h>
#else
#include <decimal.h>
#endif
/* ------------------------------------------------------- *
// PHYSICAL FILE : MYLIB/MYFILE
// FILE LAST CHANGE DATE : 2014/08/05
// RECORD FORMAT : MYFORMAT
// FORMAT LEVEL IDENTIFIER : 4FA3F5B1EE90B
* ------------------------------------------------------- */
typedef _Packed struct {
char EMPNBR[10]; /* EMPLOYEE NUMBER */
/* ZONE SPECIFIED IN DDS */
/* REPLACED BY CHARACTER TYPE */
char EMPNAME[50]; /* EMPLOYEE NAME */
char SEX;
#ifndef __cplusplus
decimal(10, 2) SALARY;
#else
_DecimalT<10, 2> SALARY; /* SALARY (DOLLARS) */
/* BCD class SPECIFIED IN DDS */
#endif
int POSITION; /* POSITION CODE */
char HIREDATE[26]; /* HIRE DATE */
/* TIMESTAMP FIELD */
_Packed struct { short len; /* LENGTH OF DATA */
char data[2000]; /* FIELD DATA */
} COMMENTS; /* EMPL COMMENTS */
} MYLIB_MYFILE_MYFORMAT_both_t;
typedef _Packed struct {
char EMPNBR[10];
/* DDS - ASCENDING */
/* SIGNED NUMERIC KEY FIELD */
} MYLIB_MYFILE_MYFORMAT_key_t;
typedef struct {
unsigned char EMPNBR;
unsigned char EMPNAME;
unsigned char SEX;
unsigned char SALARY;
unsigned char POSITION;
unsigned char HIREDATE;
unsigned char COMMENTS;
} MYLIB_MYFILE_MYFORMAT_nmap_t;
#ifndef __LVLCHK__
#define __LVLCHK__
typedef struct _LVLCHK_ {
unsigned char format_name[10];
unsigned char sequence_no[13];
#ifdef __cplusplus
_LVLCHK_(const char * pformat, const char * pseq) {
short int i;
for (i=0; i < 10; format_name[i]=pformat[i++]);
for (i=0; i < 13; sequence_no[i]=pseq[i++]);};
#endif
} _LVLCHK_T[];
#endif
#ifdef __cplusplus
_LVLCHK_T MYLIB_MYFILE__QSYS_LIB_ MYSRCLIB__LIB_GENCSRC_FILE_MYFILE_MBR_lvlchk = {
_LVLCHK_("MYFORMAT ","4FA3F5B1EE90B"),
_LVLCHK_("","")
};
#else
_LVLCHK_T MYLIB_MYFILE__QSYS_LIB_MYSRCLIB__LIB_GENCSRC_FILE_MYFILE_MBR_lvlchk = {
"MYFORMAT ","4FA3F5B1EE90B",
"",""
};
#endif
Lines 1-5 include declarations needed for packed-decimal field declarations and are designed to work correctly whether this header file is used by the C compiler or the C++ compiler.
Lines 6-11 provide a comment block to describe the header file.
Lines 12-31 define the record format layout structure
Lines 13-15 define a zoned numeric field, which is mapped into a character array type.
Line 16 defines a simple character field.
Line 17 defines a character field whose length is one. Because the ONEBYTE(*CHAR) option was specified on the GENCSRC command, this character field is declared as a simple scalar character, rather than a character array of length one. Also note that this field does not have a descriptive comment. This is because the TEXT attribute in the DDS was simply the field name. GENCSRC is smart enough to know that a comment repeating the field name would look silly.
Lines 19-24 define a packed numeric field, again using syntax that allows use in either C or C++ programs.
Line 25 is a binary data type.
Line 26 is a timestamp data type.
Lines 28-30 define a variable-length character field by declaring an inner structure with a length element and the data for the value.
Lines 34-38 define a structure that maps out the key fields for the file. This allows you to construct a key value for use in key-based file reading.
Lines 41-49 define a structure containing appropriately named character values to provide the layout of the null map that is passed and returned by the native I/O read and write family of functions. This is present because the *NULLFLDS option was specified on the SLTFLD parameter of GENCSRC.
Lines 52-64 define some types and values that can be referenced when the file is opened at run time to cause a run-time check to occur that verifies the file that is actually opened has the same file format as was used to generate the include. This may very well catch a case where a program might otherwise corrupt a file by writing data using the wrong layout.
In a future TechTip, I'll cover how to use the C runtime library native I/O functions to open, read, update, and write to a database file using the declarations provided by the GENCSRC generated header file.
Downloadable Code
You can download the code files for this article here and here.
LATEST COMMENTS
MC Press Online