C and C++ Development Tools

The C and C++ languages provide substantial integration to Qshell with the standard I/O functions described in chapter 19. In addition, Qshell provides direct ILE C and C++ compiler support so that you can develop your application using Qshell.

Compiling C and C++

The icc utility is provided by the iSeries Tools for Developers (product number 5799-PTL). It is located in the /QIBM/ProdData/DeveloperTools/qsh/bin directory. The icc utility is a wrapper program for accessing the CL commands used for C and C++ application development.

In addition, icc creates soft links in the file system, allowing a development process that simulates placing binary objects like modules and programs in the IFS (Integrated File System), instead of in the QSYS.LIB file system.

Use EBCDIC source files with icc . Internally, icc simply uses the CRTCMOD (Create C Module), CRTCPPMOD (Create C++ Module), and CRTPGM (Create Program) CL commands to process the source files.

The syntax of the icc command is shown here:

usage: icc [-cgs+v] [-D name[=value]] [-I directory] [-O [optlevel]] [-o outfile] [-q value] [-M dependfile] [-P listingfile] [-L directory] [-l operand

Table 24.1 shows values for icc options.

Table 24.1: Icc Compiler Options

Option

Description

-c

Compile only; don't link the module object(s) into a program object.

-g

Generate debugging information in the resulting module.

-+

Treat the source files as C++ source code, regardless of the file extension.

-v

Print the CL commands used for the compilation steps.

-D name=value

Define name as value , as if it were defined in a C/C++ #define directive. If a value portion is not specified, name=1 is used by default.

-I dir

Search in directory dir for included files that are not specified with a full path .

-Oopt level

Specify an optimization level of one, two, three, or four, corresponding to the system optimization level of 10, 20, 30, or 40 on the CRTCMOD or CRTCPPMOD commands.

-o outfile

Use outfile as the name of the resulting executable program instead of the default name (a.out).

-q option= value

Specify advanced option settings for the C/C++ CL command.

-M dependfile

Generate a make description file into dependfile.

-P listingfile

Generate listing information into listingfile.

-L directory

Search for the library files specified with the -l option in the directory named.

-l name

Link modules in the specified library file. An icc library file is a symbolic link to an iSeries Binding Directory (*BNDDIR) object. The value name would search for library files named libname.a.

-zifsIO option

Specify the IFS I/O value for the SYSIFCIO keyword on the CRTCMOD or CRTCPPMOD CL commands. The default value is IFSIO. Other acceptable value are NONE, ALL, NOIFSIO, and IFS64IO.

The icc utility determines the appropriate action for input files based on the file extensions. Files with extensions of .CPP , .cpp , .CC , .cc , or .C are treated as C++ source files. Files with an .o extension are symbolic links to module or service program objects. Files with an .a extension are symbolic links to binding directory objects.

The icc utility uses a library to hold the iSeries objects it creates that cannot be placed in the IFS directory. Modules and programs are inserted into the library with the same name as the directory containing the source. Use the OUTPUTDIR environment variable to override the name of the library used for the creation of those objects.

Figures 24.1 and 24.2 illustrate program creation using icc . Figure 24.1 uses the printf() API for generating output, and returns the exit status from the main routine. If desired, you could set the exit status using the exit() API instead.

pwd /home/jsmith/src/c ls -lS total: 16 kilobytes 37 -rw-rw-rw- 1 JSMITH 0 107 Jun 8 15:04 HelloC.c 37 -rw-rw-rw- 1 JSMITH 0 116 Jun 8 15:04 HelloCpp.C cat HelloC.c #include int main(int argc, char **argv) { printf("Hello From C "); return 4; } icc -gv HelloC.c command = CRTCMOD MODULE(c/HelloC) SRCSTMF('HelloC.c') DBGVIEW(*ALL) TEXT('/home/JSMITH/src/c/HelloC.o') SYSIFCOPT(*IFSIO) OPTION(*LOGMSG) command = CRTPGM PGM(c/a) MODULE(c/HelloC) TEXT('/home/JSMITH/src/c/a.out') ls -l total: 24 kilobytes -rw-rw-rw- 1 JSMITH 0 107 Jun 8 15:04 HelloC.c lrwxrwxrwx 1 JSMITH 0 29 Jun 8 15:06 HelloC.o -> /qsys.lib/c.lib/HelloC.module -rw-rw-rw- 1 JSMITH 0 116 Jun 8 15:04 HelloCpp.C lrwxrwxrwx 1 JSMITH 0 21 Jun 8 15:06 a.out -> /qsys.lib/c.lib/a.pgm ./a.out Hello From C echo $? 4

Figure 24.1: This simple C example sends output to the Qshell runtime terminal and sets the exit status.

cat HelloCpp.C #include int main(int argc, char **argv) { cout << "Hello from C++" << endl; return 5; } icc -gv -o HelloCpp HelloCpp.C command = CRTCPPMOD MODULE(C/HELLOCPP) SRCSTMF('HelloCpp.C') DBGVIEW(*ALL) TEXT('/home/JSMITH/src/c/HelloCpp.o') command = CRTPGM PGM(C/HELLOCPP) MODULE(C/HELLOCPP) TEXT('/home/JSMITH/src/c/HelloCpp') ls -l HelloCpp* lrwxrwxrwx 1 JSMITH 0 28 Jun 8 15:18 HelloCpp -> /qsys.lib/c.lib/HelloCpp.pgm -rw-rw-rw- 1 JSMITH 0 118 Jun 8 15:18 HelloCpp.C lrwxrwxrwx 1 JSMITH 0 31 Jun 8 15:18 HelloCpp.o -> /qsys.lib/c.lib/HelloCpp.module ./HelloCpp Hello from C++ echo $? 5

Figure 24.2: This simple C++ example, built with the icc utility, sends output to the Qshell runtime terminal and sets the exit status.

In Figure 24.2, the cout object generates output and returns the exit status from the main routine. Again, the exit status could be set using the exit() API, instead.

The Ixlc Utility (V5R2)

The ixlc utility represents a fully supported C and C++ compiler that runs in Qshell in V5R2. Unlike icc , which is a simple wrapper program with limited documentation, the full documentation for the ixlc compiler is found in the V5R2 ILE compiler's reference manual. Also unlike icc , ixlc supports ASCII source files. Using ASCII source files enables a set of useful iSeries Tools for Developers utilities and workstation tools. Additionally, ixlc allows compilation of source files in the QSYS.LIB file system (files and members in libraries).

Since the ixlc command is fully supported, it should be used for development under V5R1 and beyond. The syntax of the command is shown here:

ixlc [-c+] source-file [compiler-options] [-B'binder-command']

The ixlc compiler has an extensive and robust set of compilation options. The compilation options are based on the keywords and options that would be used with the CRTCMOD and CRTCPPMOD CL commands. The ixlc utility uses the last specified option if multiple incompatible or conflicting options are specified.

The V5R2 ILE compiler reference fully documents all of the options. Table 24.2 describes some of the most common ones, and those related directly to Qshell development. Table 24.2 groups closely related options together; keep in mind that only one of a set of closely related options is usually used.

Table 24.2: Ixlc Compiler Options

Option

Description

-c

Compile only; do not link the generated module object into a program object.

-+

Treat the source files as C++ source code regardless of file extension.

-olib/ object

Name the output object something other than the default name. The default object name used is the same as the source file. The default library used is the current library specified in the user profile, or QGPL if no current library is set. Set the current library on the user profile using the Qshell system utility, for example:

system " CHGUSRPRF USRPRF(JSMITH) CURLIB(C) "

-I dir

Use the -I option to specify directories searched for include files. The values specified with the -I option override any values specified in the INCLUDE environment variable.

The default include path is /QIBM/Include.

-B' binder command

Use the -B option to specify a different binder command than the default. A binder command usually consists of the CL command CRTPGM or CRTSRVPGM, and is used to link modules in ways other than the default.

Use the -B option to provide nonstandard options to CRTPGM or CRTSRVPGM, or to link multiple modules together.

-qifsio=64

-qnoifsio

-qifsio

Set IFS I/O options. IFS I/O options are used to affect the way C/C++ applications access files using the C and C++ standard I/O routines. Specifying IFS I/O means that those routines access files in IFS, while specifying no IFS I/O indicates that those routines access files and members in libraries.

The default value is -qifsio=64 (64-bit IFS I/O) for C++ applications, and -qnoifsio for C applications.

-g

-qdbgview=none

-qdbgview=stmt

-qdbgview=source

-qdbgview=list

-qdbgview=all

Generate debug information. The -g option generates debug information equivalent to -qdbgview=all. Different types of debug views allow different debug capabilities:

  • A stmt-level debug view allows debugging using statement numbers and symbolic names , in conjunction with a separate listing file (not generated by this parameter).
  • A source-level debug view allows source level debugging using the source code (which must remain on the system in the same location).
  • A list-level debug view uses the various listing options to affect the listing that is presented while using the debugger.
  • The -qdbgview=none is the default debug option.

- qtext = description

Add a description to the resulting object. If the description contains spaces, use single or double quotes on the Qshell command.

-qnoprint

-qprint

-qoutput= filename

Generate a listing. The -qprint option generates a compile listing to a spooled file. The -qoutput option generates a compile listing to an IFS file. The -qnoprint option is the default option.

-qgen

-qnogen

Stop the compilation process after the compiler performs syntax-checking. The -qgen option is the default option.

-qlonglong

-qnolonglong

Disable the explicit 64-bit "long long" data type. The -qlong-long option is the default value, enabling the "long long" data type.

-qnoshowinc

-qshowinc

Show user and system includes. Use the -qshowinc option to expand user and system include files in the listings generated using the listing options. The -qnoshowinc option is the default.

-qnoshowsys

-qshowsys

Show system includes. Use the -qshowsys option to expand system include files in the listings generated using the listing options. The -qnoshowsys option is the default.

-qnoshowusr

-qshowusr

Show user includes. Use the -qshowusr option to expand user include files in the listings generated using the listing options. The -qnoshowusr option is the default option.

-qnofull

-qfull

Use the -qfull option to turn on all optional listing categories of output in the listings generated using the listing options. Typically, use the -qfull option with the individual -qno xxxx option to turn off listing categories that are not desired. The -qnofull option is the default.

-qnoexpmac

-qexpmac

Use the -qexpmac option to expand macros in the listings generated using the listing options. The -qnoexpmac option is the default.

-qnoagr

-qagr

Use the -qagr option to generate an aggregate listing category showing the sizes and offsets of fields within structures in the listings generated using listing options. The -qnoagr option is the default.

Choosing Ixlc or Icc

The ixlc utility has some differences from the icc utility that will affect your decision to use one or the other in production development within Qshell:

 

 

Examples

Figure 24.4 is a typical "Hello World" programming example. Subsequent examples in this chapter show various ways that your C or C++ application can interact with Qshell. The Java and perl examples for phone-number lookup, PTF status, and database access in chapters 22 and 23 are similar to those examples in this chapter. This is done to provide you with a good basis for comparing the three languages.

cat HelloCpp.C #include int main(int argc, char **argv) { cout << "Hello from C++" << endl; return 5; } ixlc HelloCpp.C Program HELLOCPP was created in library QGPL on 06/29/03 at 14:13:54. system "CRTLIB JSMITH" CPC2102: Library JSMITH created. system "CHGUSRPRF USRPRF(JSMITH) CURLIB(JSMITH)" CPC2205: User profile JSMITH changed. ixlc HelloCpp.C Program HELLOCPP was created in library JSMITH on 06/29/03 at 14:14:35. /qsys.lib/jsmith.lib/hellocpp.pgm Hello from C++ echo $? 5

Figure 24.4: This simple C++ example, built with the ixlc utility, sends output to the Qshell runtime terminal and sets the exit status. It uses the cout object for generating output and returns the exit status from the main routine. If desired, you could set the exit status with the exit() API, instead.

Using the goodoleboys.txt data file from Figure 21.1, Figure 24.5 demonstrates a functioning, interactive phone-number lookup application. This application uses two list-data structures to provide lookup services based on the data file. Compare and contrast the complexity of this C example with the relative simplicity of the Java and perl examples (Figures 23.7 and 22.7, respectively). Java has a very rich and robust class library, while perl is well-suited (and intended for) text processing. In C, the programmer frequently has to resort to writing additional program code for solving minor problems unrelated to the main program. This example needed to include memory-management, list-manipulation, and string functions. Those features can sometimes be provided with third-party class libraries.

cat phoneList.C #include #include #include // This example doesn't handle lines longer than // BUFSIZE characters. #define BUFSIZE 256 typedef struct { int listCurrentSize; int listMaxSize; char **itemKeys; char **items; } list_t; void listAllocate(list_t *l, int maxSize); void listAdd(list_t *l, char *key, char *item); char *listFind(list_t *l, char *key); void rtrim(char *variable, int maxSize); int main(int argc, char **argv) { list_t numberByOleBoy; list_t oleBoyByWife; char buffer[BUFSIZE]; char oleBoy[16]; char oleBoysWife[16]; char phoneNumber[16]; int len; char *theItem; if (argc < 2 strcmp(argv[1], "-h") == 0) { printf("Usage: phoneList [-h] ... "); exit(1); } // Allocate a large list that needs no extending for // this simple example (this example doesn't handle // extending the list size). listAllocate(&numberByOleBoy, 128); listAllocate(&oleBoyByWife, 128); // Read input from all the data files passed on the // command line. for (int i=1; i FILE *argvFile = fopen(argv[i], "r"); if (argvFile == NULL) { fprintf(stderr, "Error opening file %s ", argv[i]); exit(1); } // The first 2 lines of each data file represent // header lines fgets(buffer, BUFSIZE, argvFile); fgets(buffer, BUFSIZE, argvFile); while (fgets(buffer, BUFSIZE, argvFile) != NULL) { // For each field: // - Take the characters from the correct column for // the field into a variable. // - Remove any trailing spaces at the end of the variable memset(oleBoy, 0, sizeof(oleBoy)); memset(oleBoysWife, 0, sizeof(oleBoysWife)); memset(phoneNumber, 0, sizeof(phoneNumber)); memcpy(oleBoy, &buffer[0], 10); memcpy(oleBoysWife, &buffer[37], 10); memcpy(phoneNumber, &buffer[19], 8); rtrim(oleBoy, sizeof(oleBoy)); rtrim(oleBoysWife, sizeof(oleBoysWife)); rtrim(phoneNumber, sizeof(phoneNumber)); listAdd(&numberByOleBoy, oleBoy, phoneNumber); if (strcmp("none", oleBoysWife) != 0) { listAdd(&oleBoyByWife, oleBoysWife, oleBoy); } } // while (fgets) fclose(argvFile); } // for i loops each argument // Get the user's input. printf("Enter 'exit' or a name to lookup: "); fflush(stdout); while (fgets(buffer, BUFSIZE, stdin)) { len = strlen(buffer); buffer[len-1] = 0; if (strcmp(buffer, "exit") == 0 strcmp(buffer, "quit") == 0) { break; } // Is the name a good ole boy? theItem = listFind(&numberByOleBoy, buffer); if (theItem != NULL) { // Yes, print out the number printf("Found good ole boy's number at %s ", theItem); } else { // Otherwise, is the name a good ole boy's wife? theItem = listFind(&oleBoyByWife, buffer); if (theItem != NULL) { // Yes, print out the number. theItem = listFind(&numberByOleBoy, theItem); printf("Found good ole boy's number using his wife at %s ", theItem); } else { printf("Didn't find number (the lookup is case sensitive) "); } } printf("Enter 'exit' or a name to lookup: "); fflush(stdout); } exit(0); } // Allocate a new list. void listAllocate(list_t *l, int maxSize) { l->listCurrentSize = 0; l->listMaxSize = maxSize; // Create array of strings, size=maxSize l->itemKeys = (char**)malloc(sizeof(char*)*maxSize); l->items = (char**)malloc(sizeof(char*)*maxSize); } // Add an element by key to the list // (making a dynamic copy of the element and the key) void listAdd(list_t *l, char *key, char *item) { char *newKey = strdup(key); char *newItem = strdup(item); int ndx = l->listCurrentSize; // Note this simple example doesn't expand the list. if (!(ndx < l->listMaxSize)) { fprintf(stderr, "Hit the maximum list size. Exit with status=1 "); exit(1); } // printf("Add: <%s> at <%s>(%d) ", newItem, newKey, ndx); l->itemKeys[ndx] = newKey; l->items[ndx] = newItem; l->listCurrentSize = ndx+1; } char *listFind(list_t *l, char *key) { // Note: this simple example uses a poor search mechanism // (a linear search). See the standard C qsort() and qsearch() // functions. // printf("Find: <%s> ", key); for (int i=0; ilistCurrentSize; ++i) { if (strcmp(key, l->itemKeys[i]) == 0) { return l->items[i]; } } return NULL; } // Remove spaces from the end of the variable // given the maximum size of the variable. void rtrim(char *variable, int maxSize) { for (int i=maxSize-1; i>=0; --i) { if (variable[i] == 0 variable[i] == ' ') { variable[i] = 0; } else { // done break; } } } ixlc phoneList.C Program PHONELIST was created in library JSMITH on 06/29/03 at 16:16:08. /qsys.lib/jsmith.lib/phonelist.pgm /home/jsmith/src/data/goodoleboys.txt Enter 'exit' or a name to lookup: Bill Found good ole boy's number at 333-4444 Enter 'exit' or a name to lookup: Daisy Found good ole boy's number using his wife at 333-4444 Enter 'exit' or a name to lookup: Claude Found good ole boy's number at 333-4340 Enter 'exit' or a name to lookup: Etheline Found good ole boy's number using his wife at 333-4340 Enter 'exit' or a name to lookup: Fred Didn't find number (the lookup is case sensitive) Enter 'exit' or a name to lookup: exit

Figure 24.5: Like the perl and Java examples in chapters 22 and 23, this phone-list example shows a functioning, interactive phone-lookup utility.

The C runtime library provides the ability to invoke a Qshell command. Figure 24.6 uses the Qshell system utility to interact directly with a CL command. As mentioned in previous chapters, invoking system like this is a shortcut.

cat popen.H #ifndef __POPEN_H #define __POPEN_H #include #include #include #ifdef __cplusplus extern "C" { #endif // A close simulation of the popen() and pclose() APIs // with slight differences. FILE *popen(const char *pgm, char **pgmArgs); int pclose(FILE *fp); #ifdef __cplusplus } #endif #endif cat popen.C #include #include #include #include #include #include #include #include #include #include #include "popen.H" #define POPEN_MAX_ARGS 32 extern char **environ; // Allow the NO_MAIN preprocessor directive to remove // the main() entry point. Its used for testing, but // not when this module is used by other programs. #ifndef NO_MAIN int main(int argc, char **argv) { FILE *fp; int rc; char i; char *popenchild; char buffer[256]; char **args; if (argc < 2) { printf("usage: popen [args] "); printf(" or "); printf(" popen -c "); printf(" Execute a program or qshell command, piping "); printf(" its output back to the caller "); printf(" Example: "); printf(" popen /usr/bin/ls /home /qibm "); printf(" popen -c 'echo " hi bye" while read i; do echo $i; done" "); exit(1); } // Qshell command? if (strcmp(argv[1], "-c") == 0) { // Yes. Run the qshell program with the // -c parameter and pass it along plus all subsequent // arguments. popenchild = "/usr/bin/qsh"; args = &argv[1]; } else { // Nope, just a program object itself. popenchild = argv[1]; args = &argv[2]; } fp = popen(popenchild, args); if (fp == NULL) { printf("Failed to start a child, err=%d ", errno); exit(0); } while(fgets(buffer, sizeof(buffer), fp) != NULL) { fprintf(stdout, "%s", buffer); } pclose(fp); exit(0); } #endif // popen() like interface. Always returns a FILE * that can // be used for both reading and writing from and to the child. FILE *popen(const char *pgm, char **pgmArgs) { // Data/variables for the spawn API int fdCount=3; int fdMap[3]; struct inheritance inherit; // Arguments plus 1 for program name and 1 for NULL terminator. int argCount; char *cargv[POPEN_MAX_ARGS+2] = { (char *)pgm, NULL }; // NOTE: Could pass all current environment variables too. int envCount; char **env; char *stdioEnvironmentVariable = "QIBM_USE_DESCRIPTOR_STDIO=Y"; // File descriptor data and variables used to setup // file descriptors for this job and the child jobs. int fdToClose; int pipeFd[2]; // The FILE pointer that will be returned to the caller FILE *fp; int rc; char *junk; int i; pid_t pid; // Pipe creates a descriptor for reading only and a descriptor // for writing only. Socketpair creates two descriptors, each that // can be used for both reading AND writing. // socketpair() is more useful in this example. // rc = pipe(pipeFd); rc = socketpair(AF_UNIX, SOCK_STREAM, 0, pipeFd); if (rc != 0) { printf("pipe() failed, %d ", errno); return NULL; } // Set up the fdMap to map file descriptors in the child // to a copy of pipeFd[0] from this job. fdMap[STDIN_FILENO] = pipeFd[0]; fdMap[STDOUT_FILENO] = pipeFd[0]; fdMap[STDERR_FILENO] = pipeFd[0]; // Setup the default spawn inheritence parameters memset(&inherit, 0, sizeof(inherit)); // Add the desired options. inherit.pgroup = SPAWN_NEWPGROUP; inherit.flags = SPAWN_SETTHREAD_NP; // Copy the arguments if there are any. for (int i=0; i if (pgmArgs[i] == NULL) { argCount = i; break; } cargv[i+1] = pgmArgs[i]; } cargv[argCount+1] = NULL; // Create the environment variable array (we're adding // a variable so we can't just directly use environ. envCount = 0; while (environ[envCount] != NULL) { ++envCount; } // Add room for the NULL terminator and the new variable env = (char **)malloc((envCount+2) * sizeof(char *)); for (int i=0; i env[i] = environ[i]; } // Add the stdio environment variable if its not present // (because we're not running from QSHELL) and the null // terminator. if (getenv("QIBM_USE_DESCRIPTOR_STDIO") != NULL) { env[envCount] = NULL; } else { env[envCount] = stdioEnvironmentVariable; env[envCount+1] = NULL; } // Create the child. pid = spawn(pgm, fdCount, fdMap, &inherit, cargv, env); if ((int)pid < 0) { printf("Failed to start child %s, err=%d ", pgm, errno); free(env); return NULL; } // Open a stream FILE over the file descriptor connected // to the parent. fp = fdopen(pipeFd[1], "r+"); if (fp == NULL) { printf("Creating stream failed, errno=%d ", errno); free(env); return NULL; } // Close the file descriptor that we're not going to use // in our process. close(pipeFd[0]); free(env); return fp; } int pclose(FILE *fp) { fclose(fp); return 0; } ixlc -g popen.C Program POPEN was created in library JSMITH on 07/02/03 at 15:19:17. ln -s /qsys.lib/jsmith.lib/popen.pgm popen ./popen -c 'echo "hi bye" while read i; do echo $i; done' hi bye

Figure 24.6: The capability to start Qshell commands and programs while processing the input to and output from programs is a powerful Qshell programming feature.

The popen program in Figure 24.6 demonstrates the simple implementation of an API that acts similar to the Unix-standard popen() API, using the spawn() interface. This program is necessary because the iSeries does not provide the popen() API natively, but instead provides only the constituent parts . Use this simulated API to run Qshell utilities or other C/C++ programs while having access to the program's input or output data. After being compiled, the popen program is used to invoke the compound Qshell statement 'echo "hi bye" while read i; do echo $i; done'. The example uses a while loop in the compound statement to add complexity and demonstrate the capability of the program.

Figure 24.7 uses the simulated popen() API introduced in Figure 24.6 to process the output from a Qshell command. Like the similar Java and perl examples, this example displays the status of the PTFs passed on the command line. Since the API implementations for the popen(), pclose(), and list- related APIs have been shown previously in this section, they are not shown again here.

cat ptfStatus.C #include #include #include #include #include #include "list.H" #include "popen.H" // This example doesn't handle lines longer than // BUFSIZE characters. #define BUFSIZE 256 int debug = 0; int isAPtfLine(char *buffer); int main(int argc, char **argv) { list_t ptfList; FILE *DSPPTF; char buffer[BUFSIZE]; char *pgm = "/usr/bin/qsh"; char *pgmArgs[] = { "-c", "system "DSPPTF OUTPUT(*PRINT)"" }; if (argc < 2 strcmp(argv[1], "-h") == 0) { printf("Usage: phoneList [-h] ... "); printf("Query the status of a list of PTFs "); exit(1); } listAllocate(&ptfList, 4096); // Create a file handle connected to the output // of a process running the system command to process // the CL command DSPPTF. DSPPTF = popen(pgm, pgmArgs); if (DSPPTF == NULL) { printf("Failed to start a child, err=%d ", errno); exit(0); } // Process all of the output of the Qshell command while(fgets(buffer, sizeof(buffer), DSPPTF) != NULL) { char *ptf = NULL; char *status = NULL; if (debug) { fprintf(stdout, "<%s> ", buffer); } if (isAPtfLine(buffer)) { // Use the PTF that we just matched in the parenthesis ptf = &buffer[2]; buffer[9] = 0; // Grab the PTF status from the line. status = &buffer[14]; buffer[49] = 0; // Remove trailing whitespace from status. rtrim(status, 35); if (debug) { printf("OUT:<%s> <%s> ", ptf, status); } listAdd(&ptfList, ptf, status); } } pclose(DSPPTF); printf("------- -------------------- "); printf(" PTF Status "); printf("------- -------------------- "); for (int i=1; i char *ptf = argv[i]; char *status = NULL; status = listFind(&ptfList, ptf); if (status != NULL) { printf("%s %s ", ptf, status); } else { printf("%s Not Loaded ", ptf); } } exit(0); } // PTF list in the output is a 7 character name, // 2 alpha, 5 digits or // 3 alpha, 4 digits // prefixed by two spaces on each line. int isAPtfLine(char *buffer) { if (buffer[0] == ' ' && buffer[1] == ' ' && isalpha(buffer[2]) && isalpha(buffer[3]) && isalnum(buffer[4]) && isdigit(buffer[5]) && isdigit(buffer[6]) && isdigit(buffer[7]) && isdigit(buffer[8])) { return 1; } return 0; } ixlc -c -g -DNO_MAIN popen.C Module POPEN was created in library JSMITH on 07/08/03 at 13:46:39. ixlc -c -g list.C Module LIST was created in library JSMITH on 07/08/03 at 13:46:46. ixlc -g -c -B"CRTPGM PGM(JSMITH/PTFSTATUS) MODULE(JSMITH/POPEN JSMITH/LIST JSMITH/PTFSTATUS)" ptfStatus.C Module PTFSTATUS was created in library JSMITH on 07/08/03 at 13:46:59. Program PTFSTATUS created in library JSMITH. /qsys.lib/jsmith.lib/ptfstatus.pgm SI06971 SI06972 SI06973 SI06974 SI06975 SI06976 SI06977 ------- -------------------- PTF Status ------- -------------------- SI06971 Temporarily applied SI06972 Superseded SI06973 Not Loaded SI06974 Not Loaded SI06975 Temporarily applied SI06976 Temporarily applied SI06977 Superseded

Figure 24.7: This C example demonstrates using the spawn() API to run a CL command and process the output.

Compare and contrast this PTF example with the Java code in Figure 23.8 and the perl code in Figure 22.9. The output from the CL command is processed in a list_t structure that acts rather like a perl associative array or a Java Hashtable object. The list_t structure serves as the lookup mechanism for a list of randomly selected PTFs. The resulting C example looks quite similar to the perl and Java examples, but depends on quite a bit of hand-written code. The Java and perl examples, on the other hand, depend only on the runtime services for those languages.

Although a standard database access mechanism is not built into the C or C++ languages, the ODBC ( Open Database Connectivity ) APIs are available. Java's JDBC APIs are very similar to ODBC APIs; Java designers modeled JDBC based on ODBC. The same can be said for the perl database APIs. The iSeries provides an API set that is very similar to ODBC called the CLI ( Call Level Interface ). CLI is used to access the native relational database on the local or a remote iSeries system.

Figure 24.8 demonstrates a more realistic phone-number lookup application then the example in Figure 24.5. The names and phone numbers reside in the relational database on the iSeries.

cat phoneDB.C #include #include #include #include #define BUFSIZE 256 void checkForSQLError(int rc, char *errorMessage) { if (rc != SQL_SUCCESS) { fprintf(stderr, "Error: %s ", errorMessage); exit(1); } } int main(int argc, char **argv) { char buffer[BUFSIZE]; long bufferLen; long bufferLen2; char oleBoy[16]; char oleBoysWife[16]; char phoneNumber[16]; long phoneNumberLen = 0; int len; char *theItem; // Variables for basic SQL access int rc; SQLHENV henv = SQL_INVALID_HANDLE; SQLHDBC hdbc = SQL_INVALID_HANDLE; SQLHSTMT ps = SQL_INVALID_HANDLE; char *sqlText; long attr; // Variables for demonstrating SQL diagnostic data int sqlCode = 0; char sqlState[10]; char sqlErrorMessage[256]; int sqlErrorMessageLen = 256; SQLSMALLINT sqlErrorMessageLenOut = 0; if (argc != 1) { printf("Usage: phoneList [-h] "); exit(1); } // First create the SQL environment rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv); checkForSQLError(rc, "allocate SQL environment"); // Use SQL_ATTR_SERVER_MODE for multi-connection, multi-user,or // multi-threaded SQL CLI applications. // // iSeries DB access is typically optimized for single connection // programs accessing a local DB under the currently running user. // In that environment, DB access and DB engine work occurs directly // within the calling process, providing significant optimizations. // // If we're using a multi-user, multi-connection or multi-threaded SQL // application, it may be beneficial to use CLI's server mode for SQL. // This causes each connection to be serviced by a backend agent (the // server). The backend agent for one connection is more isolated // from the front end application job and the other SQL connections. // Using server mode for SQL provides significant portability // improvements in some environments. // // attr= SQL_TRUE; // rc = SQLSetEnvAttr(henv, SQL_ATTR_SERVER_MODE, &attr, 0); // checkForSQLError(rc); // Create a handle for an SQL connection object rc = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc); checkForSQLError(rc, "creating connection handle"); // Connect to the local iSeries database (NULL is allowed for // specifying the *LOCAL database with the current user and password rc = SQLConnect(hdbc, NULL, SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS); checkForSQLError(rc, "connecting to local DB"); // Create a statement handle to be used for a prepared statement rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &ps); checkForSQLError(rc, "allocating statement handle"); // Prepare the statement (a prepared statement can be reused) sqlText = "SELECT PHONENUMBER FROM JSMITHQ.CUSTOMERS WHERE NAME = ? OR WIFE = ?"; rc = SQLPrepare(ps, sqlText, SQL_NTS); // If prepare fails, retrieve diagnostic data (diagnose typo's or problems // with the SQL tables and the statement we're using) if (rc != SQL_SUCCESS) { rc = SQLError(henv, hdbc, ps, sqlState, (SQLINTEGER *)&sqlCode, sqlErrorMessage, sqlErrorMessageLen, &sqlErrorMessageLenOut); fprintf(stderr, "Error on SQLPrepare: rc=%d, " "sqlState=%5.5s, sqlCode=%d, msg=%s ", rc, sqlState, sqlCode, sqlErrorMessage); exit(1); } // Output character data for column 1 to the pho neNumber variable rc = SQLBindCol(ps, 1, SQL_CHAR, (SQLPOINTER)&phoneNumber, sizeof(phoneNumber), &phoneNumberLen); checkForSQLError(rc, "bind output column"); // Get the user's input. printf("Enter 'exit' or a name to lookup: "); fflush(stdout); while (fgets(buffer, BUFSIZE, stdin)) { len = strlen(buffer); buffer[len-1] = 0; if (strcmp(buffer, "exit") == 0 strcmp(buffer, "quit") == 0) { break; } // Is the name a good ole boy? // Set both the first and the second parameters to // the input NTS (null terminated string). bufferLen = SQL_NTS; rc = SQLBindParam(ps, 1, SQL_CHAR, SQL_CHAR, strlen(buffer)+1, 0, buffer, &bufferLen); checkForSQLError(rc, "binding parameter 1"); // This parameter is modified by the API at execute time. // Using a second variable for the second call avoids effecting the first bufferLen2 = SQL_NTS; rc = SQLBindParam(ps, 2, SQL_CHAR, SQL_CHAR, strlen(buffer)+1, 0, buffer, &bufferLen2); checkForSQLError(rc, "binding parameter 2"); // Execute the statement using the values set rc = SQLExecute(ps); checkForSQLError(rc, "execute the statement"); // After execution, retrieve the data rc = SQLFetch(ps); if (rc != SQL_SUCCESS && rc != SQL_NO_DATA_FOUND) { // Error condition. checkForSQLError(rc, "fetching data"); } if (rc == SQL_NO_DATA_FOUND) { printf("Didn't find customer: %s ", buffer); } else { // Yes, print out the number printf("Found %s at %s ", buffer, phoneNumber); } // close the open cursor (releases locks and resources when unused) rc = SQLCloseCursor(ps); checkForSQLError(rc, "closing cursor"); printf("Enter 'exit' or a name to lookup: "); fflush(stdout); } // Cleanup for good measure and building good habits. // Close the statement and free the statement handle rc = SQLFreeStmt(ps, SQL_DROP); ps = SQL_INVALID_HANDLE; checkForSQLError(rc, "closing statement"); // Disconnect from the DB rc = SQLDisconnect(hdbc); checkForSQLError(rc, "disconnect"); // Free the connection handle rc = SQLFreeConnect(hdbc); hdbc = SQL_INVALID_HANDLE; checkForSQLError(rc, "free connection handle"); rc = SQLFreeEnv(henv); henv = SQL_INVALID_HANDLE; checkForSQLError(rc, "free environment handle"); exit(0); } ixlc -g phoneDB.C Program PHONEDB was created in library JSMITH on 07/12/03 at 16:31:34. /qsys.lib/jsmith.pgm/phonedb.pgm Error on SQLPrepare: rc=0, sqlState=42704, sqlCode=-204, msg=CUSTOMERS in JSMITHQ type *FILE not found. db2 -f /home/jsmith/src/data/goodoleboys.sql DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. **** CLI ERROR ***** SQLSTATE: 02000 NATIVE ERROR CODE: 100 Row not found for DELETE. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. DB20000I THE SQL COMMAND COMPLETED SUCCESSFULLY. /qsys.lib/jsmith.pgm/phonedb.pgm Enter 'exit' or a name to lookup: Bill Found Bill at 333-4444 Enter 'exit' or a name to lookup: Daisy Found Daisy at 333-4444 Enter 'exit' or a name to lookup: Claude Found Claude at 333-4340 Enter 'exit' or a name to lookup: Etheline Found Etheline at 333-4340 Enter 'exit' or a name to lookup: Fred Didn't find customer: Fred Enter 'exit' or a name to lookup: exit

Figure 24.8: Use C and the CLI APIs to access the iSeries database.

Compare and contrast this code with the similar Java example in Figure 23.8 and the perl example in Figure 22.9. For example, note that this C program is significantly longer than the Java and perl equivalents. This is due, in part, to the resource cleanup and error-checking required in C. The C database APIs have more parameters and more stringent requirements on the size, type, and storage model of the parameters than either the perl or Java programs. The fundamental concepts of database access and the general APIs remain quite consistent between this C example and the perl and Java programs, but these complexities might initially cause more errors in your application code as you learn the programming model.

The commands shown in Figure 24.8 demonstrate the error that might occur if database setup has not been completed. Running the goodoleboys.sql script with the db2 utility corrects the problem, and the phoneDB program runs successfully.

 

 

Summary

The C and C++ compilers and languages are not as integrated with the Qshell environment as the Java language. Extra steps during the development and build stage are required. Multiple versions of the C and C++ compilers provide flexibility to help you choose a solution that matches the requirements of your application. Writing special-purpose APIs might require that you focus more on testing and reusing C/C++ application code in order to keep productivity high.

 

 

Appendix A Summary of Changes by Release

Категории