Ile C
Ile C
ERserver
iSeries ®
WebSphere Development Studio
ILE C/C++ Programmer’s Guide
Version 5
SC09-2712-03
ERserver
iSeries ®
WebSphere Development Studio
ILE C/C++ Programmer’s Guide
Version 5
SC09-2712-03
Note!
Before using this information and the product it supports, be sure to read the general information
under “Notices” on page 477.
Contents v
Using Subfiles . . . . . . . . . . . . . 233 Control Boundary Examples for ILE C/C++ . . . 292
Using Intersystem Communication Function Files 236 Exception Percolation: an Example . . . . . 296
I/O Considerations for Intersystem
Communication Function Files . . . . . . 236 Chapter 14. Using iSeries Pointers in
Opening ICF Files as Binary Stream Files . . . 236 Your Program . . . . . . . . . . . 299
I/O Considerations for Binary Stream ICF Files 236
Using Open Pointers . . . . . . . . . . . 300
Binary Stream Functions for ICF Files . . . . 236
Using Pointers Other Than Open Pointers . . . . 300
Opening ICF Files as Record Files . . . . . 237
Declaring Pointer Variables . . . . . . . . . 300
I/O Considerations for Record ICF Files . . . 237
Using Pointer Casting . . . . . . . . . . 303
Record Functions for ICF Files. . . . . . . 238
Using Printer Files. . . . . . . . . . . . 242
I/O Considerations for Printer Files . . . . . 243 Chapter 15. Using Packed Decimal
Opening Printer Files as Binary Stream Files . . 243 Data in Your C Programs . . . . . . 309
Opening Printer Files as Record Files . . . . 243 Converting from Packed Decimal Data Types. . . 309
Record Functions for Printer Files . . . . . 243 Converting from a Packed Decimal Type to a
Writing to a Tape File . . . . . . . . . . 245 Packed Decimal Type. . . . . . . . . . 309
I/O Considerations for Tape Files . . . . . 245 Converting from a Packed Decimal Type to an
Opening Tape Files as Binary Stream Files. . . 246 Integer Type . . . . . . . . . . . . . 311
Binary Stream Functions for Tape Files . . . . 246 Converting from a Packed Decimal Type to a
Opening Tape Files as Record Files . . . . . 246 Floating Point Type . . . . . . . . . . 312
Record Functions for Tape Files . . . . . . 247 Overflow Behavior . . . . . . . . . . 313
Writing to a Diskette File . . . . . . . . . 249 Passing Packed Decimal Data to a Function . . . 313
I/O Considerations for Diskette Files . . . . 249 Passing a Pointer to a Packed Decimal Variable
Opening Diskette Files as Binary Stream Files 250 to a Function . . . . . . . . . . . . 314
Binary Stream Functions for Diskette Files. . . 250 Calling Another Program that Contains Packed
Opening Diskette Files as Record Files . . . . 250 Decimal Data . . . . . . . . . . . . . 315
Record Functions for Diskette Files . . . . . 250 Using Library Functions with a Packed Decimal
Using Save Files . . . . . . . . . . . . 252 Data Type . . . . . . . . . . . . . . 316
I/O Considerations for Save Files . . . . . 252 Understanding Packed Decimal Data Type Errors 320
Opening Save Files as Binary Stream Files . . . 253
I/O Considerations for Binary Stream Save Files 253 Chapter 16. Calling Conventions . . . 325
Binary Stream Functions for Save Files . . . . 253 Program and Procedure Calls . . . . . . . . 325
Opening Save Files as Record Files . . . . . 253 Calling Programs . . . . . . . . . . . 325
I/O Considerations for Record Save Files . . . 253 Calling Conventions for Dynamic Program Calls 326
Record Functions for Save Files . . . . . . 253 Calling Procedures for ILE C . . . . . . . 345
Calling C++ Programs and Procedures from ILE
Part 6. Working with iSeries C . . . . . . . . . . . . . . . . 351
Calling Procedures for ILE C++ . . . . . . 351
Features . . . . . . . . . . . . . 255 Introducing the Call Stack . . . . . . . . 352
Calling a Program Using a Linkage Specification 353
Chapter 13. Handling Exceptions in Calling an ILE Procedure Using a Linkage
Your Program . . . . . . . . . . . 257 Specification . . . . . . . . . . . . . . 354
Handling Exceptions . . . . . . . . . . . 259 Passing Parameters . . . . . . . . . . . 355
Checking the Return Value of a Function . . . 259 Passing Parameters in C++ . . . . . . . . 355
Checking the Errno Value . . . . . . . . 260 Using Default Parameter Passing Styles . . . 359
Checking the Global Variable _EXCP_MSGID 261 Using Operational Descriptors. . . . . . . 360
Checking the System Exceptions for Stream Files 261 Understanding Data-Type Compatibility . . . 361
Checking the System Exceptions for Record Changing the Names of Programs and Procedures 371
Files . . . . . . . . . . . . . . . 261 Creating C++ Classes for Use in ILE. . . . . . 371
Using Exception Handlers . . . . . . . . . 265 Mapping a C++ Class to a C Structure . . . . 372
Unhandled Exceptions . . . . . . . . . 267 Using C++ Objects in a C Program . . . . . 373
Using Direct Monitor Handlers . . . . . . 268 Qualifying Library Calls . . . . . . . . . . 376
Exception Classes . . . . . . . . . . . 269 Calling OPM Programs . . . . . . . . . . 376
Control Actions. . . . . . . . . . . . 270 Program Description . . . . . . . . . . 376
Specifying Message Identifiers. . . . . . . 271 Program Structure . . . . . . . . . . . 376
Nested Exceptions . . . . . . . . . . . 278 Program Activation . . . . . . . . . . 377
Using Cancel Handlers . . . . . . . . . 278 Program Files . . . . . . . . . . . . 377
Using Integrated Language Environment Invoking the ILE-OPM Program . . . . . . 382
Condition Handlers . . . . . . . . . . 281 Calling ILE Programs. . . . . . . . . . . 382
Using the C/C++ signal Function to Handle Program Description . . . . . . . . . . 382
Exceptions . . . . . . . . . . . . . 287 Program Structure . . . . . . . . . . . 382
Contents vii
Name Mangling . . . . . . . . . . . 453 Introducing RTTI . . . . . . . . . . . . 465
File Inclusion . . . . . . . . . . . . 453 Using C++ Language Defined RTTI . . . . . . 466
Function Prototypes, Declarations and Pointers 453 The dynamic_cast Operator . . . . . . . 466
Character Array Initialization . . . . . . . 454 Dynamic Casts with Pointers . . . . . . . 466
String Literals . . . . . . . . . . . . 454 Dynamic Casts with References . . . . . . 467
Integrated File System . . . . . . . . . 455 The typeid Operator . . . . . . . . . . 467
Set_Terminate is Scoped to an Activation Group 455 The type_info Class . . . . . . . . . . 469
Using RTTI in Constructors and Destructors . . . 469
Appendix D. Using Templates in C++ Understanding ILE C++ Extensions to RTTI . . . 469
Programs . . . . . . . . . . . . . 457 The extended_type_info Class . . . . . . . 470
Using Template Terms . . . . . . . . . . 457
How the Compiler Expands Templates . . . . . 458 Bibliography . . . . . . . . . . . . 473
Generating Template Function Definitions . . . . 459
Including Defining Templates . . . . . . . . 460 Notices . . . . . . . . . . . . . . 477
Including Defining Templates Everywhere. . . 460 Programming Interface Information . . . . . . 478
Structuring for Automatic Instantiation . . . . 460 Trademarks and Service Marks . . . . . . . 478
Manually Structuring for Single Instantiation 464 Industry Standards . . . . . . . . . . . 479
The iSeries Information Center contains advisors and important topics such as CL
commands, system application programming interfaces (APIs), logical partitions,
clustering, Java™ , TCP/IP, Web serving, and secured networks. It also includes
links to related IBM® Redbooks and Internet links to other IBM Web sites such as
the Technical Studio and the IBM home page.
Most of the examples found in this guide are illustrated by entering Control
Language (CL) commands on a CL command line. You can use a CL program to
run most of the examples. See the member T1520INF in QCLE/QAINFO for
information about running the examples in each chapter.
If you are mailing a readers’ comment form from a country other than the
United States, you can give the form to the local IBM branch office or IBM
representative for postage-paid mailing.
v If you prefer to send comments by FAX, use the following number:
– 1-416-448-6161
v If you prefer to send comments electronically, use one of these e-mail addresses:
– Comments on books:
[email protected]
IBMLink: to toribm(torrcf)
– Comments on the iSeries 400 Information Center:
[email protected]
Figures xv
xvi ILE C/C++ Programmer’s Guide
Tables
1. Programming Languages Supported by the 20. ILE C++ Data-Type Compatibility with ILE
iSeries family . . . . . . . . . . . . 3 RPG . . . . . . . . . . . . . . 361
2. Trigraphs . . . . . . . . . . . . . 22 21. ILE C++ Data-Type Compatibility with ILE
3. Parameters for CRTPGM Command and their COBOL . . . . . . . . . . . . . 362
Default Values . . . . . . . . . . . 27 22. ILE C++ Data-Type Compatibility with ILE
4. Sections of the Binder Listing based on the CL . . . . . . . . . . . . . . . 364
DETAIL Parameter . . . . . . . . . . 28 23. ILE C++ Data-Type Compatibility with OPM
5. Parameters and Default Values for RPG . . . . . . . . . . . . . . 365
CRTSRVPGM Command . . . . . . . . 34 24. ILE C++ Data-Type Compatibility with OPM
6. Integrated File System Compiles . . . . . 173 COBOL . . . . . . . . . . . . . 366
7. Data Management File System Compiles 173 25. ILE C++ Data-Type Compatibility with CL 367
8. INCDIR Command Parameter . . . . . . 175 26. Arguments Passed From a Command Line
9. INCLUDE Environment Variable . . . . . 176 CL Call to an ILE C++ Program . . . . . 368
10. Include Search Order . . . . . . . . . 176 27. CL Constants Passed from a Compiled CL
11. Parameter Values . . . . . . . . . . 177 Program to an ILE C++ Program . . . . . 368
12. INCDIRFIRST Command Options. . . . . 178 28. CL Variables Passed from a Compiled CL
13. Lock States for Open Modes . . . . . . 204 Program to an ILE C++ Program . . . . . 369
14. Handling Overflow From a Packed Decimal 29. C Locale Migration Table . . . . . . . 412
to a Smaller Target . . . . . . . . . . 313 30. Categories Used in a Locale. . . . . . . 417
15. Program Calling Conventions . . . . . . 326 31. Locale-Sensitive Run-Time Functions 419
16. Argument Passing for Integrated Language 32. Compiler Options for Performance . . . . 439
Environment Procedures . . . . . . . . 346 33. Flag Meanings for Printing the Value of a
17. Effects of Various Linkage Specifications 355 _DecimalT Template Class Object . . . . . 448
18. Default Argument Passing Style for ILE 34. Comparing Packed Structures . . . . . . 452
Programs . . . . . . . . . . . . . 360 35. typeid operations . . . . . . . . . . 468
19. Default Argument Passing Style for ILE
Procedures . . . . . . . . . . . . 360
C and C++ are two of the programming languages supported by the Integrated
Language Environment (ILE). C++ provides additional features to those found in the
C language. These features include additional keywords, parameterized types
(templates), support of object oriented programming via classes, and stricter type
checking.
Program Creation
ILE program creation consists of:
1. Compiling source code into modules
2. Binding (combining) one or more modules into a program object.
You can create a binding directory to contain the names of modules and service
programs that your ILE C++ program or service program may need. A binding
directory can reduce program size because modules or service programs listed in a
binding directory are used only if needed.
You can bind modules into service programs (*SRVPGM). Service programs are a
means of packaging callable routines (functions or procedures) into a separately
bound program. The use of service programs provides modularity and improves
maintainability. You can use off-the-shelf modules developed by third parties or
package your own modules for third-party use. Service programs are created by a
compiler option that includes the Create Service Program (CRTSRVPGM)
command.
Figure 1 shows the process of creating an ILE program through compiler and
binder invocation.
CRTPGM CRTSRVPGM
ILE programs and service programs are activated into activation groups you specify
at the time you create a program. The process of getting a program or service
program ready to run is known as activation. Activation allocates resources within
a job so that one or more programs can run in that space. When a program is
called, the system activates it into an activation group. If the specified activation
group for a program does not exist when the program is called, it is created within
the job to hold the program’s activation.
Program Calls
In ILE, you can write programs in which ILE C++ programs, OPM and EPM
programs interrelate through the use of dynamic program calls. When using such
calls, the calling program specifies the name of the called program. This name is
resolved to an address at run time, just before the calling program passes control
to the called program.
You can write programs which interrelate through faster static procedure calls. A
procedure is a self-contained set of code that performs a task and then returns to
the caller. An ILE C++ module consists of one or more procedures. Because the
procedure names are resolved at bind time (that is, when you create the program),
static calls are faster than dynamic calls.
Static calls allow operational descriptors. Operational descriptors are used to call
bindable APIs or procedures written in other ILE languages.
See Chapter 16, “Calling Conventions” on page 325 for information on calls
between programs and procedures.
Program Debugging
In ILE, you can perform source-level debugging on any program written in one or
more ILE languages, provided program was compiled with debug information.
You can control the flow of a program by using debug commands while the
program is running. You can set conditional and unconditional breakpoints prior to
running the program. After calling the program, you can step through a specified
number of statements and display or change variables. When a program stops
because of a breakpoint, a step command, or a run-time error, the pertinent
module is displayed at the point where the program stopped. At that point, you
can enter more debug commands.
Bindable APIs
ILE offers a number of bindable APIs that supplement ILE C/C++ functions.
Bindable APIs provide program calling and activation capability, condition and
storage management, math functions, and dynamic screen management. The
System API Reference contains information on bindable APIs.
Although C++ is a descendant of the C language, the two languages are not
always compatible. Please refer to the ILE C/C++ Language Reference for more
information.
In C++, you can develop new data types that contain functional descriptions
(member functions) as well as data representations. These new data types are
called classes. The work of developing such classes is known as data abstraction. You
can work with a combination of classes from established class libraries, develop
your own classes, or derive new classes from existing classes by adding data
descriptions and functions. New classes can contain (inherit) properties from one or
You can define a series of functions with different argument types that all use the
same function name. This is called function overloading. A function can have the
same name and argument types in base and derived classes.
Declaring a class member function in a base class allows you to override its
implementation in a derived class. If you use virtual functions, class-dependent
behavior may be determined at run time. This ability to select functions at run
time, depending on data types, is called polymorphism
You can redefine the meaning of the basic language operators so that they can
perform operations on user-defined classes (new data types), in addition to
operations on system-defined data types, such as int, char, and float. Adding
properties to operators for new data types is called operator overloading.
The C++ language provides templates and several keywords not found in the C
language. Other features include try-catch-throw exception handling, stricter type
checking and more versatile access to data and functions compared to the C
language.
Program Description
The program produces a company’s monthly pay summary. It processes data on
four different types of employees:
v Managers on a yearly salary.
v Sales managers who get a yearly salary plus a commission for units sold.
The program displays the basic monthly pay for each class of employee, the name
and identification number for each employee, and the amount each employee was
paid for the month. It calculates the total amount paid in salary, wages, and
commission for the month.
Program Structure
The program files are:
v A user-defined header file compclas.h containing the class declarations.
v A C++ source file compfunc.cpp containing class member function definitions.
v A C++ source file comprec.cpp containing the main program logic.
v A system header file iostream.h from the Standard Class library containing
standard input and output functions.
User-Defined Header File: In the header file compclas.h, five classes are declared:
v An abstract base class employee containing the employee’s name and
identification number.
v Three derived classes manager, regular_emp, and sales_person contain
constructor functions and functions for printing out pay information.
v A class sales_mgr that uses the pay functions from both the sales_person and
manager classes in its pay function to demonstrate multiple inheritance.
Each class contains private data on salaries, or wages and hours worked, or
commission and units sold, and on public functions that use the data. The class
structure demonstrates the private and public aspects of a class. Each class contains
one inlined function to demonstrate inlining within a class. Figure 2 on page 8
shows the class hierarchy.
Variables:
name
employee_id
Functions:
constructor
position()
print()
Derived Class:
sales_mgr
Variables:
salary
units
commission
Functions:
constructor
pay()
position()
print()
C++ Source Files: The file compfunc.cpp contains the definitions for the member
functions of the classes used in this program.
The file comprec.cpp contains the logic for this program. It defines a function
payout() that prints out a basic salary for each category of employee.
The function payout() demonstrates how you prototype and overload a function.
Each class of employee has a different number of parameters, but the same
function name is used for all employee classes. The functions are prototyped before
the definition of the main() function. They are defined in the body of comprec.cpp
after the definition of the main() function.
The main() function in comprec.cpp defines an instance of each of the four classes
of employee: manager, sales manager, regular employee, and sales person, and
uses the functions defined for each of these classes to display information about
four typical employees. Figure 3 on page 9 shows the program structure.
Source File:
compfunc.cpp
Compiled Program
definitions of class member
functions
Program Files
The following sections show the source code for each of the files that compose this
program.
Header File: The header file compclas.h contains definitions of classes that are
used in the main program comprec.cpp. See “Main Program Source File” on
page 13.
// compclas.h -- class definitions for comprec.cpp ***
#ifndef _COMPCLAS_H
# define _COMPCLAS_H 1
protected:
const char * name() { return n; }
int employee_id () { return x; }
public:
// Constructors for class employee. 3
employee() : n(""), x(0) {};
employee(char * n, int id);
public:
// Constructors for class manager.
manager(char *n, int id, double sal);
public:
// Constructor
regular_emp(char *n, int id, double wg, double hrs) ;
// Member functions
double pay();
friend ostream& operator << (ostream& out, regular_emp & lhs);
void dump() { cout << *this; }
};
// End of regular_emp class definition.
public:
// Constructors
sales_person(char *n, int id,
double com, double nts);
// Member functions
double pay();
friend ostream& operator << (ostream& out, sales_person & lhs);
void dump() { cout << *this; }
};
// End of sales_person class definition.
#endif
Program References:
C++ Source File: The file compfunc.cpp contains the function definitions for the
member functions of the classes that are used in the main program comprec.cpp.
See “Main Program Source File” on page 13.
//compfunc.cpp -- definitions for class member functions
#include <string.h>
#include <iostream.h>
#include "compclas.h"
<< "makes " << lhs.salary << " per year." << endl
<< lhs.manager::name() << " made " << lhs.pay() << " dollars this month."
<< endl << endl;
return out;
}
<< "makes " << lhs.wage << " dollars per hour" << endl
<< "and worked " << lhs.hours << " hours this month. " << endl
<< "dollars per unit sold and sold " << lhs.units
<< " units this month." << endl
<< "makes " << lhs.salary << " per year and earns a commission of "
<< lhs.commission << " dollars per unit sold." << endl
Program References:
9 The scope resolution operator (::) in this function definition indicates
that the constructor function employee for class employee is defined.
10 This constructor initializes the data types declared in the class
definition.
11 For classes with multiple inheritance, the constructor must fully
initialize the class without creating any ambiguity.
12 Because sales_mgr is a derived class with multiple inheritance, there
can be ambiguity about which values to use. To avoid ambiguity, explicitly
qualify the function by using the scope resolution operator (::).
Main Program Source File: The C++ source file comprec.cpp contains the logic of
this program.
//comprec.cpp
#include <iostream.h> 13
#include <strstrea.h>
#include "compclas.h"
#include "compfunc.cpp" // for the inline functions
payout(managers_pay);
payout(reg_emp_pay, reg_emp_hrs);
payout(monthly_salary, commission, units);
double sal=0;
for (int i=0; ep[i]; ++i) {
ep[i]->dump();
sal+=ep[i]->pay();
delete ep[i];
Program References:
13 The first two preprocessor directives let this program make use of
system include files that declare standard library functions such as the cout
and cin objects. The compiler searches for the files named in the #include
directives.
The third and fourth preprocessor directives name the user-defined include
file compclas.h and the source file comfunc.cpp that define the classes and
member functions used by comprec.cpp.
14 All functions must be declared before they can be used. This
statement declares a function payout that takes an argument of type double
and does not return a value.
The function has been declared static to limit its scope to this file only.
One reason to use the static storage class for functions is to avoid possible
name conflicts.
15 An overloaded function has the same name as another function but
different types or numbers of arguments. Each function uses the same type
of argument (double), but each has a different number of arguments.
16 The keyword inline specifies a function whose body is to be
expanded at each point of call to the function, so that the call is replaced
by the function body itself. Within a class definition, the function is
automatically inlined if you include its definition in the class definition.
Specifying inline may not inline the function. The compiler decides
whether the function is inlined.
You can control inlining with the /Oi+ compiler option.
17 Each program must have a main function. Within the braces are
statements that make up the main body of the program. main takes no
arguments and returns an int.
18 All variables must be declared before they are used.
The basic pay for a regular employee is: 1030.00 dollars per month.
The basic pay for a sales manager is: 950.00 dollars per month.
This process is not continuous. You can compile, correct compile-time errors,
modify, and recompile the program several times before binding it.
Preparing a Program
Preparing a program involves designing, writing, and creating source code. See
“Entering Source Statements” on page 20 for more information about creating
source code.
Compiling
Issue the compile command against your source, and fix any compile errors that
arise. You can see the errors either as messages in the job log or in the listing (if
you chose to create one).
Compile
Use Description
Command
CRTCMOD Create C The Create Module command creates a module object. If
Module your program will include objects from more than one
source file, you must use the Create Module command for
CRTCPPMOD Create C++ each source file, and then run CRTPGM specifying all the
Module required *MODULEs to create the bound program.
CRTBNDC Create Bound The Create Bound Program command performs both the
C Program module creation and the binding operation in one step,
and produces a *PGM object from a single source file.
CRTBNDCPP Create Bound
C++ Program
Note: The compile command might also originate in a CL program or makefile.
Binding
If you created modules during compilation, you need to bind the module objects
together using the Create Program (CRTPGM) or Create Service Program
(CRTSRVPGM) commands. The result is an executable *PGM or *SRVPGM object.
Once a program is created, you can later update it using the Update Program
(UPDPGM) or Update Service Program (UPDSRVPGM) commands. These
commands are useful, because you only need to have the new or changed modules
available when you want to update the program.
Running
*CMD objects are run, while *PGM objects are called. For example, to run HELLO
*CMD, type HELLO on the QCMD command line and press ENTER. To run
HELLO *PGM, type CALL HELLO on the QCMD line and press ENTER.
You can use the Start Programming Development Manager (STRPDM) command to
start an edit session, and enter your source statements.
Besides PDM, there are several other ways to enter your source:
v The Copy File (CPYF) command.
v The Start Source Entry Utility (STRSEU) command.
v The Programmer Menu.
This is by no means an exhaustive list. There are other ways of creating source and
placing it on an iSeries system, including NFS, and ftp.
The following example shows you how to create a library, a source physical file, a
member, start an edit session, enter source statements, and save the member.
1. To create a library called MYLIB, type:
CRTLIB LIB(MYLIB)
2. To create a source physical file called QCSRC in library MYLIB, type
CRTSRCPF FILE(MYLIB/QCSRC) TEXT(’Source physical file for all ILE C programs’)
QCSRC is the default source file name for ILE C commands that are used to
create modules and programs. For ILE C++ commands, the corresponding
default is QCPPSRC. For information about how to copy this file to an
Integrated File System file, see “Using the Integrated File System” on page 171.
3. To start an edit session type:
STRPDM
4. Choose option 3 (Work with members); specify the source file name QCSRC,
and the library MYLIB.
5. Press F6 (Create), enter the member name T1520ALP, and source type C. The
SEU Edit display appears ready for you to enter your source statements.
6. Type the following source into your SEU Edit display. Trigraphs can be used in
place of square brackets, as demonstrated in this example.
#include <stdio.h>
#include <ctype.h>
void main(void)
{
int c;
int i = 0, j = 0;
int sum = 0;
int count, cnt;
int num[MAXLEN]; /* An array of digits. */
char letter??(MAXLEN??); /* An array of characters. */
7. Press F3 (Exit) to go to the Exit display. Type Y (Yes) to save the member
T1520ALP.
The ILE C compiler recognizes source code written in any single-byte EBCDIC
CCSID (Coded Character Set Identifier) except CCSID 290, 905 and 1026. See
Chapter 18, “Internationalizing Your Program” on page 403 for information on
CCSIDs. You can use the trigraphs shown in Table 2 in place of characters in the C
character set that are not available on your keyboard.
Table 2. Trigraphs
C Character Trigraph
# ??=
[ ??(
] ??)
{ ??<
} ??>
\ ??/
| ??!
^ ??’
~ ??-
The C compiler also supports digraphs. The C++ compiler does not support
digraphs.
The Create Bound Program commands combine the steps of compiling and
binding. Using them is the same as first calling the CRTCMOD or CRTCPPMOD
Create Module command, then calling the Create Program (CRTPGM) command,
except that the module created by the Create Module command step is deleted
after the CRTPGM step.
To use the Create Bound Program commands, the source member must contain a
main() function.
You can use the CRTSQLCI command to start the ILE C compiler and create a
program object. The SQL database can be accessed from an ILE C program if you
embed SQL statements in the ILE C source.
Example
1. To create the program T1520ALP, using the source found in Figure 4 on page 21,
type the following command line:
CRTBNDC PGM(MYLIB/T1520ALP) SRCFILE(QCPPLE/QACSRC)
TEXT(’Adds integers and prints characters’) OUTPUT(*PRINT)
OPTION(*EXPMAC *SHOWINC *NOLOGMSG) FLAG(30) MSGLMT(10)
CHECKOUT(*PARM) DBGVIEW(*ALL)
For example,
CRTCMOD HELLO
CRTPGM HELLO
CALL HELLO
Both ILE C and C++ require the main() function, but in ILE C, it becomes the UEP
of an ILE program. After the PEP runs, it calls the associated UEP, and starts the
Integrated Language Environment program running.
MYPROG(*PGM)
TRNSRPT Module
INCALC Module
Modules or service programs listed in a binding directory are used only if they
provide an export that can satisfy any currently unresolved import requests.
Entries in the binding directory may refer to objects that do not yet exist at the
time the binding directory is created, but exists later.
For example,
CRTBNDDIR BNDDIR(MYBNDDIR) MODULES (MOD1, MOD2)
CRTCMOD PROG(MYPROG) BNDDIR (MYBNDDIR)
or
CRTBNDDIR BNDDIR(MYBNDDIR) MODULES (MOD1, MOD2)
CRTCPPMOD PROG(MYPROG) BNDDIR (MYBNDDIR)
You can bind modules created by the compiler with modules created by any of the
other ILE Create Module commands, including CRTRPGMOD, CRTCMOD,
CRTCBLMOD, or CRTCLMOD, or other ILE compilers.
Note: The modules or service programs to be bound must already have been
created.
Use the *DUPPROC option on the CRTPGM OPTION parameter to allow duplicate
procedure names. See ILE C/C++ Compiler Reference for further information on how
to handle this situation.
The listing is produced as a spooled file for the job you use to enter the CRTPGM
command. You can choose a DETAIL parameter value to generate the listing at
three levels of detail:
v *BASIC
v *EXTENDED
v *FULL
The default is not to generate a listing. If it is generated, the binder listing includes
the sections described in Table 4, depending on the value specified for DETAIL.
Table 4. Sections of the Binder Listing based on the DETAIL Parameter
Section Name *BASIC *EXTENDED *FULL
Command Option Summary X X X
Brief Summary Table X X X
Extended Summary Table X X
Binder Information Listing X X
Cross-Reference Listing X
Binding Statistics X
Figure 6 shows the basic binder listing for a program CVTHEXPGM. Note that this
listing is taken out of context. It only serves to illustrate the type of information
you may find in a binder listing.
xxxxxxxxxxxxx
xxxxxxxxxxxxxx
Create Program Page 1
5722SS1 V5R1M0 010525 MYLIB /CVTHEXPGM TORAS597 00/12/07 16:25:32
Program . . . . . . . . . . . . . . . . . . . . . : CVTHEXPGM
Library . . . . . . . . . . . . . . . . . . . . : MYLIB
Program entry procedure module . . . . . . . . . . : *FIRST
Library . . . . . . . . . . . . . . . . . . . . :
Activation group . . . . . . . . . . . . . . . . . : *NEW
Creation options . . . . . . . . . . . . . . . . . : *GEN *NODUPPROC *NODUPVAR *WARN *RSLVREF
Listing detail . . . . . . . . . . . . . . . . . . : *BASIC
Allow Update . . . . . . . . . . . . . . . . . . . : *YES
Allow bound *SRVPGM library name update . . . . . .: *NO
User profile . . . . . . . . . . . . . . . . . . . : *USER
Replace existing program . . . . . . . . . . . . . : *YES
Authority . . . . . . . . . . . . . . . . . . . . : *LIBCRTAUT
Target release . . . . . . . . . . . . . . . . . . : *CURRENT
Allow reinitialization . . . . . . . . . . . . . . : *NO
Storage model . . . . . . . . . . . . . . . . . . .: *SNGLVL
Interprocedural analysis . . . . . . . . . . . . .: *NO
IPA control file . . . . . . . . . . . . . . . . . : *NONE
IPA replace IL data . . . . . . . . . . . . . . . .: *NO
Text . . . . . . . . . . . . . . . . . . . . . . . : *ENTMODTXT
Each of the above approaches requires different data to make the change.
Updating a Program
In general, you can update a program by replacing modules as needed. You do not
have to re-create the program object. The ability to replace specific modules is
helpful if, for example, you are supplying an application to other sites that are
already using the program. You need only send the revised modules, and the
receiving site can update the application using the UPDPGM and UPDSRVPGM
commands.
The update commands work with both program and module objects. The
parameters for these commands are very similar to those for the Create Program
(CRTPGM) command. For example, to replace a module in a program, you would
enter the module name for the MODULE parameter and the library name.
To use the UPDPGM command, the modules to be replaced must be located in the
same libraries they were in when the program was created. You can specify that all
modules, or only some subsets of modules, are to be replaced.
Activation Groups
Activation is the process used to prepare an Integrated Language Environment
program to run. Activation allocates and initializes static storage for an Integrated
Language Environment program, and completes the binding of Integrated
Language Environment programs to Integrated Language Environment service
programs. The ACTGRP parameter on the CRTPGM and CRTSRVPGM commands
specifies the activation group in which a program or service program runs.
Public Interface
The public interface of a service program consists of the names of the exported
procedures and data items that can be referenced by other Integrated Language
Environment objects. In order to be exported from an ILE service program, a data
item must be exported from one of the module objects making up the ILE service
program.
The exports list is used to specify the public interface for a service program. A
signature is generated from the procedure and data item names listed in the
binder language. This signature can then be used to validate the interface to the
service program. As long as the public interface is unchanged, the clients of a
service program do not have to be recompiled after a change to the service
program.
If the interface to a service program changes, you may have to rebind all programs
bound to the original service program. However, depending on the changes and
how you implement them, you may be able to reduce the amount of rebinding if
you create the service program using binder language. In this case, after updating
the binder language source to identify new exports, you need to rebind only those
programs that require the new exports.
If you use binder language, a service program can be updated without requiring
programs calling it to be recompiled. For example, to add a new procedure to an
existing service program:
1. Create a module object for the new procedure.
2. Modify the binder-language source file to handle the interface associated with
the new procedure. Add any new export statements following the existing ones.
See “Updating a Service Program Export List” on page 42 for details on
modifying binder-language source files.
3. Recreate the original service program and include the new module.
Now existing programs can access the new functions. Since the old exports are in
the same order, they can still be used by the existing programs. Until it is
necessary to also update the existing programs, they do not have to be recompiled.
#include <iostream.h>
class Search {
private:
char skippat[256];
char * needle_p;
int needle_size;
public:
// Constructors
Search( unsigned char * needle, int size);
Search ( unsigned char * needle);
Search ( char * needle);
//Destructor
~Search () { delete needle_p;}
#include "search.h"
#include "search.h"
The modules that result from the compilation of these source files, SEARCH and
WHERE are bound into a service program, SERVICE1.
By default, the binder creates the service program in your current library.
The parameter EXPORT(*ALL) specifies that all data and procedures exported from
the modules are also exported from the service program.
Note: This sample application has been reduced to minimal functionality. It’s main
purpose is to demonstrate how to create a service program.
// myprog.cpp
// Finds a character string in another character string.
#include <stdio.h>
#include <iostream.h>
#include <stdlib.h>
#include "search.h"
#define HS "Find the needle in this haystack"
void main () {
int i;
The program creates an object of class Search. It invokes the constructor with a
value that represents the string of characters (″needle″) to be searched for. It calls
the member function where() with the string to be searched (″Find the needle in
this haystack″). The string ″needle″ is located, and its position in the target string
″Find a needle in this haystack″ is returned and printed.
To create the program MYPROG in library MYLIB, and bind it to the service program
SERVICE1, enter the following:
crtpgm pgm(mylib/myprog) bndsrvpgm(mylib/service1) myprog.cpp
Figure 7 shows the internal and external function calls between program MYPROG
and service program SERVICE1.
PGM MYPROGA
main function
Internal function call
search constructor
SRVPGM SERVICE1
where() function
During the process of making MYPROG ready to run, the system verifies that:
v The service program SERVICE1 in library MYLIB can be found.
v The public interface used by MYPROG when it was created is still valid at run
time.
Information about exports, which can be derived from the modules that form a
particular service program, may be used to create a binder language source file
which then defines the interface to this service program. A binder language source
file specifies the exports the service program makes available to all programs that
call it. This file can be specified on the EXPORT parameter of the CRTSRVPGM
command.
Binder language gives you better control over the exports of a service program.
This control can be very useful if you want to:
v Determine export and import mismatches in an application.
v Add functionality to service programs.
v Reduce the impact of changes to a service program on the users of an
application.
v Mask certain service program exports from service program users. That is, by
not listing certain functions or variables in the binder language source file, you
can prevent any calling programs from having access to these exports.
on a command line, indicating the module name and the library where the module
is stored. This command brings up the Display Module Information display. At the
bottom of this display, you find information about exported defined symbols,
consisting of the name and type of each symbol that can be exported from the
module.
Note: When the compiler compiles a source file, it encodes function names and
certain variables to include type and scoping information. This encoding
process is called name mangling. The symbol names in the sample display
below are shown in mangled form. The source code for module SEARCH is
shown in “Source Code Files” on page 36.
The following example shows the structure of a binder language source file:
STRPGEXP PGMLEVEL(*CURRENT)
EXPORT SYMBOL("mangled_procedure_name_a")
EXPORT SYMBOL("mangled_procedure_name_b")
...
...
EXPORT SYMBOL("mangled_procedure_name_x")
ENDPGMEXP
Note: You must specify the mangled name of each symbol on the EXPORT
command, because the binder looks for the mangled names of exports when
it tries to resolve import requests from other modules.
Once all the modules to be bound into a service program have been created, you
can create the binder language source file. You can write this file yourself, using
the Source Entry Utility (SEU), or you can let the iSeries system generate it for you,
through the Retrieve Binder Source (RTVBNDSRC) command.
For example, based on the information shown in Figure 8 on page 40, the binder
language source file for module SEARCH could list the following export symbols:
STRPGEXP PGMLEVEL(*CURRENT)
EXPORT SYMBOL(" __ct__6SearchFPc")
EXPORT SYMBOL(" __ct__6SearchFPUc")
EXPORT SYMBOL(" __ct__6SearchFPUci")
ENDPGMEXP
Note: After the binder language has been retrieved into a source file member, you
can edit the binder language and modify it as needed, for example, if you
make changes to a module, or if you want to make certain exports
unavailable to calling programs.
For detailed information on the RTVBNDSRC command and its parameters enter
RTVBNDSRC on a command line and press F1 for Help.
Member ONE in file MYLIB/QSRVSRC now contains the following binder language:
When you make changes to the exports of a service program this does not
necessarily mean that all programs that call this service program must be
re-created. You can implement changes in the binder language such that they are
backward compatible. Backward-compatible means that programs which depend
on exports that remain unchanged do not need to be re-created.
To ensure backward compatibility, add new procedure or data item names to the
end of the export list, and recreate the service program with the same signature.
This lets existing programs still use the service program, because the order of the
unchanged exports remains the same.
Using the demangling functions, you can write programs to convert a mangled
name to a demangled name and to determine characteristics of that name, such as
its type qualifiers or scope. For example, given the mangled name of a function,
the program returns the demangled name of the function and the names of its
The demangling functions classify names into five categories: function names,
member function names, special names, class names, and member variable names.
After you construct an instance of class Name, you can use the Kind member
function of Name to determine what kind of Name the instance is. Based on the kind
of name returned, you can ask for the text of the different parts of the name or of
the entire name.
If the character string passed to the Name constructor is not a mangled name, the
Demangle function returns NULL.
For further details about the demangling functions, refer to the information
contained in the <demangle.h> header file. If you installed ILE C/C++ using default
settings, this header file should be in IFS in the ’/QIBM/include’ directory and in
DM in ’QSYSINC/H’.
Use the *UNRSLVREF option to convert, create, or build pieces of code when all
the pieces of code are not yet available. After the development or conversion phase
has finished and all import requests can be resolved, make sure you re-create the
program or service program that has the unresolved imports.
The /C compiler option allows you to stop the compilation process after the
creation of the modules SEARCH and QCPPSRC. The binder is not invoked.
2. To create the corresponding binder language source file, enter the command:
rtvbndsrc module(mylib/search mylib/qcppsrc)
srcfile(mylib/qsrvsrc) srcmbr(two)
This command creates the binder language source file shown in Figure 10.
3. To create service program SERVICE2, invoke the binder as follows:
crtsrvpgm srvpgm(mylib/service2) srcfile(mylib/qsrvsrc)
srcmbr(two)" search where
Figure 10. Binder Language Source File Generated by the RTVBNDSRC Command
main()
SRVPGM SP1
func1()
SRVPGM SP2
func2()
The following import requests occur between program A and the two service
programs, SP1 and SP2, that are called by A:
1. Program A uses function func1(), which it imports from service program SP1.
2. Service program SP1 needs to import function func2() provided by service
program SP2, in order to provide func1() to program A.
3. Service program SP2, in turn, first needs to import func1 from service program
SP1 before being able to provide func2.
Creating Modules
Compile the source files m2.cpp and m3.cpp into module objects from which you
later create the service programs SP1 and SP2. This allows you to display their
exports with the DSPMOD command, or to generate binder language source with
the RTVBNDSRC command. To create module objects from the source files
described above, invoke the command:
CRTCPPMOD MODULE(MYLIB/m2)SRCFILE(MYLIB/QCPPSRC)
CRTCPPMOD MODULE(MYLIB/m3)SRCFILE(MYLIB/QCPPSRC)
The CRTCPPMOD compiler option indicates to the compiler that you do not want to
create a program object from the source files. The target library is specified by the
MODULE option.
This command results in the following binder language being created for module
M2, in library MYLIB, source file QSRVSRC, file member BNDLANG1:
To generate binder language for module M3, from which you want to create service
program SP2, issue the following command:
rtvbndsrc module(mylib/m3) srcfile(mylib/qsrvsrc) srcmbr(bndlang2)
This command results in the following binder language being created for module
M3, in library MYLIB, source file QSRVSRC, file member BNDLANG2:
If you try and create service program SP1 from module M2, using the binder
language shown in Figure 12 on page 46 and the compiler invocation:
CRTSRVPGM SRVPGM(MYLIB/SP1)
SRCFILE(MYLIB/QSRVSRC) SRCMBR(BNDLANG1) m2
you find that the binder tries to resolve the import for function func2(), but fails,
because it is not able to find a matching export. Therefore, service program SP1 is
not created.
If SP1 is not created, this leads to problems if you try and create service program
SP2 from module M3 using the binder language shown in Figure 13 and the
compiler invocation:
CRTSRVPGM SRVPGM(MYLIB/SP2)
SRCFILE(MYLIB/QSRVSRC) SRCMBR(BNDLANG2) m3
Service program SP2 is not created, because the binder fails in searching for the
import for func1() in service program S1P, which has not been created in the
previous step.
the binder fails, since service programs SP1 and SP2 do not exist.
Since the *UNRSLVREF option is specified, service program SP1 is created even
though the import request for func2() is not resolved.
2. To create service program SP2 from module M3, type:
Since service program SP1 now exists, the binder resolves all the import
requests required, and service program SP2 is created successfully.
3. To re-create the service program SP1, type:
CRTSRVPGM SRVPGM(MYLIB/SP1) SRCFILE(MYLIB/QSRVSRC)
SRCMBR(BNDLANG1) BNDSRVPGM(MYLIB/SP2) m2
Although service program SP1 does exist, the import request for func2() is not
resolved. Therefore, the re-creation of service program SP1 is required. Since
service program SP2 now exists, the binder resolves all import requests
required and, service program SP1 is created successfully.
4. To create program A, type:
CRTPGM PGM(MYLIB/A) BNDSRVPGM(MYLIB/SP1 MYLIB/SP2)
Since service programs SP1 and SP2 do exist, the binder creates the program A.
Since modules M2 and M3 are specified, all import requests are resolved, and
service program SP1 is created successfully.
3. To create service program SP2, type:
CRTSRVPGM SRVPGM(MYLIB/SP2) SRCFILE(MYLIB/QSRVSRC)
SRCMBR(BNDLANG2) BNDSRVPGM(MYLIB/SP1) m3.cpp
Since service program SP1 exists, the binder resolves all the import requests
required and service program SP2 is created successfully.
4. To re-create service program SP1, type:
CRTSRVPGM SRVPGM(MYLIB/SP1) SRCFILE(MYLIB/QSRVSRC)
SRCMBR(BNDLANG1) BNDSRVPGM(MYLIB/SP2) m2.cpp
Although service program SP1 does exist, the import request for func2() is not
resolved to the one in service program SP2. Therefore, a re-creation of service
program SP1 is necessary to make the circular reference work.
Since service program SP2 now exists, the binder can resolve the import request
for func2() from service program SP2, and service program SP1 is successfully
created.
Since service programs SP1 and SP2 do exist, the binder creates program A.
The option *UNRSLVREF ensures that the program binds to the service
program, although there is no matching export for MYPROG’s import void
print(char *).
Before you can run program MYPROG successfully, you must re-create service
program PRINT from the real source code, instead of from the placeholder code in
dummy.cpp.
Note: MYPROG only runs successfully if PRINT actually exports a function that
matches MYPROG’s import request.
New procedure or data item names should be added to the end of the export list
to ensure changes are compatible. A signature is generated by the order in which
the modules are processed and the order in which the symbols are exported from
the copied modules. A service program becomes difficult to update once the
exports are used by other ILE C/C++ programs. If the service program is changed,
the order or number of exports could change. If the signature changes all ILE
C/C++ programs and service programs that use the changed service program have
to be re-created.
The following example shows how to add a new procedure called cost2() to
service program COST without having to re-create the existing program COSTDPT1
that requires an export from COST.
Program Description
The figure below shows the exports in the existing version of service program
COST, and in the updated version.
*SRVPGM *SRVPGM
Exports: Exports:
cost1 () cost1 ()
cost2 ()
The figure below shows the import requests in the existing program COSTDPT1, and
in the new program COSTDPT2.
*PGM *PGM
Imports: Imports:
cost1 () cost2 ()
The binder language for the old version of service program COST is located in
member BND of source file QSRVSRC, in library MYLIB:
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL("cost1__Fi9_DecimalTXSP10SP2_")
ENDPGMEXP
The updated binder language includes the new export procedure cost2(). It is
located in member BNDUPD of source file QSRVSRC, in library MYLIB:
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL("cost1__Fi9_DecimalTXSP10SP2_")
EXPORT SYMBOL("cost2__Fi9_DecimalTXSP10SP2_9_DecimalTXSP3SP1_")
ENDPGMEXP
In the binder language source that defines the old service program, the PGMLVL
value is changed from *CURRENT to *PRV:
STRPGMEXP PGMLVL(*PRV)
EXPORT SYMBOL("cost1__Fi9_DecimalTXSP10SP2_")
ENDPGMEXP
Note: If you want to ensure that existing programs can call the new version of the
service program without being re-created, make sure to:
1. Add the new exports to the end of the symbol list in the binder language
2. Explicitly specify a signature for the new version of the service program
that is identical to the signature of the old version.
It is necessary to re-create the service program COST, using the two modules
COST1 and COST2 and the updated version of the binder language BNDUPD, so
that it supports the new cost2() function. Program COSTDPT1, which used COST
before it was re-created, remains unchanged.
Run program COSTDPT2 from an OS/400 command line using the CL command
CALL COSTDPT2.
In addition to the source for the CMD, CL, and ILE C programs, you need Data
Description Specification (DDS) source for the output file, binder language source
for the ILE C service programs and a binding directory for the ILE C service
programs.
Program T1520PG1
Module T1520IC1 Module T1520IC2
main() calc_and_format()
When an OS/400 job starts, the system creates two activation groups to be used by
OPM programs. One activation group is reserved for OS/400 system code. The
other is used for all other OPM programs. You cannot delete the OPM default
activation groups. They are deleted by the system when your job ends. An ILE C
program may leave an activation group, and return to a call stack entry that is
running in another activation group if any of the following are processed:
v An exit() function
v An unhandled exception
v A return statement from the main() function
v Alongjmp() function — used to jump back to a previous invocation that is
before the previous control boundary
Therefore when the CL Command program calls the CL program, all the resources
necessary to run these programs are allocated in the default activation group.
When the CL program calls the ILE C program, because the ILE C program is
created with ACTGRP(*NEW), a new activation group is started. The ILE C service
programs are also activated in this new activation group because they are created
with ACTGRP(*CALLER).
In the following example, the ILE C program and ILE C service programs are
activated within one activation group because the programs are developed as one
cooperative application. To isolate programs from other programs running in the
same job you can select different activation groups. For example, a complete
customer solution may be provided by integrating software packages from four
different vendors. Different activation groups ease the integration by isolating the
resources associated with each vendor package.
Task Overview
In the steps that follow, you will:
v Create a physical file to contain an audit trail for the ILE C program
v Create a CL program that passes the parameters item name, price, quantity, and
user ID to an ILE C program.
v Create a CL command prompt to enter data for item name, price, and quantity.
The CL command prompt passes the data to the CL program which in turn calls
an ILE C program.
Instructions
1. To create a physical file T1520DD1 using the source shown below, type:
CRTPF FILE(MYLIB/T1520DD1) SRCFILE(QCPPLE/QADDSSRC) MAXMBRS(*NOMAX)
R T1520DD1R
USER 10 COLHDG(’User’)
ITEM 20 COLHDG(’Item name’)
PRICE 10S 2 COLHDG(’Unit price’)
QTY 4S COLHDG(’Number of items’)
TXRATE 2S 2 COLHDG(’Current tax rate’)
TOTAL 21 COLHDG(’Total cost’)
DATE 6 COLHDG(’Transaction date’)
K USER
This file contains the audit trail for the ILE C program T1520PG1. The DDS
source defines the fields for the audit file.
2. To create a CL program T1520CL1 using the source shown below, type:
CRTCLPGM PGM(MYLIB/T1520CL1) SRCFILE(QCPPLE/QACLSRC)
This program passes by reference, the CL variables item name, price, quantity,
and user ID to an ILE C program T1520PG1. Variables in a CL program are
passed by reference, allowing an ILE C program to change the contents in the
CL program.
The Retrieve Job Attributes (RTVJOBA) command obtains the user ID for the
audit trail.
You use this CL command prompt to enter the item name, price, and quantity
for the ILE C program T1520PG1.
4. To compile module T1520IC1 using the source shown below, type:
CRTCMOD MODULE(MYLIB/T1520IC1) SRCFILE(QCPPLE/QACSRC) OUTPUT(*PRINT) DBGVIEW(*ALL)
The main() function in this module is the UEP which is the target of a
dynamic program call from the CL program T1520CL1. The UEP receives
control from the PEP.
The main() function in this module receives the incoming arguments from the
CL program T1520CL1 that are verified by the CL command prompt
T1520CM1. All the incoming arguments are pointers. The variable item_name
is null ended within the CL program T1520CL1.
/* This function calculates the tax and formats the total cost. The */
/* function calc_and_format() returns 1 if successful and 0 if it */
/* fails. */
#include <stdio.h>
#include <string.h>
#include <decimal.h>
/* Tax rate is imported from the service program T1520SP2. */
const extern decimal (2,2) taxrate;
int calc_and_format (decimal (10,2) price,
short int quantity,
char formatted_cost[22])
{
decimal (17,4) hold_result;
char hold_formatted_cost[22];
int i,j;
memset(formatted_cost, ’ ’, 21);
hold_result = (decimal(4,0))quantity *
price *
(1.00D+taxrate); /* Calculate the total cost. */
if (hold_result < 0.01D || hold_result > 1989800999801.02D)
{
printf("calc out of range:%17.4D(17,4)\n", hold_result);
return(0);
}
/* Format the total cost. */
sprintf(hold_formatted_cost, "%21.2D(17,4)", hold_result);
j = 0;
Figure 23. ILE C Source to Calculate Tax and Format Cost (Part 1 of 2)
Figure 23. ILE C Source to Calculate Tax and Format Cost (Part 2 of 2)
DBGVIEW(*ALL) specifies that you want a root source view and a listing
view, along with debug data to debug this module.
The function calc_and_format in this module calculates and formats the total
cost. To do the calculation, the data item taxrate is imported from service
program T1520SP2. This data item must be imported because it is not defined
in this module (T1520IC2).
6. To compile module T1520IC3 using the following source, type:
CRTCMOD MODULE(MYLIB/T1520IC3) SRCFILE(QCPPLE/QACSRC)
OUTPUT(*PRINT) DBGVIEW(*SOURCE) OPTION(*SHOWUSR)
/* These includes are for the call to QWCCVTDT API to get the system */
/* date to be used in the audit trail. */
#include <QSYSINC/H/QWCCVTDT>
#include <QSYSINC/H/QUSEC>
/* DDS mapping of the audit file, T1520DD1. */
#include "myinc"
/* Tax rate is imported from service program T1520SP2. */
const extern decimal (2,2) taxrate;
void write_audit_trail (char user_id[10],
char item_name[],
decimal (10,2) price,
short int quantity,
char formatted_cost[22])
{
char char_item_name[21];
char char_price[11];
char temp_char_price[11];
char char_quantity[4];
char char_date[6];
char char_taxrate[2];
/* Qus_EC_t is defined in QUSEC. */
Qus_EC_t errcode;
char get_date[16];
int i;
double d;
/* File field structure is generated by the #pragma mapinc directive. */
MYLIB_T1520DD1_T1520DD1R_both_t buf1;
_RFILE *fp;
/* Get the current date. */
errcode.Bytes_Provided = 0;
QWCCVTDT ("*CURRENT ", "", "*MDY ", get_date, &errcode);
memcpy (char_date, &(get_date[1]), 6);
The function write_audit_trail in this module writes the audit trail for the
ILE C program T1520PG1. To write the audit trail, the tax rate is imported
from the service program T1520SP2. This module (T1520IC3) is used to create
service program T1520SP1.
Note: This source requires 2 members, QUSEC and QWCCVTDT, that are in
the QSYSINC/H file. The QSYSINC library is automatically searched
for system include files as long as OPTION(*STDINC) (the default) is
specified on CRTBNDC or CRTCMOD.
This service program exports the tax rate data so that it can be used by
calc_and_format and write_audit_trail. The data item taxrate is an export
because it is coded in this service program so that it can be imported by
calc_and_format in service program T1520IC2 and the write_audit_trail in
T1520IC3. This module is used to create service program T1520SP2.
Note: If you do not specify DBGVIEW(*SOURCE) you can debug the modules
that reference taxrate, but you cannot display taxrate during that debug
session nor can you debug this module that defines taxrate.
8. To create a binding directory entry T1520BD1, type:
CRTBNDDIR BNDDIR(MYLIB/T1520BD1)
This binding directory is used to contain the names of two service programs
T1520SP1 and T1520SP2 that are needed to create service program T1520SP1
and ILE C program T1520PG1.
9. To add two service program names to the binding directory T1520BD1, type:
ADDBNDDIRE BNDDIR(MYLIB/T1520BD1) OBJ((MYLIB/T1520SP1 *SRVPGM))
ADDBNDDIRE BNDDIR(MYLIB/T1520BD1) OBJ((MYLIB/T1520SP2 *SRVPGM))
The service program names T1520SP1 and T1520SP2 can be added even
though the service program objects do not exist yet.
10. Type:
CRTSRCPF FILE(MYLIB/QSRVSRC) MBR(T1520SP2)
11. To create a source physical file QSRVSRC using the following binder language
source:
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL(’taxrate’)
ENDPGMEXP
Use the binder language to define the export data item taxrate of the service
program T1520SP2. See the binder language in QCPPLE/QASRVSRC member
T1520SP2. BND is the source type for binder language source.
The End Program Export (ENDPGMEXP) command identifies the end of the
exports from the service program T1520SP2.
The symbol name taxrate is also used to create a signature. The signature
validates the public interface to the service program T1520SP2 at activation.
This ensures that the ILE C service program T1520SP1 and the ILE C program
T1520PG1 can use service program T1520SP2 without being re-created.
STRPGMEXP PGMLVL(*CURRENT)
EXPORT SYMBOL(’write_audit_trail’)
ENDPGMEXP
ENTMOD(*ONLY) specifies that only one module from the list of modules
can have a PEP. An error is issued if more than one module is found to have a
PEP. If ENTMOD(*FIRST) is used then the first module found from a list of
modules, that has a PEP is selected as the PEP. All other modules with PEPs
are ignored.
In addition to the CALL command, the TFRCTL command, and as a CPP there are
several other ways to run a program. You can use:
v The Programmer Menu..
v The Start Programming Development Manager (STRPDM) command..
v A high-level language CALL statement. Chapter 16, “Calling Conventions” on
page 325 contains information on interlanguage calls.
v The EVOKE statement from an ICF file.
v The QCAPCMD program.
v An ILE C/C++ program can be called by the REXX interpreter.
Note: When a CRTPGM parameter does not appear in the Create Bound Program
command invocation, the default is the CRTPGM parameter. For example,
the parameter ACTGRP(*NEW) is the default for the CRTPGM command,
and is used for the Create Bound Program command. You can change the
CRTPGM parameter defaults using the Change Command Defaults
(CHGCMDDFT) command.
The ILE C/C++ run-time library functions are bound to the application in the
same activation group in which it is called. Therefore all program activations in the
same activation group share one instance of the ILE C/C++ run-time library and
the state of the ILE C/C++ run time propagates across program call boundaries.
That is, if one program in an activation group changes the state of the ILE C/C++
run time, then all other programs in that activation group are affected. For
example, other programs in the same activation group are affected by the locale
setting of an application or the multibyte functions’ shift-in/shift-out state.
If ACTGRP is set to *CALLER, multiple calls of an ILE C/C++ program share one
instance of the ILE C/C++ run-time library state in the same activation group.
Through this option, ILE C/C++ programs can run within the OPM default
activation groups. Certain restrictions exist for ILE C/C++ programs that run in
the OPM default activation groups. For example, you are not allowed to register
atexit() functions within the OPM default activation groups.
If the activation group is named, all calls to programs in this activation group
within the same job share the same instance of the ILE C/C++ run-time library
state.
Activation Groups
After successfully creating an ILE C/C++ program, you can run your code.
Activation is the process of getting an ILE C/C++ program or service program
ready to run. When an ILE C/C++ program is called, the system performs
activation. Because ILE C/C++ service programs are not called, they are activated
during the call to an ILE C/C++ program that directly or indirectly requires their
services.
When activation allocates the storage necessary for the static variables that are
used by an ILE C/C++ program, the space is allocated from an activation group.
At the time the ILE C/C++ program or service program is created, you specify the
activation group that should be used at run time.
Once an ILE C/C++ program is activated, it remains activated until the activation
group is deleted. Even though they are activated, programs do not appear in the
call stack unless they are running.
An activation group can continue to exist even when the main() function of an ILE
C/C++ program is not on the call stack. This occurs when the ILE C/C++ program
was created with a named activation group (specifying a name on the ACTGRP
option of the CRTPGM command), and the main() function issues a return. This
can also occur when the ILE C/C++ program performs a longjmp() across a
control boundary by using a jump buffer that is set in an ILE C/C++ procedure.
This procedure is higher in the call stack and before the nearest control boundary.
Calling a Program
When you call a program, the OS/400 system locates the corresponding executable
code and performs the instructions found in the program.
Note: Only programs can run independently. Service programs or other bound
procedures must be called from a program that requires their services.
Figure 30 illustrates the call to the CL program RUNCP, and the transfer of control to
the C++ program XRUN2.
To create and run programs RUNCP and XRUN2, follow the steps below:
1. Enter the source code below into a source physical file QCLSRC, in library MYLIB.
/* Source for CL Program RUNCP */
PGM PARM(&STRING)
DCL VAR(&STRING) TYPE(*CHAR) LEN(20)
DCL VAR(&NULL) TYPE(*CHAR) LEN(1) VALUE(X’00’)
Program RUNCP uses the TFRCTL command to pass control to the ILE C++
program XRUN2, which does not return to RUNCP.
3. Create program XRUN2 in library MYLIB from source file xrun2.cpp, shown
below:
// xrun2.cpp
// Source for Program XRUN2
// Receives and prints a null-terminated character string
#include <iostream.h>
Program XRUN2 receives a null terminated character string from the CL program
and prints the string.
Program Description
A newly created command COST prompts for and accepts user input values. It then
calls a C++ program CALCOST and passes it the input values. CALCOST accepts the
input values from the command COST, performs calculations on these values, and
prints results. Figure 31 illustrates this example.
Command
COST
C++ Program
CALCOST
Use the CL command prompt COST to enter item name, price, and quantity for
the ILE C++ program CALCOST.
3. Create program CALCOST from the source file calcost.cpp shown below:
// calcost.cpp
// Source for Program CALCOST
#include <iostream.h>
#include <string.h>
#include <bcd.h>
This program receives the incoming arguments from the CL command COST,
calculates a cost, and prints values. All incoming arguments are pointers.
4. Enter data for the program CALCOST. From the command line, type COST and
press F4 (Prompt).
Type the data shown below into COST:
Hammers
1.98
5000
Nails
0.25
2000
The program object must exist. In this example, the library MYLIB is not in the
library list *LIBL. Since the program object is not in the library list, the library
name is specified on the CALL command.
\\ CALL program-name \]
For example, if MYLIB appears in your library list, you can simply type:
CALL MYPROG
If you need prompting for the command parameters, type CALL and press F4
(Prompt). If you need help, type CALL and press F1 (Help).
Example
The following example shows an ILE C/C++ program T1520REP that requires
parameters at run time.
1. To create the program T1520REP using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520REP) SRCFILE(QCPPLE/QACSRC)
When you call a program from a CL command line, the parameters you pass on
the CALL command are changed as follows:
v String literals are passed with a null terminating character.
v Numeric constants are passed as packed decimal digits.
v Characters that are not enclosed in single quotation marks are folded to
uppercase, and are passed with a null character.
v Characters that are enclosed in single quotation marks are not changed, and
mixed case strings are supported, and are passed with a null terminating
character.
For example:
CALL PGM(T1520REP) PARM(abc) - ABC\0 (converted to uppercase; passed as a string)
CALL PGM(T1520REP) PARM(’123.4’) - 123.4\0 (passed as a string)
CALL PGM(T1520REP) PARM(123.4) - 123.4 (passed as a packed decimal (15,5))
CALL PGM(T1520REP) PARM(’abc’) - abc\0 (passed as a string)
CALL PGM(T1520REP) PARM(’abC’) - abC\0 (passed as a string)
You can use the QCAPCMD program to add the null character to arguments that
are passed to an ILE C/C++ program.
The REXX interpreter treats all REXX variables as strings (without a null
terminator). REXX passes parameters to OS/400 which then calls the ILE C/C++
program. Conversion to a packed decimal data type still occurs, and strings are
null terminated.
Note: These changes only apply to calling a program from a command line, not to
interlanguage calls. See Chapter 16, “Calling Conventions” on page 325 for
information on ILE C/C++ calling conventions.
You can also type the parameters without specifying any keywords:
CALL library/program-name (param-1 param-2 ... param-n)
The following example demonstrates how to pass the value ’Hello, World’ to
program XRUN1 which expects parameters at run time. The source file xrun1.cpp for
program XRUN1 is shown below:
// xrun1.cpp
// Prints out command line arguments.
#include <iostream.h>
int main ( int argc, char *argv[])
{
int i;
for ( i = 1; i < argc; ++i )
cout << argv[i] << endl;
}
The resulting module and program objects are created into the default library,
in this example, MYLIB.
2. Run the program from a command line using the command:
CALL PGM(MYLIB/XRUN1) PARM(’Hello, World’)
Ending a Program
When a program ends normally, the system returns control to the caller. The caller
could be a workstation user or another program.
If a program ends abnormally during run time, and the program had been running
in a different activation group from its caller, the escape message CEE9901 is
issued and control is returned to the caller:
Application error <msgid> unmonitored by <pgm> at
statement <stmtid>, instruction <instruction>
A CL program can monitor for this exception by using the Monitor Message
(MONMSG) command.
If the program is running in the same activation group as is its caller and the
program ends abnormally, what message is issued depends on how the program
ended. If it ended with a function check, CPF9999 is issued.
Once a program (type *PGM) is called, it remains activated until the activation
group it runs in is deleted. Because service programs are not called directly, they
are activated during the call to the program that requires their services.
Note: OPM programs always run in the default activation group; you cannot
change their activation group specification.
For ILE programs you specify the activation group that should be used at run-time
through the ACTGRP parameter of the Create Program or Create Service Program
commands. You can choose between:
v Running your program in a named activation group.
v Accepting the default activation group:
– *NEW for programs
– *CALLER for service programs.
v Activating a program into the activation group of a calling program.
The system creates the named activation group as soon as the first program that
has specified this activation group is called. This group is then used by all
programs and service programs that have specified its name.
A named activation group ends when it is deleted through the Reclaim Activation
Group (RCLACTGRP) command. This command can only be used when the
activation group is no longer in use. It also ends when you call the exit() function
in your code.
When a named activation group ends, all resources associated with the programs
and service programs of the group are returned to the system.
Note: Using named activation groups may result in non-ANSI compliant run-time
behavior. If a program created using named activation groups remains
activated by a return statement, you encounter the following problems:
v Static variables are not reinitialized.
ACTGRP GROUP1
PROG1
PROG2
PROG3
In the following example, programs PROG1, PROG2, and PROG3 are part of the same
application and run in the same activation group, GROUP1. Figure 33 illustrates this
scenario:
To create these programs in the same activation group, you specify GROUP1 on the
ACTGRP parameter when you create each program:
crtpgm pgm(prog1) actgrp(group1) prog1.cpp
crtpgm pgm(prog2) actgrp(group1) prog2.cpp
crtpgm pgm(prog3) actgrp(group1) prog3.cpp
*NEW is the default value of the ACTGRP parameter on the CRTPGM command.
An activation group created with *NEW always ends when the last program
associated with it ends.
Note: *NEW is not valid for a service program, which can only run in the
activation group of its caller, or in a named activation group.
If you create a program with ACTGRP(*NEW), more than one user can call the
program at the same time without using the same activation group. Each call uses
a new copy of the program. Each new copy has its own data and opens its files.
In the following example, programs PROG4, PROG5, and PROG6 run in separate
unnamed activation groups.
PROG4
ACTGRP *NEW
PROG5
ACTGRP *NEW
PROG6
Because *NEW is the default, you obtain the same result with the following
invocations:
crtpgm pgm(prog4) prog4.cpp
crtpgm pgm(prog5) prog5.cpp
crtpgm pgm(prog6) prog6.cpp
Note: If you invoke three source files in one command, a single program object
PROG is created in activation group *NEW:
crtpgm pgm(prog) prog7.cpp prog8.cpp prog9.cpp
In the default activation groups, I/O files are not automatically closed. The I/O
buffers are not flushed unless explicitly requested.
Certain restrictions exist for ILE C/C++ programs running in the OPM default
activation groups. For example, you are not allowed to register atexit() functions
within the OPM default activation groups.
In the following example, a service program SRV1 is activated into the respective
activation groups of programs PROG7 and PROG8. PROG7 runs in a named activation
group GROUP2, while PROG8 runs in an unnamed activation group *NEW.
ACTGRP GROUP2
PROG7
SRV1
ACTGRP *NEW
PROG8
SRV1
Figure 35. Running a Service Program in the Activation Groups of Calling Programs
By default,the service program SRV1 is created into the activation group of each
calling program.
crtsrvpgm srvpgm(srv1) srv1.cpp
This occurs when the program was created with a named activation group, and the
main() function issues a return. It can also occur when the program performs a
longjmp() across a control boundary by using a jump buffer that is set in an ILE C
or C++ procedure. (This procedure is higher in the call stack and before the nearest
control boundary.)
Named activation groups are persistent. You must delete them explicitly. Otherwise
they end only when the job ends. The storage associated with programs running in
named activation groups is not released until these activation groups are deleted.
The OPM default activation group is also a persistent activation group. The storage
associated with ILE programs running in the default activation group is released
either when you sign off (for an interactive job) or when the job ends (for a batch
job).
In such situations, you may want to reclaim system resources that are no longer
needed for a program, but are still tied up because an activation group has not
been deleted. You have the following options:
v Delete a named activation group that is not in use through the Reclaim
Activation Group (RCLACTGRP) command .
The command provides options to either delete all eligible activation groups or
to delete an activation group by name.
v Free resources for programs that are no longer active through the Reclaim
Resources (RCLRSC) command.
You are not required to explicitly manage run-time storage. However, you may
wish to do so if you want to make use of dynamically allocated storage, for
example, if you do not know exactly how big an array should be. In this case you
could acquire the actual storage for the array at run-time, once your program
determines how big the array should be.
You can use one or more user-created heaps to isolate the dynamic storage
required by some programs and procedures within an activation group.
The rest of this section explains how to use a default heap to manage run-time
storage in a program.
Allocated dynamic storage remains allocated until it is explicitly freed, or until the
heap is discarded. The default heap is discarded only when the owning activation
group ends.
Programs in the same activation group all use the same default heap. If one
program accesses storage beyond what has been allocated, it can cause problems
for another program.
For example, assume that two programs, PGM1 and PGM2 are running in the same
activation group. 10 bytes are allocated for PGM1, but 11 bytes are changed by it. If
the extra byte was in fact allocated for PGM2 problems may arise for PGM2.
In an ILE C++ program, you manage dynamic storage belonging to the default
heap using the operators new and delete to create and delete dynamic objects.
Dynamic objects are never created and deleted automatically. Their creation can fail
if there is not enough free heap space available, and your programs must provide
for this possibility.
The following examples illustrate dynamic storage allocation with new and delete:
1. Dynamic allocation and de-allocation of storage for a class object:
TClass *p; // Define pointer
p= new TClass; // Construct object
if (!p) {
Error("Unable to construct object");
exit(1);
}
...
delete p; // Delete object
2. Dynamic allocation and de-allocation of storage for an array of objects:
TClass *array // Define pointer
array = new TClass[100];// Construct array of 100 objects
...
delete[] array; // Delete array
Messaging Support
Severity Compiler Response
Informational (00) Compilation continues. The message reports
conditions found during compilation.
Warning (10) Compilation continues. The message reports valid,
but possibly unintended, conditions.
Error (20) Compilation continues and object code is generated.
Error conditions exist that the compiler can correct,
but the program might not run correctly.
Severe error (30) Compilation continues, but object code is not
generated. Error conditions exist that the compiler
cannot correct.
Unrecoverable error (40) The compiler halts. An internal compiler error has
been found. This message should be reported to your
IBM service representative.
The ILE source debugger is used to help you locate programming errors in ILE
C/C++ programs and service programs.
Before you can use the ILE source debugger, you must use one of the non-default
debug options (DBGVIEW) when you compile a source file. Next, you can start
your debug session. Once you set breakpoints or other ILE source debugger
options, you can call the program.
The type of debug data that can be associated with a module is referred to as a
debug view.
Once you have created a module with debug data and bound it into a program
(*PGM), you can start debugging.
The compiler creates the listing view while the module (*MODULE) is being
generated. The listing view is created by copying the text of the appropriate source
members into the module. There is no dependency on the source members upon
which it is based, once the listing view is created.
For example, to create a listing view to debug a program created from the source
file myfile.cpp, type:
CRTBNDCPP MYFILE SRCSTMF(’/home/myfile.cpp’) DBGVIEW(*LIST)
Note: The maximum line length for a listing view is 255 characters.
Debug Commands
Many debug commands are available for use with the ILE source debugger. For
example, if you type break 10 on the debug command line, and press Enter, the
Integrated Language Environment source debugger adds an unconditional
breakpoint to line 10 of your source.
You can use these commands in the root source view, include source view, and
listing view. They can also be used when the source is compiled with the statement
view debug option. These options are: DBGVIEW(*NONE), DBGVIEW(*ALL),
DBGVIEW(*STMT), DBGVIEW(*SOURCE), and DBGVIEW(*LIST). Debug data is
created when you compile a module with one of the debug options.
Note: You must have *USE object authority to use the STRDBG command and
*CHANGE authority for the objects that are to be debugged.
Initially you can add as many as twenty programs to a debug session by using the
Program (PGM) parameter on the STRDBG command. They can be any
combination of OPM or ILE programs. (Depending on how the OPM programs
were compiled and also on the debug environment settings, you may be able to
debug them by using the ILE source debugger.)
You can also add as many as twenty service programs to a debug session by using
the Service program (SRVPGM) parameter on the STRDBG command.
Before you can debug an ILE C/C++ module with the Integrated Language
Environment source debugger, you must compile the module using the
CRTCMOD/CRTCPPMOD command or the CRTBNDC/CRTBNDCPP command
with one of the debug options *SOURCE, *LIST, *STMT, or*ALL.
You can create up to three views for each module that you want to debug. They
are:
v Root source view
A root source view contains text from the root source member. This view does
not contain any macro or include file expansions.
You can create a root source view by using the *SOURCE or *ALL options on
the DBGVIEW parameter for either the CRTCMOD/CRTCPPMOD or
CRTBNDC/CRTBNDCPP commands when you create the module or the
program, respectively.
The ILE C/C++ compiler creates the root source view while the module object
(*MODULE) is being compiled. The root source view is created using references
The first program that is specified on the STRDBG command is shown, if it has
debug data and, if OPM, the OPMSRC parameter is *YES. If ILE and it has debug
data, the entry module is shown; otherwise, the first module bound to the ILE
program with debug data is shown.
1. The OPM program was compiled with OPTION(*LSTDBG) or
OPTION(*SRCDBG). (Three OPM languages are supported: RPG, COBOL, and
CL. RPG and COBOL programs must be compiled with *LSTDBG, or
*SRCDBG, but CL programs must be compiled with *SRCDBG.)
2. The ILE debug environment is set to accept OPM programs. You can do this by
specifying OPMSRC(*YES) on the STRDBG command. (The system default is
OPMSRC(*NO).)
If these two conditions are not met, then debug the OPM program with the OPM
system debugger.
To start a debug session for the sample debug program DEBUGEX which calls the
OPM program RPGPGM, type:
STRDBG PGM(MYLIB/DEBUGEX MYLIB/RPGPGM) OPMSRC(*YES)
Once you have created a module with debug data or debug views, and bound it
into a program object (*PGM), you can start to debug your program.
This example shows you how to create three modules with debug views and start
a debug session.
1. To create module T1520IC1 with a root source view, type:
CRTCMOD MODULE(MYLIB/T1520IC1) SRCFILE(QCPPLE/QACSRC) DBGVIEW(*SOURCE)
A root source view and debug data is created to debug module T1520IC1.
2. To create module T1520IC2 with all three views, type:
CRTCMOD MODULE(MYLIB/T1520IC2) SRCFILE(QCPPLE/QACSRC) DBGVIEW(*ALL)
OPTION(*SHOWINC)
All views and debug data are created to debug module T1520IC2.
3. To create module T1520IC3 with both root source and include view, type:
CRTCMOD MODULE(MYLIB/T1520IC3) SRCFILE(QCPPLE/QACSRC) DBGVIEW(*SOURCE)
OPTION(*SHOWUSR)
An include view containing the root source member, user include files, and
debug data is created to debug module T1520IC3.
4. To create program T1520PG1, type:
CRTPGM PGM(MYLIB/T1520PG1) MODULE(MYLIB/T1520IC1 MYLIB/T1520IC2) ENTMOD(*ONLY)
BNDDIR(MYLIB/T1520BD1) DETAIL(*FULL)
Note: The creation of this program requires modules, service programs, and a
binding directory. See “Creating a Program in Two Steps” on page 24.
5. To start a debug session for program T1520PG1, type:
STRDBG PGM(MYLIB/T1520PG1)
Debug . . . ___________________________________________________________
________________________________________________________________________
F3=End program F6=Add/Clear breakpoint F10=Step F11=Display variable
F12=Resume F17=Watch variable F18=Work with watch F24=More keys
The module T1520IC1 is shown. It is the module with the main() function.
For ILE programs, use option1 (Add program) on the Work with Module List
display of the DSPMODSRC command. To remove an ILE program or service
program, use option 4 (Remove program) on the same display. When an ILE
program or service program is removed, all breakpoints for that program are
removed. There is no limit to the number of ILE programs or service programs
that can be included in a debug session at one time.
For OPM programs, you have two choices depending on the value specified for
OPMSRC. If you specified OPMSRC(*YES), by using either STRDBG, the SET
debug command, or Change Debug (CHGDBG) options, then you add or remove
an OPM program using the Work With Module Display. (Note that there will not
be a module name listed for an OPM program.) There is no limit to the number of
OPM programs that can be included in a debug session when OPMSRC(*YES) is
specified.
If you specified OPMSRC(*NO), then you must use the Add Program (ADDPGM)
command or the Remove Program (RMVPGM) command. Only twenty OPM
programs can be in a debug session at one time when OPMSRC(*NO) is specified.
Note: You cannot debug an OPM program with debug data from both an ILE and
an OPM debug session. If OPM program is already in an OPM debug
session, you must first remove it from that session before adding it to the
ILE debug session or stepping into it from a call statement. Similarly, if you
want to debug it from an OPM debug session, you must first remove it from
an ILE debug session.
Example
This example shows you how to add an ILE C service program to, and remove an
ILE C program from a debug session.
Note: Assume the ILE C program T1520ALP is part of this debug session, and the
program has been debugged. It can be removed from this debug session.
1. To add programs to or remove programs from a debug session type:
DSPMODSRC
Changing the debug options using the SET debug command affects the value for
the corresponding parameter, if any, specified on the STRDBG command. You can
also use the Change Debug (CHGDBG) command to set debug options.
Example
This example shows you how to allow the ILE source debugger to add an OPM
program to an ILE debug session.
Suppose you are in a debug session working with an ILE program and you decide
you should also debug an OPM program that has debug data available. To enable
the ILE source debugger to accept OPM programs, follow these steps:
1. After entering STRDBG, if the current display is not the Display Module Source
display, type:
DSPMODSRC
(Yes) for the OPM source debug support field, and press Enter to return to the
Display Module Source display.
You can now add the OPM program, either by using the Work with Module
display, or by processing a call statement to that program.
Example
This example shows how to setthe Update production files debug option from a
debug session.
1. To set debug options from a debug session type:
DSPMODSRC
The source of an OPM program can be shown if the following conditions are met:
1. The OPM program was compiled with OPTION(*LSTDBG).
2. The ILE debug environment is set to accept OPM programs; that is the value of
OPMSRC is *YES. (The system default is OPMSRC(*NO).)
Once you have displayed a view of a module, you may want to display a different
module or see a different view of the same module (if you created the module
with several different views). The ILE source debugger remembers that the last
position in which the module is displayed, and displays it in the same position
when a module is redisplayed. Lines that have breakpoints set are highlighted.
When a breakpoint, step, or message causes the program to stop and the display to
be shown, the statement where the breakpoint occurred is highlighted.
You can change the module that is shown on the Display Module Source display
by using:
v The Work with Module list display
v The Display Module debug command
If you use this option with an ILE program object, the entry module with a root
source, COPY, or listing view is shown (if it exists). Otherwise the first module
object bound to the program object with debug data is shown. If you use this
option with an OPM program object, then the source or listing view is shown (if
available).
Example
This example shows you how to change from the module shown on the Display
Module Source display to another module in the same program using Display
Module debug command.
Example
This example shows you how to change the view of the module shown on the
Display Module Source display.
1. To change the view of the module on the Display Module Source display type
DSPMODSRC, and press Enter. The Display Module Source display is shown.
2. Press F15 (Select view). The Select View window is as shown:
Display Module Source
..............................................................................
: Select View :
: :
: Current View . . . : ILE C root source view :
: :
: Type option, press Enter. :
: 1=Select :
: :
: Opt View :
: _ ILE C root source view :
: 1 ILE C include view :
: :
: Bottom :
: F12=Cancel :
: :
:............................................................................:
More...
Debug . . . ___________________________________________________________
________________________________________________________________________
F3=End program F6=Add/Clear breakpoint F10=Step F11=Display variable
F12=Resume F17=Watch variable F18=Work with watch F24=More keys
The current view is listed at the top of the window, and the other views that
are available are shown below. Each module in a program can have a different
set of views available, depending on the debug options used to create it.
3. Type a 1 next to the include view, and press Enter. The Display Module Source
display appears showing the module with an include source view. The source
of the include view will be shown at a statement position that is equivalent to
the statement position in the current view.
When the program stops, the Display Module Source display appears. Use this
display to evaluate variables, set more breakpoints, and run any of the source
debugger commands. The appropriate module is shown with the source positioned
to the line where the condition occurred. The cursor will be positioned on the line
where the breakpoint occurred if the cursor was in the text area of the display the
last time the source was displayed. Otherwise, it is positioned on the debug
command line.
If you change the view of the module after setting breakpoints, then the line
numbers of the breakpoints are mapped to the new view by the source debugger.
You can set and remove unconditional and conditional breakpoints by using:
v F13 (Work with module breakpoints)
v F6 (Add/Clear breakpoint)
You can also add breakpoints with the BREAK or TBREAK debug commands. You
can remove one, or all breakpoints from a program by using the Clear Program
debug command.
Example
This example shows you how to set and remove an unconditional breakpoint using
F6 (Add/Clear breakpoint), and add a conditional breakpoint using F13 (Work
with module breakpoints).
1. To work with a module, type DSPMODSRC and press Enter. The Display Module
Source display is shown.
If you want to set the breakpoint in the module shown, continue with step 3.
If you want to set a breakpoint in a different module, type display module
name on the debug command line where name is the name of the module that
you want to display.
2. Type display module T1520IC2, and press Enter.
3. To set an unconditional breakpoint, place the cursor on line 50.
4. Press F6 (Add/Clear breakpoint). A breakpoint is added to line 50 as shown:
The clear command can be used to remove a breakpoint. For example, clear
50 removes the breakpoint at line 50.
5. To set a conditional breakpoint press F13 (Work with module breakpoints).
The Work with Module Breakpoints display is shown.
6. On this display type 1 (Add) on the first line of the list to add a conditional
breakpoint.
7. To set a conditional breakpoint at line 35 when i is equal to 21, enter 35 for
the Line field, i==21 for the Condition field, and press Enter as shown:
Work with Module Breakpoints
System: TORASD80
Program . . . : T1520PG1 Library . . . : MYLIB
Module . . . : T1520IC2 Type . . . . . : *PGM
Type options, press Enter.
1=Add 4=Clear
Opt Line Condition
1 35 i==21
_ 50
You can set a conditional breakpoint to a statement. For example, if you have
a compiler listing that contains line numbers and statement numbers, you can
use the statement syntax to set a breakpoint on a specific statement when
there are several statements on a single line.
Note: After a watch condition has been registered, the new content at the watched
storage location is saved as the new current value of the corresponding
variable. The next watch condition will be registered if the new content at
the watched storage location changes subsequently.
Characteristics of Watches
When using watches, keep the following watch characteristics in mind:
v Watches are monitored on a system-wide basis, with a maximum number of 256
watches that can be active simultaneously. This number includes watches set by
the system.
Depending on overall system use, you may be limited in the number of watch
conditions you can set at a given time. If you try to set a watch condition while
the maximum number of active watches across the system is exceeded, you will
receive an error message and the watch condition is not set.
Note: If a variable crosses a page boundary, two watches are used internally to
monitor the storage locations. Therefore, the maximum number of
variables that can be watched simultaneously on a system-wide basis
ranges from 128 to 256.
v Watch conditions can only be set when a program is stopped under debug, and
the variable to be watched is in scope. If this is not the case, an error message is
issued when a watch is requested, indicating that the corresponding call stack
entry does not exist.
Note: Changes that are made to a watched storage location are ignored if they
are made by a job other than the one that set the watch condition.
v After the command is successfully run, your application is stopped if a program
in your session changes the content of the watched storage location, and the
Display Module Source display is shown.
If the program has debug data, it will be shown if a source view is available.
The source line of the statement that was about to be run when the content
change at the storage-location was detected is highlighted. A message indicates
which watch condition was satisfied. If the program cannot be debugged, the
text area of the display will be blank.
v Eligible programs are automatically added to the debug session if they cause the
watch-stop condition.
v When multiple watch conditions are hit on the same program statement, only
the first one will be reported.
v You can set watch conditions when you are using service jobs for debugging,
that is when you debug one job from another job.
Bottom
Command
===>____________________________________________________________________
F3=Exit F4=Prompt F5=Refresh F9=Retrieve F12=Cancel
The Work with Watch display shows all watches currently active in the debug
session. You can clear, and display watches from this display. When you select
Option 5 Display, the Display Watch window that is shown below displays
information about the currently active watch.
Work with Watch
..........................................................
: Display Watch : DEBUGGER
: :
: Watch Number ....: 1 :
: Address .........: 080090506F027004 :
: Length ..........: 4 :
: Number of Hits ..: 0 :
: :
: Scope when watch was set: :
: Program/Library/Type: PAYROLL ABC *PGM :
: :
: Module...: PAYROLL :
: Procedure: main :
: Variable.: salary :
: :
: F12=Cancel :
: :
..........................................................
Bottom
Command
===>____________________________________________________________________
F3=Exit F4=Prompt F5=Refresh F9=Retrieve F12=Cancel
Note: This display does not show watch conditions that are set by the system.
The watch number can be obtained from the Work with Watches display.
Note: While the CLEAR PGM command removes all breakpoints in the program
that contains the module being displayed, it has no effect on watches. You
must explicitly use the WATCH keyword with the CLEAR command to
remove watch conditions.
v The CL End Debug (ENDDBG) command removes watches that are set in the
local job or in a service job.
If the value of the variable salary changes subsequently, the application stops, and
the Display Module Source display is as shown:
Display Module Source
v The line number of the statement where the change to the watch variable was
detected is highlighted. This is typically the first executable line following the
statement that changed the variable.
v A message indicates that the watch condition was satisfied.
If a text view is not available, a blank Display Module Source display is shown,
with the same message as above in the message area.
Display Module Source
(Source not available)
F3=End program F12=Resume F14=Work with module list F18 Work with watch
F21=Command entry F22=Step into F23=Display output
Watch number 1 at instruction 18, variable: salary
When calls to other functions are encountered, you can step into an OPM program
if it has debug data available and if the debug session accepts OPM programs for
debugging.
If the ILE source debugger is not set to accept OPM programs, or if there is no
debug data available, then you see a blank Display Module Source display with a
message indicating that the source is not available. (An OPM program has debug
data if it was compiled with OPTION(*LSTDBG).)
If this variable is omitted, the default is 1. If one of the statements that are run
contains a call to another program, the ILE source debugger steps over the called
program.
Note: The called program must have debug data associated with it in order for it
to be shown in the Display Module Source display.
The STEP INTO command works with the CL CALL command as well. You can
take advantage of this to step through your program after calling it. After starting
the source debugger, from the initial Display Module Source display, enter STEP 1
INTO. This sets the step count to 1. Use the F12 key to return to the command line
and then call the program. The program stops at the first statement with debug
data.
5. Press F22 (Step into). One statement of the program runs, and then the Display
Module Source display of CPGM is shown.
The first executable statement of CPGM is processed (line 13) and then the
program stops.
Note: You cannot specify the number of statements to step through when you
use F22. Pressing F22 performs a single step.
14 return taxrate;
Bottom
Debug . . .______________________________________________________________________
_________________________________________________________________________________
Example
This example shows you how to use F10 (Step) to step over one statement at a
time in your program.
1. To work with a module type DSPMODSRC and press Enter. The Display Module
Source display is shown.
2. Type display module T1520IC2, and press Enter.
3. To set an unconditional breakpoint at line 50, type Break 50 on the debug
command line, and press Enter.
4. To set a conditional breakpoint at line 35, type Break 35 when i==21 on the
debug command line, and press Enter.
5. Press F12 (Resume) to leave the Display Module Source display.
6. Call the program. The program stops at breakpoint 35 if i is equal to 21, or at
line 50 whichever comes first.
7. To step over a statement, press F10 (Step). One statement of the program runs,
and then the Display Module Source display is shown. If the statement is a
function call, the function runs to completion. If the called function has a
breakpoint set, however, the breakpoint will be hit. At this point you are in the
function and the next step will take you to the next statement inside the
function.
Note: You cannot specify the number of statements to step through when you
use F10. Pressing F10 performs a single step.
Display Module Source
Program: T1520PG1 Library: MYLIB Module: T1520IC2
47 if (j<0) return(0);
48 if (hold_formatted_cost[i] == ’$’)
49 {
50 formatted_cost[j] = hold_formatted_cost[i];
51 break;
52 }
53 if (i<16 &&; !((i-2)%3))
54 {
55 formatted_cost[j] = ’,’;
56 --j;
57 }
58 formatted_cost[j] = hold_formatted_cost[i];
59 --j;
60 }
61
Debug . . . ___________________________________________________________
________________________________________________________________________
F3=End program F6=Add/Clear breakpoint F10=Step F11=Display variable
F12=Resume F17=Watch variable F18=Work with watch F24=More keys
Breakpoint at line 50.
The service program is added to debug for the user, and the DSPMODSRC panel
shows the procedure in the service program. From this point, modules in the
service program can be accessed using the Work with Modules display just like
modules in programs the user added to debug.
If you specify into on the step debug command, each statement in a procedure or
function that is called counts as a single statement. You can start the step into
function by using:
v The Step Into debug command
v F22 (Step into)
Example
This example shows you how to use F22 (Step into) to step into one procedure.
1. Type DSPMODSRC and press Enter. The Display Module Source display is shown.
2. To set an unconditional breakpoint at line 50, type Break 50 on the debug
command line, and press Enter.
3. To set a conditional breakpoint at line 35, type Break 35 when i==21 on the
debug command line, and press Enter.
4. Press F12 (Resume) to leave the Display Module Source display.
5. Call the program. The program stops at breakpoint 35 if i is equal to 21 or at
line 50 whichever comes first.
6. Press F22 (Step into). One statement of the program runs, and then the Display
Module Source display is shown. If the statement is a procedure or function
call, only the first statement of the procedure or function runs.
Note: You cannot specify the number of statements to step through when you
use F22. Pressing F22 performs a single step.
7. To step into 5 statements, type step 5 into on the debug command line, and
press Enter.
The next five statements of your program are run, and then the Display
Module Source display is shown. If the third statement is a call to a function,
the first two statements of the calling procedure run, and the first three
statements of the function run.
8. To step into 11 statements, type step 11 into on the debug command line, and
press Enter. The next 11 statements of your program runs. The Display Module
Source display is shown.
You can use the Enter key as a toggle switch between displays.
You can change variables by using the eval debug command with assignment.
Example
This example shows you how to use the F11 (Display variable) to display a
variable.
Messages with multiple line responses will cause the Evaluate Expression
display to be shown. This display will show all response lines. It also shows a
history of the debug commands entered and the results from these commands.
To return to the Display Module Source display, press the ENTER key. You can
use the Enter key as a toggle switch between displays. Single-line responses
will be shown on the Display Module Source message line.
You can also use the eval debug command to determine the value of an
expression. For example, if j has a value of 1024, type eval (j * j)/512 on the
debug command line. You use the qual debug command to determine the line
or statement number within the function that you want the variables scoped to
for the eval debug command. The Evaluate Expression display shows (j *
j)/512 = 2048.
Example
This example shows you how to use the eval debug command to assign an
expression to a variable.
1. Type DSPMODSRC and press Enter. The Display Module Source display is shown.
2. Type display module T1520IC2, and press Enter.
3. To specify the scope of the eval command you can use a qualify command. For
example, qual 48. will qualify the eval command to the scope that line 48 is
located at. Line 48 is the number within the function to which you want the
variables scoped for the following eval debug command.
Note: You do not always have to use the qual debug command before the eval
debug command. An automatic qual is done when a breakpoint is
encountered or a step is done. This establishes the default for the
scoping rules to be the current stop location.
Debug . . . ___________________________________________________________
________________________________________________________________________
F3=End program F6=Add/Clear breakpoint F10=Step F11=Display variable
F12=Resume F17=Watch variable F18=Work with watch F24=More keys
hold_formatted_cost[1]= ’#’ = ’#’
on the debug command line. variable-name is the name of the variable that you
want to change and value is an identifier, literal, or constant value that you want
to assign to variable variable-name.
EVAL COUNTER=3
Use the EVAL debug command to assign numeric, alphabetic, and alphanumeric
data to variables. When you assign values to a character variable, the following
rules apply:
v If the length of the source expression is less than the length of the target
expression, the data is left justified in the target expression and the remaining
positions are filled with blanks
v If the length of the source expression is greater than the length of the target
expression, the data is left justified in the target expression and truncated to the
length of the target expression.
Note: You do not always have to use the QUAL debug command before the EVAL
debug command. An automatic QUAL is done when a breakpoint is
encountered or a step is done. This establishes the default for the scoping
rules to be the current stop location.
The example below shows the results of changing the array element at 1 from $ to
#.
Example
This example shows you how to use the equate debug command with a variable
name.
1. Type DSPMODSRC and press Enter. The Display Module Source display is shown.
2. To equate an expression, type equate <name> <definition> where <name> is a
character string that contains no blanks and <definition> is a character string
separated from <name> by at least one blank. The character strings can be in
uppercase, lowercase, or mixed case. The length of the character strings
combined is limited to 144 characters, which is the length of the command line.
After any equates have been expanded, the length is limited to 150 characters,
which is the maximum command length. For example, type equate dv display
variable .
To see the equates that are defined for this debug session, type: display equate. A
list of the active equates is shown on the Evaluate Expression display.
Displaying a Structure
The following example shows a structure with two elements being displayed. Each
element of the structure is formatted according to its type and displayed.
1. Type DSPMODSRC, and press Enter. The Display Module Source display is shown.
2. Set a breakpoint at line 9.
3. Press F12 (Resume) to leave the Display Module Source display.
4. Call the program. The program stops at the breakpoint at line 9.
5. Type eval test on the debug command line, and press Enter as shown:
Display Module Source
Program: TEST1 Library: DEBUG Module: MAIN
1 struct {
2 char charValue;
3 unsigned long intValue;
4 } test;
5
6 main(){
7 test.intValue = 10;
8 test.charValue = ’c’;
9 test.charValue = 11;
10 }
Bottom
Debug . . . eval test__________________________________________________
________________________________________________________________________
F3=Exit program F6=Add/Clear breakpoint F10=Step F11=Display variable
F12=Resume F17=Watch variable F18=Work with watch F24=More keys
6. Press Enter to go to the next display. The Evaluate Expression Display shows
the entire structure as shown:
Evaluate Expression
Previous debug expressions
> BREAK 9
> EVAL test
test.charValue = ’c’
test.intValue = 10
7. Press Enter from the Evaluate Expression Display to return to the Display
Module Source screen.
> BREAK 9
> EVAL test: x 32
00000 83000000 0000000A 00000000 00000000 - c..............
00010 00000000 00000000 00000000 00000000 - ...............
The following example shows the usage of the ’:f’ syntax to specify that the
newline character (x’15’) should be scanned for while displaying string output. If
the end of the display line occurs, the output is wrapped to the next display line.
When the :f formatting code is used, the text string will display on the current line
until a newline is encountered. If no newline character is encountered before the
end of the display screen line, the output is wrapped until a newline is found.
DBCS SO/SI characters are added as necessary to make sure they are matched.
int main()
{
char testc[]={"This is the first line.\nThis is the second line."
"\nThis is the third line."};
int i;
i = 1;
}
In cases where you are evaluating structures, records, classes, arrays, pointers,
enumerations, bit fields, unions or functions, the message returned when you press
F11 (Display variable) may span several lines. Messages that span several lines are
shown on the Evaluate Expression display to show the entire text of the message.
Once you have finished viewing the message on the Evaluate Expression display,
press Enter to return to the Display Module Source display.
The Evaluate Expression display also shows all the past debug commands that you
entered and the results from these commands. You can search forward or
backward on the Evaluate Expression display for a specified string, or text and
retrieve or reissue debug commands.
Pointers
// Display a pointer
>eval pc1
pc1 = SPP:0000C0260900107C
// Dereference a pointer
>eval *pc1
*pc1 = ’C’
// Casting a pointer
>eval *(short *)pc1
*(short *)pc1 = -15616
Simple Variables
// Perform logical operations
>eval i1==u1 || i1<u1
i1==u1 || i1<u1 = 0
Figure 39. Sample EVAL Commands for Pointers, Variables, and Bit Fields (Part 1 of 2)
// Implicit conversion
>eval u1 = -10
u1 = -10 = 4294967286
// Implicit conversion
>eval (int)u1
(int)u1 = -10
Bit Fields
// Display an entire structure
>eval bits
bits.b1 = 1
bits.b4 = 2
Figure 39. Sample EVAL Commands for Pointers, Variables, and Bit Fields (Part 2 of 2)
The examples below show the use of the EVAL command with structures, unions,
and enumerations. The structures, unions, and enumerations are based on the
source in “Sample Source for EVAL Commands” on page 126.
Note: For C++, the structures are simple structures, not Classes.
Figure 40. Sample EVAL Commands for C Structures, Unions and Enumerations (Part 1 of 2)
// Assign by number
>eval Number = 1
Number = 1 = two (1)
// Assign by enumeration
>eval Number = three
Number = three = three (2)
Figure 40. Sample EVAL Commands for C Structures, Unions and Enumerations (Part 2 of 2)
Figure 41. Sample EVAL Commands for System and Space Pointers
You can use the EVAL command on C and C++ language features and constructs.
The ILE source debugger can display a full class or structure but only with those
fields defined in the derived class. You can display a base class in full by casting
the derived class to the particular base class.
The example below shows the use of the EVAL command with C++ language
constructs. The C++ language constructs are based on the source in “Sample
// Disambiguate variable ac
> EVAL A::ac
A::ac = 12
The example below shows the results of evaluating a template class. You must
enter a template name that matches the demangled template name. Typedef names
To circumvent this problem you can lower the optimization level of a module to
display variables accurately as you debug a program, and then raise the level
again afterwards to improve the program efficiency as you get the program ready
for production.
Example
This example shows you how to change the optimization level of module T1520IC4
from *FULL to *NONE to allow variables to be displayed and changed when the
program is in debug mode. Once debug is complete, you can change the
optimization level back to *FULL for improved run-time performance.
1. Type WRKMOD MODULE(T1520IC1) and press Enter. The Work with Modules
display is shown.
2. Select option 5 (Display) to see the attribute values that need to be changed.
The Display Module Information display is shown:
Display Module Information
Module . . . . . . . . . . . . : T1520IC1
Library . . . . . . . . . . : MYLIB
Detail . . . . . . . . . . . . : *BASIC
Module attribute . . . . . . . : CLE
Module information:
Module creation date/time . . . . . . . . . . . . . . : 93/09/93 12:00:00
Source file . . . . . . . . . . . . . . . . . . . . . : QACSRC
Library . . . . . . . . . . . . . . . . . . . . . . : MYLIB
Source member . . . . . . . . . . . . . . . . . . . . : T1520IC1
Source file change date/time . . . . . . . . . . . . : 93/08/18 13:31:40
Owner . . . . . . . . . . . . . . . . . . . . . . . . : SMITH
Coded character set identifier . . . . . . . . . . . : 65535
Text description . . . . . . . . . . . . . . . . . . :
Creation data . . . . . . . . . . . . . . . . . . . . : *YES
Intermediate language data . . . . . . . . . . . . . : *NO
More...
Press Enter to continue.
F3=Exit F12=Cancel
Note: In the display shown above, the Creation data value is *YES. This means
that the module can be translated again once the optimization level
value is changed. If the value is *NO, you must compile the module
again in order to change the optimization level.
3. Press the Roll Down key to see more information for the module as shown:
Display Module Information
Module . . . . . . . . . . . . : T1520IC4
Library . . . . . . . . . . : MYLIB
Detail . . . . . . . . . . . . : *BASIC
Module attribute . . . . . . . : CLE
Sort sequence table . . . . . . . . . . . . . . . . . : *HEX
Language identifier . . . . . . . . . . . . . . . . . : *JOBRUN
Optimization level . . . . . . . . . . . . . . . . . : *NONE
Maximum optimization level . . . . . . . . . . . . . : *FULL
Debug data . . . . . . . . . . . . . . . . . . . . . : *YES
Compressed . . . . . . . . . . . . . . . . . . . . . : *NO
Program entry procedure name . . . . . . . . . . . . : _C_pep
Number of parameters . . . . . . . . . . . . . . . . : 0 255
Module state . . . . . . . . . . . . . . . . . . . . : *USER
Module domain . . . . . . . . . . . . . . . . . . . . : *SYSTEM
Number of exported defined symbols . . . . . . . . . : 1
Number of imported (unresolved) symbols . . . . . . . : 10
Press Enter to continue.
More...
F3=Exit F12=Cancel
Note: Imports can be left unresolved using the *UNRSLVREF parameter of the
CRTPGM command.
7. Create the program again using the CRTPGM command.
Removing all observability reduces the module to its minimum size (with
compression). It is not possible to change the module in any way unless you
compile the module again. To compile it again, you must have authorization to
access the source code.
Using the CHGMOD command, you can remove either kind of data from the
module, remove both types, or remove none.
Example
Use the following procedure to remove observability from an ILE C/C++ program:
1. Type WRKMOD and press Enter. The Work with Modules display is shown.
2. Select option 5 (Display) to see the attribute values that need to be changed.
The Display Module Information display is shown.
Check the value of the field Creation data. If it is *YES, the Create Data exists,
and can be removed. If this value is *NO, there is no Create Data to remove.
The module cannot be translated again unless you re-create it.
Note: Imports can be left unresolved using the *UNRSLVREF parameter of the
CRTPGM command. Program creation time is the same.
8. Create the ILE program again by using the CRTPGM command.
The sample EVAL command for displaying system and space pointers presented in
Figure 41 on page 121 is based on the following source:
#include <iostream.h>
#include <mispace.h>
#include <pointer.h>
The sample EVAL command for displaying C++ constructs presented in Figure 42 on
page 122 is based on the following source:
class B {
public:
int b;
};
class C {
public:
int ac;
static int c;
int amb;
int not_amb;
};
class outter {
public:
static int static_i;
class F : public E<int>, public D {
public:
int f;
int not_amb;
void funct();
} inobj;
};
int VAR = 2;
void outter::F::funct()
{
int local;
a=1; //EVAL VAR : Is VAR in global scope
b=2;
c=3;
local = 828;
int VAR;
VAR=1;
static_i=10;
A::ac=12;
C::ac=13;
not_amb=32;
not_amb=13;
// Stop here and show:
// EVAL VAR : is VAR in local scope
// EVAL ::VAR : is VAR in global scope
// EVAL %LOCALVARS : see all local vars
// EVAL *this : fields of derived class
// EVAL this->f : show member f
// EVAL f : in derived class
// EVAL a : in base class
// EVAL b : in Virtual Base class
// EVAL c : static member
// EVAL static_i : static var made visible
: by middle-end
// EVAL au : anonymous union members
// EVAL a=49 :
// EVAL au
// EVAL ac : show ambigous var
// EVAL A::ac : disambig with scope op
// EVAL B::ac : Scope op
// EVAL E<int>::ac : Scope op
// EVAL this : notice pointer values
// EVAL (E<int>*)this : change
// EVAL (class D *)this : class is optional
// EVAL *(E<int> *)this : show fields
// EVAL *(D *) this : show fields
void main()
{
outter obj;
int outter::F::*mptr = &outter::F::b;
int i;
int& r = i;
obj.inobj.funct();
i = 777;
obj.static_i = 2;
// Stop here
// EVAL obj.inobj.*mptr : member ptr
// EVAL obj.inobj.b
// EVAL i
// EVAL r
// EVAL r=1
// EVAL i
// EVAL (A &) (obj.inobj) : reference cast
// EVAL
}
/* Same */
typedef struct c { /* Structure typedef */
unsigned a;
char *b;
} c;
c d; /* Structure using typedef */
/** UNIONS **/
union u{ /* Union */
int x;
unsigned y;
};
union u u; /* Variable using union */
union u *pU; /* Same */
/** ENUMERATIONS **/
enum number {one, two, three};
enum color {red,yellow,blue};
enum number number = one;
enum color color = blue;
Figure 45. Sample ILE Source Debugger and ILE C Application (Part 1 of 2)
Figure 45. Sample ILE Source Debugger and ILE C Application (Part 2 of 2)
Evaluate Expression
Previous debug expressions
>eval pc1
pc1 = SPP:C0260900107C0000 Displaying pointers
>eval pc2=pc1
pc2=pc1 = SPP:C0260900107C0000 Assigning pointers
>eval *pc1
*pc1 = ’C’ Dereferencing pointers
>eval &pc1
&pc1 = SPP:C026090010400000 Taking an address
>eval *&pc1
*&pc1 = SPP:C0260900107C0000 Can build expressions with
normal C precedence
>eval *(short *)pc1
*(short *)pc1 = -15616 Casting
Bottom
Debug . . . _________________________________________________________________
_______________________________________________________________________________
F3=Exit F9=Retrieve F12=Cancel F18=Command entry F19=Left F20=Right
Evaluate Expression
Previous debug expressions
>eval i1==u1 || i1<u1
i1==u1 || i1<u1 = 0 Logical operations
>eval i1++
i1++ = 100 Unary operators occur in proper order
>eval i1
i1 = 101 Increment has happened after i1 was used
>eval ++i1
++i1 = 102 Increment has happened before i1 was used
>eval u1 = -10
u1 = -10 = 4294967286 Implicit conversions happen
>eval (int)u1
(int)u1 = -10
>eval dec1 Decimal types are displayed but cannot
dec1 = 12.3 be used in expressions
Bottom
Debug . . . _________________________________________________________________
_______________________________________________________________________________
F3=Exit F9=Retrieve F12=Cancel F18=Command entry F19=Left F20=Right
Bottom
Debug . . . _________________________________________________________________
_______________________________________________________________________________
F3=Exit F9=Retrieve F12=Cancel F18=Command entry F19=Left F20=Right
Evaluate Expression
Bottom
Debug . . . _________________________________________________________________
_______________________________________________________________________________
F3=Exit F9=Retrieve F12=Cancel F18=Command entry F19=Left F20=Right
>eval color
color = blue (2) Both the enumeration and its value are
>eval number displayed
number = three (2)
>eval (enum color)number Casting to a different enumeration
(enum color)number = blue (2)
>eval number = 1 Assigning by number
number = 1 = two (1)
>eval number = three Assigning by enumeration
number = three = three (2)
>eval arr1[one] Using in an expression
arr1[one] = ’A’
Bottom
Debug . . . _________________________________________________________________
_______________________________________________________________________________
F3=Exit F9=Retrieve F12=Cancel F18=Command entry F19=Left F20=Right
This sample program sets up system and space pointers for an example of how
they can be displayed with the debugger.
#include <stdio.h>
#include <mispace.h>
#include <pointer.h>
#include <mispcobj.h>
#include <except.h>
#include <lecond.h>
#include <leenv.h>
#include <qtedbgs.h> /* From qsysinc */
/* Link up the Create User Space API */
#pragma linkage(CreateUserSpace,OS)
#pragma map(CreateUserSpace,"QUSCRTUS")
void CreateUserSpace(char[20],
char[10],
long int,
char,
char[10],
char[50],
char[10],
_TE_ERROR_CODE_T *
);
/* Link up the Delete User Space API */
#pragma linkage(DeleteUserSpace,OS)
#pragma map(DeleteUserSpace,"QUSDLTUS")
void DeleteUserSpace(char[20],
_TE_ERROR_CODE_T *
);
return 0;
}
This screen illustrates displaying system and space pointers with the debugger.
Evaluate Expression
Previous debug expressions
>eval pSYSptr System pointers are formatted
pSYSptr =
SYP:QTEUSERSPC :1934:QTEMP :111111110
0011100
>eval pBuffer
pBuffer = SPP:071ECD0002000000 Space pointers return 6 bytes that can
be used in System Service Tools
Bottom
Debug . . . _________________________________________________________________
_______________________________________________________________________________
F3=Exit F9=Retrieve F12=Cancel F18=Command entry F19=Left F20=Right
Figure 47. Debug Language Syntax (Program Flow and Program Data)
On the iSeries Data Management system, all files are made up of records. All I/O
operations at the operating system level are carried out a record at a time, using
data management operations.
The ILE C/C++ run-time library allows your program to process stream files as
text stream files or as binary stream files. Text stream files process one character at
a time. Binary stream files process one character at a time or one record at a time.
Since the iSeries Data Management system carries out I/O operations one record at
a time, the ILE C/C++ library simulates stream file processing with OS/400
records. Although the ILE C/C++ library logically handles I/O one character at a
time, the actual I/O that is performed by the operating system is done one record
at a time.
Note: Since the iSeries Data Management system carries out I/O operations one
record at a time, using system commands such as OPNQRYF together with
stream I/O operations on the same file may cause positioning problems in
the file your program is processing. Do not mix the use of ILE C/C++
extensions for record I/O and stream file functions on the same file as
unpredictable results can occur. Avoid using system commands that logically
work with records instead of characters in programs that contain stream I/O
operations.
Record Files
The ILE C library provides a set of extensions to the ANSI C definition for I/O.
This set of extensions, referred to as record I/O, allows your program to perform
I/O operations one record at a time.
The ILE C record I/O functions work with all the file types that are supported on
the iSeries system.
Each file that is opened with _Ropen() has an associated structure of type _RFILE.
The <recio.h> header file defines this structure. Unpredictable results may occur if
you attempt to change this structure.
Different open modes and keyword parameters apply to the different iSeries Data
Management system file types. Chapter 10, “Using Externally Described Files in
Your Programs” on page 185, Chapter 11, “Using Database Files and Distributed
Data Management Files In Your Programs” on page 201, and Chapter 12, “Using
Device Files in Your Programs” on page 219 provide information on each file type
and how to open a record file using _Ropen().
Stream Files
On the iSeries Data Management system, a stream is a continuous string of
characters.
Figure 48. iSeries Data Management Records Mapping to an ILE C Stream File
The ILE C compiler allows your program to process stream files as text stream files
or as binary stream files. See “Text Streams” on page 143 and “Binary Streams” on
page 143.
Stream Buffering
Three buffering schemes are defined for ANSI standard C streams. They are:
v Unbuffered - characters are intended to appear from the source or at the
destination, as soon as possible. The ILE C compiler does not support
unbuffered streams.
v Fully buffered - characters are transmitted to and from a file one block at time,
after the buffer is full. The ILE C compiler treats a block as the size of the
system file’s record.
v Line buffered - characters are transmitted to and from a file, as a block, when a
new-line control character (\n) is encountered.
The ILE C compiler supports fully-buffered and line-buffered streams in the same
manner, because a block and a line are equal to the record length of the opened
file.
Note: The setbuf() and setvbuf() functions do not allow you to control buffering
and buffer size when using the data management system.
The mode variable is a character string that consists of an open mode which may be
followed by keyword parameters. The open mode and keyword parameters must
be separated by a comma or one or more blank characters.
Text Streams
A text stream is an ordered sequence of characters that are composed of lines. Each
line consists of zero or more characters and ends with a new-line character. The
iSeries Data Management system may add, alter, or delete some special characters
during input or output. Therefore, there may not be a one-to-one correspondence
between the characters written to a text stream and characters read from the same
text stream. When a file is closed, an implicit new-line character is appended to the
end of the file unless a new-line character is already specified.
Data read from a text stream compares as equal to data written to the text stream
if:
v The data consists of printable characters, horizontal tab, vertical tab, new-line
character, or form-feed control characters.
v No new-line character is immediately preceded by a space (blank) character.
v The last character in a stream is a new-line character.
v The lines that are written to a file do not exceed the record length of the file.
Binary Streams
A binary stream is a sequence of characters that has a one-to-one correspondence
with the characters stored in the associated iSeries Data Management system file.
Character translation is not performed on binary streams. When data is written to
a binary stream, it is the same when it is read back later. New-line characters have
no special significance in a binary stream. On the iSeries system, the length of a
binary stream file is a multiple of the record length. When a file is closed, the last
record in the file is padded with nulls (hexadecimal value 0x00) to the end of the
record.
Similar to text streams, binary streams map to records in iSeries Data Management
system files. They can be processed one character at a time or one record at a time.
If you do not specify a library name when you open the file, the database file is
dynamically created in library QTEMP. If you do not specify a member name, a
member is created with the same name as the file.
The length that is specified on the lrecl parameter of fopen() is used for the record
length of the file that is created, with the following exceptions:
Chapter 8. Using Stream and Record I/O Functions with iSeries Data Management Files 143
v If you do not specify a record length when you open a text file, then a source
physical file with a record length of 266 is created.
v If you do not specify a record length when you open a binary or record file,
then a physical file with a record length of 80 is created.
v If you specify a record length of zero (lrecl=0) when you open a text file, then a
source physical file with a record length of 266 is created.
v If you specify a record length of zero (lrecl=0) when you open a binary file, then
a physical file with a record length of 80 is created.
v If the lrecl parameter is not specified for program-described files, then the record
length that is specified on the CRTPRTG, or CRTPRTF is used. This length has a
default value of 132, and if specified must be a minimum of 1.
Note: To use the source entry utility (SEU) to edit source files, specify an lrecl
value of 240 characters or less on fopen().
Dynamic file creation for text stream files is the same as specifying:
CRTSRCPF FILE(filename) RCDLEN(recln)
Dynamic file creation for binary stream files is the same as specifying:
CRTPF FILE(filename) RCDLEN(recln)
Streams stdin, stdout, and stderr are implicitly opened the first time they are used.
v Stream stdin is opened with fopen(″stdin″, ″r″).
v Stream stdout is opened with fopen(″stdout″, ″w″).
v Stream stderr is opened with fopen(″stderr″, ″w″).
These streams are not real iSeries Data Management system files, but are simulated
as files by the ILE C library routines. By default, they are directed to the terminal
session.
The stdin, stdout, and stderr streams can be associated with other devices using
the OS/400 override commands on the files STDIN, STDOUT, and STDERR
respectively. If stdin, stdout, and stderr are used, and a file override is present on
any of these streams prior to opening the stream, then the override takes effect,
and the I/O operation may not go to the terminal.
If stdout or stderr are used in a non-interactive job, and if there are no file
overrides for the stream, then the ILE C compiler overrides the stream to the
printer file QPRINT. Output prints or spools for printing instead of displaying at
your workstation.
If stdin is specified (or the default accepted) for an input file that is not part of an
interactive job, then the QINLINE file is used. You cannot re-read a file with
QINLINE specified, because the database reader will treat it as an unnamed file,
and therefore it cannot be read twice. You can avoid this by issuing an override. If
If stdin is specified in batch and has no overrides associated with it, then QINLINE
will be used. If stdin has overrides associated with it, then the override is used
instead of QINLINE.
Note: You can use freopen() to reopen text streams. The stdout and stderr streams
can be reopened for printer and database files. The stdin stream can be
overridden only with database files.
Session Manager
ILE C stream I/O functions that output information to the display are defined
through the Dynamic Screen Manager (DSM) session manager APIs. You can
obtain the session handle for the C session and then use the DSM APIs to
manipulate that session. The session handle is supplied through the
_C_Get_Ssn_Handle() in <stdio.h>. For example, you can write a simple C program
to clear the C session using the DSM QsnClrScl API, as shown in the following
example:
#include <stdio.h>
#include "qsnapi.h"
void main (void)
{
QsnClrScl(_C_Get_Ssn_Handle(), ’0’, NULL);
}
You can use the DSM APIs to perform any operation that is valid with a session
handle, which includes the window services APIs and many of the low-level
services also. You can display the session using a combination of the QsnStrWin,
QsnDspSsnBot, and QsnReadSsnDta APIs, but it is simpler in this case to simply
write a program that contains a getc(). As another example, you can use the
QsnRtvWinD and QsnChgWin APIs to change the C session from the default
full-screen window to a smaller window.
The file description also contains the file’s characteristics, details on how the data
associated with the file is organized into records, and how the fields are organized
within these records. Whenever a file is processed, the operating system uses the
file description. Data is created and accessed on the system through file objects.
Chapter 8. Using Stream and Record I/O Functions with iSeries Data Management Files 145
v Intersystem communications function (ICF) files define the layout of the data
sent and received between two application programs on different systems. This
file links the configuration objects that are used to communicate with the remote
system
v Save files save data in a format that is used for backup and recovery purposes.
v Distributed Data Management (DDM) files access data on remote systems.
\\ file-name \]
library-name/ file-name (member-name)
library-name
Enter the name of the library that contains the file. If you do not specify a
library, the system searches the job’s library list for the file.
file-name
Enter the name of the file. This is a required parameter.
member-name
Enter the name of the file member. If you do not specify a member name,
the first member (*FIRST) is used.
Note: If you specify *ALL for member-name when using fopen() and
_Ropen(), multi-member processing occurs.
If you surround the file name, library name, or member name in double quotation
marks and the name is a normal name, the double quotation marks are discarded
by the ILE C\C++ compiler. A normal name is any file, library, or member name
with the following characters:
v Uppercase characters
v Numeric values
v $ (hexadecimal value 0x5B)
v @ (hexadecimal value 0x7C)
v # (hexadecimal value 0x7B)
v _ (hexadecimal value 0x6D)
v . (hexadecimal value 0x4B)
The following characters cannot appear anywhere in your file names, library
names, or member names:
v r v w v a
v r+ v w+ v a+
Notes:
1. The number of files that can be simultaneously opened by fopen() depends on
the amount of the system storage available.
2. The fopen() function open modes also apply to the freopen() function.
3. If the text stream file contains deleted records, the deleted records are skipped
by the text stream I/O functions.
If you specify a mode or keyword parameter that is not valid on fopen(), errno is
set to EBADMODE, and NULL is returned.
To open an iSeries system file as a text stream file, use the open() member function
with the following modes:
v ios::app
v ios::ate
v ios::in
v ios::out
v ios::trunc
Example
Chapter 8. Using Stream and Record I/O Functions with iSeries Data Management Files 147
The following example illustrates how to open a text stream file. Library MYLIB
must exist. The file TEST is created for you if it does not exist. The mode ″w+″
indicates that if MBR does not exist, it is created for update. If it does exist, it is
cleared.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
/* Open a text stream file. */
/* Check to see if it opened successfully */
if (( fp = fopen ( "MYLIB/TEST(MBR)", "w+") ) == NULL )
{
printf ( "Cannot open MYLIB/TEST(MBR)\n" );
exit ( 1 );
}
fclose ( fp );
}
Writing, reading, and updating can be performed on text stream files that are
opened for processing.
If the number of characters being written in the buffer exceeds the record length of
the file, the data written to the file is truncated, and errno is set to ETRUNC.
Example
fputs ( buf, fp );
fclose ( fp );
}
The following example illustrates how to read from a text stream file.
Chapter 8. Using Stream and Record I/O Functions with iSeries Data Management Files 149
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
char buf[12];
char *result;
FILE *fp;
/* Open an existing text file for reading. */
if (( fp = fopen ( "MYLIB/TEST(MBR)", "r") ) == NULL )
{
printf ( "Cannot open file\n" );
exit ( 1 );
}
/* Read characters into the buffer. */
fclose ( fp );
}
Figure 53. ILE C Source to Read Characters from a Text Stream File
If the data being written to the text stream file is shorter than the record length
being updated, and the last character of the data being written is a new-line
character, then the record is updated and the remainder of the record is filled with
blank characters. If the last character of the data being written is not a new-line
character, the record is updated and the remainder of the record remains
unchanged.
v rb v wb v ab
v r+b or rb+ v w+b or wb+ v a+b or ab+
Notes:
1. The number of files that can be simultaneously opened by fopen() depends on
the size of the system storage available.
2. The fopen() function open modes also apply to the freopen() function.
3. If the binary stream file contains deleted records, the deleted records are
skipped by the binary stream I/O functions.
If you specify the type parameter the value must be memory for binary stream
character-at-a-time processing.
Note: The memory parameter identifies this file as a memory file that is accessible
only from C programs. This parameter is the default and is ignored.
If you specify a mode or keyword parameter that is not valid on fopen(), errno is
set to EBADMODE.
Example
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
/* Open an existing binary file. */
if (( fp = fopen ( "MYLIB/TEST(MBR)", "wb+" ) ) == NULL )
{
printf ( "Cannot open file\n" );
exit ( 1 );
}
printf ("Opened the file successfully\n");
fclose ( fp );
}
Writing, reading, and updating can be performed on binary stream files opened for
character-at-a-time processing.
To open an iSeries Data Management system file as a binary stream file for
character-at-a-time processing, use theOPEN() member function with ios::binary as
well as any of the following modes:
v ios::app
v ios::ate
v ios::in
v ios::out
v ios::trunc
Chapter 8. Using Stream and Record I/O Functions with iSeries Data Management Files 151
Writing Binary Stream Files (one character at a time)
If you write data to a binary stream processed one character at a time, and the size
of the data is greater than the current record length, then the excess data is written
to the current record up to its record size and the remaining data is written to the
next record in the file.
Example
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
char buf[5] = {’a’, ’b’, ’c’, ’d’, ’e’};
/* Open an existing binary file for writing. */
if (( fp = fopen ( "MYLIB/TEST(MBR)", "wb" ) ) == NULL )
{
printf ( "Cannot open file\n" );
exit ( 1 );
}
/* Write 5 characters from the buffer to the file. */
fclose ( fp );
}
Example
The following illustrates how to read from a binary stream file by character.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
char buf[6];
/* Open an existing binary file for reading. */
if (( fp = fopen ( "MYLIB/TEST(MBR)", "rb" ) ) == NULL )
{
printf ( "Cannot open file\n" );
exit ( 1 );
}
/* Read characters from the file to the buffer. */
fclose ( fp );
}
Figure 58. ILE C Source to Read Characters from a Binary Stream File
Chapter 8. Using Stream and Record I/O Functions with iSeries Data Management Files 153
Figure 59. Updating a Binary Stream File with Data Longer than Record Length
Example
The following example illustrates updating a binary stream file with data that is
longer than the record length.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
char buf[5] = "12345";
/* Open an existing binary file for updating. */
if (( fp = fopen ( "QTEMP/TEST(MBR)", "rb+" ) ) == NULL )
{
printf ( "Cannot open file\n" );
exit ( 1 );
}
/* Write 5 characters from the buffer to the file. */
fclose ( fp );
}
Figure 60. ILE C Source to Update a Binary Stream File with Data Longer than the Record
Length
If the amount of data being updated is shorter than the current record length, then
the record is partially updated and the remainder is unchanged.
Example
The following example illustrates updating a binary stream file with data that is
shorter than the record length.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
char buf[2] = "12";
/* Open an existing binary file for updating. */
if (( fp = fopen ( "QTEMP/TEST(MBR)", "rb+" ) ) == NULL )
{
printf ( "Cannot open file\n" );
exit ( 1 );
}
/* Write 2 characters from the buffer to the file. */
fclose ( fp );
}
Figure 62. ILE C Source to Update a Binary Stream File with Data Shorter than the Record
Length
Chapter 8. Using Stream and Record I/O Functions with iSeries Data Management Files 155
Opening Binary Stream Files (one record at a time)
To open an iSeries Data Management system file as a binary stream file for
record-at-a-time processing, use fopen() with any of the following modes:
v rb v wb v ab
v r+b or rb+ v w+b or wb+ v a+b or ab+
Notes:
1. The number of files that can be simultaneously opened by fopen() depends on
the size of the system storage available.
2. The fopen() open modes also apply to freopen().
3. If the binary stream file contains deleted records, the deleted records are
skipped by the binary stream I/O functions.
4. The file must be opened with the type set to record.
If you specify a mode or keyword parameter that is not valid on fopen() function,
errno is set to EBADMODE.
Only fread() and fwrite() can be used for binary stream files opened for
record-at-a-time processing.
To open an iSeries Data Management system file as a binary stream file for
record-at-a-time processing, use theOPEN() member function with ios::binaryas
well as any of the following modes:
v ios::app
v ios::ate
v ios::in
v ios::out
v ios::trunc
If the product of size and count is less than the actual record length, the current
record is padded with blank characters and errno is set to EPAD.
Only fwrite() is valid for writing to binary stream files opened for
record-at-a-time processing. All other output and positioning functions fail, and
errno is set to ERECIO.
Example
The following example illustrates how to write to a binary stream file by record.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
char buf[5] = {’a’, ’b’, ’c’, ’d’, ’e’};
/* Open an existing binary file for writing. */
if ((fp = fopen ( "MYLIB/TEST(MBR)", "wb,type=record,lrecl=3" ))==NULL)
{
printf ( "Cannot open file\n" );
exit ( 1 );
}
/* Write 3 characters from the buffer to the file. */
fclose ( fp );
}
If the product of size and count is less than the actual record length, errno is set to
ETRUNC to indicate that there is data in the record that was not copied into the
buffer.
This figure illustrates how only the current record is read into the buffer, when the
product of size and count is greater than the record length.
Chapter 8. Using Stream and Record I/O Functions with iSeries Data Management Files 157
Figure 65. Reading From a Binary Stream File a Record at a Time
Only fread() function is valid for reading binary stream files opened for
record-at-a-time processing. All other input and positioning functions fail, and
errno is set to ERECIO.
Example
The following example illustrates how to read a binary stream a record at a time.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
char buf[6];
/* Open an existing binary file for reading a record at a time. */
if (( fp = fopen ( "MYLIB/TEST(MBR)", "rb, type=record" ) ) == NULL )
{
printf ( "Cannot open file\n" );
exit ( 1 );
}
/* Read characters from the file to the buffer. */
fclose ( fp );
}
Figure 66. ILE C Source to Read from a Binary Stream File by Record
To assign a pointer to the common part of the I/O feedback area, use the
_Riofbk() function. To assign a pointer to the part of the I/O feedback area that is
specific to the type of file, add the offset contained in the file_dep_fb_offset field
of the common part to a pointer to the common part.
Note: The offset is in bytes, so you need to cast the pointer (char *) to the common
part to a pointer to character when performing the pointer arithmetic. The
structures that map to the I/O feedback areas are that are contained in the
xxfdbk.h header file.
Chapter 8. Using Stream and Record I/O Functions with iSeries Data Management Files 159
160 ILE C/C++ Programmer’s Guide
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries
Integrated File System
This chapter describes how to open, write, read, and update text and binary stream
files through the iSeries Integrated File System.
The integrated file system provides a common interface to store and operate on
information in stream files. Examples of stream files are PC files, files in UNIX®
systems, LAN server files, iSeries files, and folders.
Note: The ILE C/C++ integrated file system enabled stream I/O functions are
defined through the integrated file system. You need to be familiar with the
integrated file system to use the ILE C/C++ stream I/O function. There are
7 file systems that make up the integrated file system. Depending on your
application and environment, you may use several of the file systems. If you
have existing applications that use iSeries system files, you need to
understand the limitations of the new QSYS.LIB file system. If you have
new applications, you can use the other file systems which do not have the
QSYS.LIB file handling restrictions. See “The Integrated File System” section
for information on each file system.
Users and application programs can interact with any of the file systems through a
common Integrated File System interface. This interface is optimized for
input/output of stream data, in contrast to the record input/output that is
provided through the data management interfaces. The common integrated file
system interface includes a set of user interfaces (commands, menus, and displays)
and application program interfaces (APIs).
Applications Integrated
File System
Application Program Interfaces Menus/Commands
OS/400 LAN
File Server Server/400
File
Server
"Root" QOpenSys QSYS.LIB QDLS QLANSrv
IOP
File File File File File
System System System System System
QOPT QFileSvr.400
File File
System System
RV3N501-0
This file system is designed to take full advantage of the stream file support and
hierarchical directory structure of the integrated file system. It has the
characteristics of the DOS and OS/2 file systems.
Path Names
This file system preserves the same uppercase and lowercase form in which object
names are entered, but no distinction is made between uppercase and lowercase
when the system searches for names.
v Path names have the following form:
Directory/Directory/ . . . /Object
v Each component of the path name can be up to 255 characters long. The path
can be up to 16 megabytes.
v There is no limit on the depth of the directory hierarchy other than program and
space limits
v The characters in names are converted to Universal Character Set 2 (UCS2) Level
1 form when the names are stored.
QOpenSys can be accessed only through the integrated file system interface. You
work with QOpenSys using integrated file system commands, user displays, or
ANSI stream I/O functions and system APIs.
Path Names
Unlike the QSYS.LIB, QDLS, QLANSrv, and ″root″ (/) file systems, the QOpenSys
file system distinguishes between uppercase or lowercase characters when
searching object names.
The path names, link support, commands, displays and ANSI stream I/O functions
and system APIs are the same as defined under the ″root″ (/) file system.
The QSYS.LIB file system maps to the iSeries file system. For example, the path
/qsys.lib/qsysinc.lib/h.file/stdio.mbr refers to the data management file STDIO, in
the file H, in library QSYSINC, within the root library QSYS.
Path Names
In general, the QSYS.LIB file system does not distinguish between uppercase and
lowercase names of objects. A search for object names achieves the same result
regardless of whether characters in the names are uppercase or lowercase.
However, if the name is enclosed in quotation marks, the case of each character in
the name is preserved. The search is sensitive to the case of characters in quoted
names.
Each component of the path name must contain the object name followed by the
object type. For example:
/QSYS.LIB/QGPL.LIB/PRT1.OUTQ
/QSYS.LIB/PAYROLL.LIB/PAY.FILE/TAX.MBR
The object name and object type are separated by a period (.). Objects in a library
can have the same name if they are different object types, so the object type must
be specified to uniquely identify the object.
The object name in each component can be up to 10 characters long, and the object
type can be up to 6 characters long.
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 163
The directory hierarchy within QSYS.LIB can be either two or three levels deep
(two or three components in the path name), depending on the type of the object
being accessed. If the object is a database file, the hierarchy can contain three levels
(library, file, member); otherwise, there can be only two levels (library, object). The
combination of the length of each component name and the number of directory
levels determines the maximum length of the path name.
If ″root″ (/) and QSYS.LIB are included as the first two levels, the directory
hierarchy for QSYS.LIB can be four or five levels deep.
The characters in names are converted to code page 37 when the names are stored.
Quoted names are stored using the code page of the job.
To work with the QDLS file system through the integrated file system interface,
use the integrated file system commands, user displays, or ANSI stream I/O
functions and system APIs.
All users working with objects in QDLS must be enrolled in the system
distribution directory.
Path Names
QDLS does not distinguish between uppercase and lowercase in the names
containing only the alphabetic characters a to z. A search for object names achieves
the same result regardless of whether characters in the names are uppercase or
lowercase.
Each component of the path name can consist of just a name, such as:
/QDLS/FLR1/DOC1
The name in each component can be up to 8 characters long, and the extension can
be up to 3 characters long. The maximum length of the path name is 82 characters.
The characters in names are converted to code page 500 when the names are
stored. A name may be rejected if it cannot be converted to code page 500.
To work with the QLANSrv file system through the integrated file system
interface, use the integrated file system commands, user displays, or ANSI stream
I/O functions and system APIs.
Path Names
The file system preserves the same uppercase and lowercase form in which object
names are entered. No distinction is made between uppercase and lowercase when
the system searches for names. A search for object names achieves the same result
regardless of whether characters in the names are uppercase or lowercase.
v Path names have the following form:
Directory/Directory/ . . . /Object
v Each component of the path name can be up to 255 characters long.
v The directory hierarchy within QLANSrv can be 127 levels deep. If all
components of a path are included as hierarchy levels, the directory hierarchy
can be 132 levels deep.
v Names are stored in the code page that is defined for the File Server.
Path Names
QOPT converts the lowercase English alphabetic characters a to z to uppercase
when used in object names. Therefore, a search for object names that uses only
those characters is not case-sensitive.
v The path name must begin with a slash (/) and contain no more than 294
characters. The path is made up of the file system name, the volume name, the
directory and subdirectory names, and the file name. For example:
/QOPT/VOLUMENAME/DIRECTORYNAME/SUBDIRECTORYNAME/FILENAME
v The file system name, QOPT, is required.
v The volume name is required and can be up to 32 characters long.
v One or more directories or subdirectories can be included in the path name, but
none are required. The total number of characters in all directory and
subdirectory names, including the leading slash, cannot exceed 63 characters.
Directory and file names allow any character except 0x00 through 0x3F, 0xFF,
0x80, lowercase-alphabetic characters, and the following characters:
– Asterisk (*)
– Hyphen (-)
– Question mark (?)
– Quotation mark (")
– Greater than (>)
– Less than (<)
v The file name is the last element in the path name. The file name length is
limited by the directory name length in the path. The directory name and file
name that are combined cannot exceed 256 characters, including the leading
slash.
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 165
v The characters in names are converted to code page 500 within the QOPT file
system. A name may be rejected if it cannot be converted to code page 500.
Names are written to the optical media in the code page that is specified when
the volume was initialized.
Note: The characteristics of the QFileSvr.400 file system are determined by the
characteristics of the file system that are being accessed on the target system.
Path Names
For a first-level directory, which actually represents the ″root″ (/) directory of the
target system, the QFileSvr.400 file system preserves the same uppercase and
lowercase form in which object names are entered. However, no distinction is
made between uppercase and lowercase when QFileSvr.400 searches for names.
For all other directories, case-sensitivity is dependent on the specific file system
being accessed. QFileSvr.400 preserves the same uppercase and lowercase form in
which object names are entered when file requests are sent to the OS/400 file
server.
v Path names have the following form:
/QFileSvr.400/RemoteLocationName/Directory/Directory . . . /Object
Note: First-level directories are not persistent across IPLs. That is, the first-level
directories must be created again after each IPL.
v Each component of the path name can be up to 255 characters long. The
absolute path name can be up to 16 megabytes long.
Note: The file system in which the object resides may restrict the component
length and path name length to less than the maximum allowed by
QFileSvr.400.
v There is no limit to the depth of the directory hierarchy, other than program and
system limits, and any limits that are imposed by the file system being accessed.
v The characters in names are converted to UCS2 Level 1 form when the names
are stored.
Notes on Usage
v The __IFS64_IO__, _LARGE_FILES, and _LARGE_FILE_API macros are not
mutually exclusive. For example, you might specify SYSIFCOPT(*IFS64IO) on
the command line, and define either or both of the _LARGE_FILES and
_LARGE_FILE_API macros in your program source.
Stream Files
The ILE C/C++ compiler allows your program to process stream files as true text
or binary stream files (using the integrated file system enabled stream I/O) or as
simulated text and binary stream files (using the default data management stream
I/O).
When writing an application that uses stream files, for better performance, it is
recommended that the integrated file system be used instead of the default C
stream I/O which is mapped on top of the data management record I/O.
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 167
On the integrated file system, a stream is simply a continuous string of characters.
A database file is record arranged; It has predefined subdivisions consisting of one
or more fields that have specific characteristics, such as length and data type.
Stream File
.........................................
.........................................
.........................................
Record-oriented Database File
Default C/C++ stream I/O on the iSeries systems is simulated on top of an iSeries
database file. Figure 69 illustrates how an iSeries record is mapped to a C/C++
stream. This is simulated stream file processing with iSeries records.
The differences in structure of stream files and record-oriented files affects how an
application is written to interact with them and which type of file is best suited to
an application. A record-arranged file for example, is well suited for storing
customer information, such as name, address, and account balance. These fields
can be individually accessed and manipulated using the extensive programming
functions of the iSeries system. However, a stream file is better suited for storing
information such as a customer’s picture, which is composed of a continuous string
of bits representing variations in color. Stream files are particularly well suited for
storing strings of data such as the text of a document, images, audio, and video.
Text Streams
Text streams contain printable characters and control characters that are organized
into lines. Each line consists of zero or more characters and ends with a new-line
character (\n). A new-line character is not automatically appended to the end of
file.
Data read from an integrated file system text stream is equal to the data which was
written if the data consists only of printable characters and the horizontal tab,
new-line, vertical tab, and form-feed control characters.
For most integrated file system stream files, a line consists of zero or more
characters, and ends with a carriage-return new-line character combination.
However, the integrated file system can have logical links to files on different
systems that may use a single line-feed as a line terminator. A good example of
this are the files on most UNIX systems.
On input, the default in text mode is to strip all carriage-returns from new-line
carriage-return character combination line delimiters. On output, each line-feed
character is translated to a carriage-return character that is followed by a line-feed
character. The line terminator character sequence can be changed with the CRLN
option on fopen().
Note: The *IFSIO option also changes the value for the ’\n’ escape character value
to the 0x25 line feed character. If *NOIFSIO is specified, the ’\n’ escape
character has a value of 0x15.
When a file is opened in text mode, there may be codepage conversions on data
that is processed to and from that file. When the data is read from the file, it is
converted from the code page of the file to the code page of the application, job, or
system receiving the data.
When data is written to an iSeries file, it is converted from the code page of the
application, job, or system to the code page of the file. For true stream files, any
line-formatting characters (such as carriage return, tab, and end-of-file) are
converted from one code page to another.
When reading from QSYS.LIB files end-of-line characters (carriage return and line
feed) are appended to the end of the data that is returned in the buffer.
The code page conversion that is done when a text file is processed can be
changed by specifying the codepage or CCSID option on fopen(). The default is to
convert all data read from a file to job’s CCSID/code page.
Binary Streams
A binary stream is a sequence of characters that has a one-to-one correspondence
with the characters stored in the associated iSeries system file. The data is not
altered on input or output, so the data that is read from a binary stream is equal to
the data that was written. New-line characters have no special significance in a
binary stream. The application is responsible for knowing how to handle the data.
The fgets() function handles new-line characters. Binary files are always created
with the CCSID of the job.
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 169
Opening Text Stream and Binary Stream Files
Each text stream file and each binary stream file is represented by a file control
structure of type FILE. This structure is initialized depending on the mode in
which the file was opened. Unpredictable results may occur if you attempt to
change the file control structure.
The mode variable is a character string that consists of an open mode which may be
followed by keyword parameters. The open mode and keyword parameters must
be separated by a comma or one or more blank characters.
To open a text stream file, use fopen() with one of the following modes:
v r v w v a
v r+ v w+ v a+
To open a binary stream file, use fopen() with one of the following modes:
v rb v wb v ab
v rb+ or r+b v wb+ or w+b v ab+ or a+b
To open a binary stream file, use theopen() member function with ios::binary, or
any of the following modes:
v ios::app
v ios::ate
v ios::in
v ios::out
v ios::trunc
As the hexadecimal values of the file contents shows in the binary stream
(script.bin), the new-line character is converted to a line-feed hexadecimal value
(0x25). While in the text stream (script.txt), the new-line is converted to a
carriage-return line-feed hexadecimal value (0x0d25).
Care must be taken when transferring files to and from various platforms. Use of a
download and upload utility like FTP allows you to specify the correct mapping of
characters so your streamed source remains valid on the iSeries platform, even if it
has been stored temporarily on other platforms. See “Pitfalls to Avoid” on page 180
for more tips.
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 171
Copying Source Files into the Integrated File System
You can copy your main source physical file to an Integrated File System file.
Assuming that you used a standard name for your source physical file, use the
following command:
CPYTOSTMF FROMMBR(’/QSYS.LIB/MYLIB.LIB/QCSRC.FILE/QCSRC.MBR’) TOSTMF(’/home/qcsrc.c’)
For file systems that are case sensitive, the path name may be case sensitive.
If the path name does not begin with a / or \ character, the path is assumed to
begin at your current directory. For example:
MyDir/MyFile
is equivalent to
/CurrentDir/MyDir/MyFile
SRCSTMF is mutually exclusive with SRCMBR and SRCFILE. Also, if you specify
SRCSTMF, then the compiler ignores TEXT(*SRCMBRTXT). Other values for TEXT are
valid.
When an IFS file specification is used for the root source file *that is, when the
SRCSTRMF option is used), all #include directives within that compilation are
similarly resolved to the IFS file system. The syntactical variations are:
Table 6. Integrated File System Compiles
#include specification enclosed in < > enclosed in ″ ″
filename (e.g., <cstdio>) resolves to resolves to
[syssearchpath]/filename [usrsearchpath]/filename
dir/filename (e.g., resolves to resolves to
<sys/limits.h>) [syssearchpath]/dir/filename [usrsearchpath]/dir/filename
/dir/filename (e.g., resolves to /dir/filename resolves to /dir/filename
″home/header.h″)
When a DM file specification is used for the root source file (that is, when the
SRCFILE/SRCMBR options are used), all #include directives within that
compilation are similarly resolved to the DM filesystem. The syntactical variations
are:
Table 7. Data Management File System Compiles
#include library file member
specification
mbr default search default file mbr
mbr.file default search file mbr
file/mbr default search file mbr
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 173
Table 7. Data Management File System Compiles (continued)
#include library file member
specification
file(mbr) default search file mbr
file/mbr.ext default search file mbr or mbr.ext
lib/file/mbr lib file mbr
lib/file(mbr) lib file mbr
Note: When the *SYSINCPATH compile option is specified, then user includes (″ ″)
are treated the same as system includes (< >) when compiled.
v When library is not specified:
– include specification is enclosed in < >: Search the *LIBL.
– include specification is enclosed in ″ ″: Check the library containing the root
source member; if not found there, then search the *USRLIBL; if still not
found, search the *LIBL.
v When library is specified:
– include specification is enclosed in < >: Search the lib/file/mbr only.
– include specification is enclosed in ″ ″: Search the user portion of the library
list using the file and member names provided; if not found, search for the
member in the library/file specified.
Default file:
v Include specification enclosed in < >:
– C: default file is QCSRC.
– C++: default file is STD.
v Include specification enclosed in ″ ″:
– default is the root source file.
mbr.ext
v Include specification enclosed in < >:
– <file/mbr.h> format resolves to member mbr in file file. This rule is for Posix
support (for example, to be able to include specifications like <sys/limits.h>).
The only member names which activate Posix support are extensions of ″h″ or
″H″.
– Otherwise, <file/mbr.ext> resolves to file file, and member mbr.ext
v Include specification enclosed in ″ ″:
– ″file/mbr.ext″ resolves to file file, and member mbr.ext
The Include Directory parameter (INCDIR) works with the Create Module and
Create Bound Program compiler commands, allowing you to redefine the path
used to locate include header files (with the #include directive) when compiling a
source stream file only. The parameter is ignored if the source file’s location is not
defined as an IFS path via the Source Stream File (SRCSTMF) parameter, or if the
full (absolute) path name is specified on the #include directive.
The include files search path adheres to the following directory search order to
locate the file:
Table 8. INCDIR Command Parameter
#include type Directory Search Order
#include <file_name> 1. If you specify a directory in the INCDIR parameter,
the compiler searches for file_name in that directory
first.
2. If more than one directory is specified, the compiler
searches the directories in the order that they
appear on the command line.
3. Searches the directory /QIBM/include.
#include ″file_name″ 1. Searches the directory where your current source
file resides. The current source file is the file that
contains the #include ″file_name″ directive.
2. If you specify a directory in the INCDIR parameter,
the compiler searches for file_name in that
directory.
3. If more than one directory is specified, the compiler
searches the directories in the order that they
appear on the command line.
4. Searches the directory /QIBM/include.
For example, if you enter the following value for the INCDIR parameter:
Include directory . . . . . . . INCDIR ’/tmp/dir1’
+ for more values ’./dir2’
and with your source stream file you include the following header files:
#include "foo.h"
#include <stdio.h>
The compiler first searches for a file ″foo.h″ in the directory where the root source
file resides. If the file is found, it is included and the search ends. Otherwise, the
compiler searches the directories entered INCDIR, starting with ″/tmp/dir1″. If the
file is found, this file is included. If the directory does not exist, or if the file does
not exist within that directory, the compiler continues to search in the subdirectory
″dir2″ within the current working directory (symbolized by ″.″). Again, if the file is
found, this file is included, otherwise, since the directories in INCDIR path have
now been exhausted, the default user include path (/QIBM/include) is used to
find the header.
As for <stdio.h>, the same logic is followed in the same order, except the initial
search in the root source directory is bypassed.
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 175
Table 9. INCLUDE Environment Variable
Environment Release Compiler Behavior
Variable
INCLUDE V4R5 ILE C v The path overrides the include search
path order (i.e., only the INCLUDE path
is searched). If you want to search the
default include path, it must be explicitly
included in the INCLUDE path.
v If the environment variable is not
defined: in a system include search (<>),
only the default include path is searched;
in a user include search (″″), only the
current working directory is searched.
INCLUDEPATH V4R5 ILE C++ v The path does not override the search
order.
v The INCLUDEPATH directories are
searched prior to the default include
path. The default path is implicitly
included in the INCLUDEPATH path
unless the *NOSTDINC option is chosen.
v If the environment variable is not
defined: in a system include search (<>),
only the default include path is searched;
in a user include search (″″), the root
source directory and the default include
path is searched.
INCLUDE V5R1 ILE C/C++ v The environment variable does not
override the order.
v INCLUDE simply has higher priority in
the search order than the default include
path, but less than INCDIR and the root
source’s directory (if a user include
search).
Note: This feature is only available for source stream file compiles.
The *NOSTDINC option allows you to remove the default include path
(/QIBM/include for IFS source stream files; QSYSINC for data management source
file members) from the search order, while the *STDINC option retains the default
include path at the end of the order. *STDINC is the default.
This option works as did the former SYSINC parameter for data management
source file members. The options relate to the old parameter values as follows:
Table 11. Parameter Values
SYSINC values Equivalent New Command Option
*YES *STDINC
*NO *NOSTDINC
The *INCDIRFIRST option allows you to process the directories listed via the
INCDIR parameter first in the search order (that is, before the root source file
directory) in a user include search, while the *NOINCDIRFIRST option retains
INCDIR directories to their default position in the user include search order as
described above.
Note: This option is only valid for source stream file compiles.
For example, if *INCDIRFIRST is selected, the following changes occur to the user
include search order:
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 177
Table 12. INCDIRFIRST Command Options
#include type Directory Search Order
#include ″file_name″ 1. If you specify a directory in the INCDIR
parameter, the compiler searches for file_name
in that directory.
2. If more than one directory is specified, the
compiler searches the directories in the order
that they appear on the command line.
3. Searches the directory where your current root
source file resides.
4. If the INCLUDE environment variable is
defined, the compiler searches the directories in
the order they appear in the INCLUDE path.
5. If the *NOSTDINC compiler option is not
chosen, search the default include directory
/QIBM/include.
The *SYSINCPATH option changes the search path of user includes to the system
include search path. In affect it is equal to changing the double-qoutes in the user
#include directive (#include ″file_name″) to angle brackets (#include <file_name>).
*NOSYSINCPATH turns off this option and is the default value.
Restrictions
Source Stream Files Only:
If you specify SRCSTMF, the *MODULE object contains no source file attribute
information.
If the source file is not specified via the Source Stream File parameter, the job’s
library list (LIBL) is used to define the default include paths. Following the C++
design of INCDIR and the design of the INCLUDE environment variable, this
parameter is only used to add ″directories″ to the default user include path. Since
the library list has no concept of the directory file structure, this parameter would
be meaningless and is therefore ignored.
If you specify a full (absolute) path name on the #include directive, this option has
no effect.
Note:
If you have #include "myinc.h" and a C/C++ source file, you are compiling a
source member from the QSYS file system through the SRCSTMF parameter in the
compiler command like:
CRTCMOD MODULE(MYSOURCE) SRCSTMF(’/qsys.lib/goodness.lib/qcppsrc.file/mysource.mbr’)
or, if you have set the search path appropriately, as shown in “Examples of Using
Integrated File System Source” on page 180:
#include "myinc.mbr"
We recommend that you put source and header files in the Integrated File System
to avoid changing your current C++ source code. You may be porting from other
platforms which have hierarchical file systems (like the Microsoft® Windows®
operating system, Unix, or OS/2), and the Integrated File System is more like those
file systems.
Preprocessor Output
If you specify SRCSTMF with OPTION(*PPONLY), then the processor writes a stream
file to the current directory with the new extension .i. For example, if you specify
SRCSTMF(’/home/MOE/mainprogram.c’) with OPTION(*PPONLY), then the processor
writes output to the current directory as a stream file called mainprogram.i. For
this to happen, you need *RW authority to the current directory.
Listing Output
The compiler can send the listing output to a user-specified IFS file, as well as a
spool file. The prolog identifies the source file from a path name:
Module . . . . . . . . . . . . : TEST
Library . . . . . . . . . . . : MOE
Source stream file . . . . . . : /home/MOE/src/mainprogram.c
It also identifies the include files from their path names. Source stream file is not
included in the prolog when SRCFILE() and SRCSTMF() are specified.
**** FILE TABLE SECTION ****
0 = hello.cpp
1 = /QIBM/include/iostream.h
2 = /QIBM/include/string.h
The listing includes the files specified on any #include directive, and the file
specified or implied on the SRCSTMF() or SRCFILE()/SRCMBR() options. This
happens for database files and stream file source.
The format of the OUTPUT option is: OUTPUT(*NONE | *PRINT | filename), where
*PRINT causes the compiler to send it to a spool file, and filename is the
user-specified IFS file.xxxxx
The compiler converts source files, translating code pages to the root source.
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 179
The source stream file may have been entered through a mounted file on an ASCII
system. In such a case, the compiler translates from the ASCII codepage that is
associated with the stream file (for example, 437) to EBCDIC (for example, 37).
Support for Unicode wide-character literals can be enabled when building your
program by specifying LOCALETYPE(*LOCALEUCS2) on the compile command.
See Chapter 19, “International Locale Support” on page 411 for more information..
You can configure most file transfer utilities to automatically do the conversion to
enable ASCII-based file systems to work for producing stream file source.
Pitfalls to Avoid
Any source file created on the workstation with an ASCII editor that deposits an
EOF marker at the end of a text file will generate an invalid character warning
message when it is compiled with the ILE C/C++ compiler. This includes your
main source file. The problem arises when the source file is copied to, or saved in,
the ″Root″ file system on the iSeries . This is because of the translation between
ASCII and EBCDIC codepoints.
If you receive an invalid character message referring to the last character of a file,
it is likely that you have an EOF marker in the file. One way to avoid this problem
is to use an editor which does not add the EOF marker.
Alternatively you can use a File Transfer Protocol (FTP) utility. FTP will result in a
″Root″ file system file with either codepage 819 or 37. Any of these FTP commands
issued to the target iSeries system prior to the put command will result in a file of
codepage 819:
v ascii
v quote type a
If you issue the following command to the target iSeries system prior to the put
command, put results in a file with codepage 37 (EBCDIC): quote type e. When
the file is transferred using FTP to the ″Root″ file system, the file is created with
either codepage 819 or codepage 37 (depending on the previous commands as
outlined above) whether the file exists prior to the transfer or not.
Files transferred to an Integrated File System with codepage 37, fail to compile.
Bottom
F3=Exit F4=Prompt F5=Refresh F10=Additional parameters F12=Cancel
F13=How to use this display F24=More keys
Without a pathname, the system assumes that your source is located in the current
directory. The default current directory is the base ″/″ directory of the ″Root″ file
system, but your individual user profile may change this default to a different
directory. You can change the current directory with the CHGCURDIR command.
Note: The current directory and the current library are separate and distinct
entities. Although you can set the current library and the current directory
to be the same name, a change in one will not affect the other.
The header files specified in any #include statements in your source will be
searched for in the source directory first and then the specified INCDIR directory.
For example, if you compile the following source in file /goodness/mysource.cpp:
#include "special/mystuff.h"
with the INCDIR value set to /mydir, your included header file is first searched for
as /goodness/special/mystuff.h and then /mydir/special/mystuff.h.
Large Files
Within the C or C++ run-time, stream I/O for files up to two gigabytes in size is
enabled by specifying the *IFSIO option on the system interface keyword
(SYSIFCOPT) on the Create Module or Create Bound Program command prompt.
The format of the SYSIFCOPT command is:
CRTCPPMOD MODULE(QTEMP/IFSIO) SRCFILE(QCLE/QACSRC) SYSIFCOPT(*IFSIO)
CRTBNDCPP PGM(QTEMP/IFSIO) SRCFILE(QCLE/QACSRC) SYSIFCOPT(*IFSIO)
When this option is specified, the compiler defines the __IFS_IO__ macro. When
__IFS_IO__ is defined, prototypes associated with stream I/O in <stdio.h> are no
Chapter 9. Using ILE C/C++ Stream Functions with the iSeries Integrated File System 181
longer defined. The header file <ifs.h> is included by <stdio.h>, which declares all
structure and prototypes associated with the integrated file system enabled C
stream I/O.
The 64–bit version of the Integrated File System interface lets you use ILE C stream
I/O with files up to and greater than two gigabytes in size. (C++ stream I/O for
files greater than two gigabytes is not supported.) To enable the 64-bit interface,
specify the *IFS64IO option with the SYSIFCOPT keyword on the CRTCPPMOD or
CRTBNDCPP command prompt. When this option is specified, the compiler
defines the __IFS64_IO__ macro which, for example, remaps the open() function to
an open64() function to allow 64-bit indexing..
Open Mode
The fstream(), ifstream(), and ofstream() classes have a new open mode ios::text,
which opens the file in text mode.
By default, I/O streams are opened in binary mode (not in text mode, as stated in
the Version 3 Release 7 books).
Line-End Characters
v If the input or output is unformatted (for example, via the read() or write()
method), newline (\n) characters are not expanded to \r\n on output and \r
characters are not stripped out on input.
v If the input or output is formatted (via the >> or << operator), newline
(\n) characters are not expanded to \r\n on output but any \r characters are
stripped out on input
If you want carriage return (\r) characters to be added, use the fopen() function
with crln=Y (the default).
Externally described files are files that have their field descriptions stored as part
of the file. The description includes information about the type of file (such as data
or device), record formats, and a description of each field and its attributes.
You can create an externally described database file using the SQL/400 database,
the Interactive Data Definition Utility (IDDU) using DDS for externally described
files, or with Data Description Specifications (DDS). The ILE C preprocessor
automatically creates C structure typedefs from external file descriptions when you
use the #pragma mapinc directive with the #include directive.
The #pragma mapinc directive only identifies the file formats and fields to the
compiler; it does not include the file description in the ILE C program. To include
a file description, the #include directive must be coded in the ILE C program.
You refer to the include-name parameter of the #pragma mapinc directive on the
#include. The #include directive must be coded after the #pragma mapinc directive
in your source program.
For example, to include a type definition of the input fields for the record format
FMT from the file EXAMPLE/TEST, the following statements must appear in your
program in the order shown below:
#pragma mapinc("tempname","EXAMPLE/TEST(FMT)","input","d",,"")
#include "tempname"
Typedefs
In C programs, for each format specified on the #pragma mapinc directive, the
compiler creates a C typedef of type struct describing the fields in the external
file.You indicate the type of structure definitions to be included in your ILE C/C++
program by the element you select on the options parameter. A header description
is also created which contains information about the external file.
/* ------------------------------------------------------------------ */
/* PHYSICAL FILE: EXAMPLE/TEST */
/* FILE CREATION DATE: 93/09/01 */
/* RECORD FORMAT: FMT1 */
/* FORMAT LEVEL IDENTIFIER: 371E00A681EA7 */
/* ------------------------------------------------------------------ */
Parameters of the #pragma mapinc directive are used to create the name of the
created type. LIBRARY, FILE, and FORMAT are the library-name, file-name, and
format-name specified on the #pragma mapinc directive. These names are folded to
uppercase unless quoted names are used. The library and file names can be
replaced with your own prefix-name as specified on the #pragma mapinc directive.
Any characters that are not recognized as valid by the C language that appear in
library and file names are translated to the underscore (_) character.
Note: Do not use the special characters #, @, or $ in library and file names. If these
characters are used in library and file names, they are also translated to the
underscore (_) character.
The tag on the structure name indicates the type of fields that are included in the
structure definition. The possible values for tag are:
Unlike the naming convention used for other listed field types, if field type lvlchk
is specified, the name of the array of structure type created is _LVLCHK_T.
When the lname option is specified and the filename in the #pragma mapinc
directive is greater than 10 characters in lengths, a system generated 10-character
name will be used in the typedefs generated by the compiler.
Level Checking
If you specify the lvlchk option on the #pragma mapinc directive, in addition to
generating the type _LVLCHK_T (array of structures), a variable of type
_LVLCHK_T is also generated. It is also initialized so that each array element
contains the level check information for the corresponding formats specified on the
#pragma mapinc. The last two array elements are always empty strings, one for
each field of the structure.
Example
The following example shows the #pragma mapinc directive and the lvlchk option
to perform a level check on a file when it is opened.
The following example contains the DDS in the file T1520DD3 in the library
MYLIB.
A R PURCHASE
A ITEMNAME 10
A SERIALNUM 10
A K SERIALNUM
/* ------------------------------------------------------------------------*/
/* PHYSICAL FILE: MYLIB/T1520DD3 */
/* FILE CREATION DATE: 93/09/02 */
/* RECORD FORMAT: PURCHASE */
/* FORMAT LEVEL IDENTIFIER: 322C4B361172D */
/* ------------------------------------------------------------------------*/
typedef _Packed struct {
char SERIALNUM[10];
/* DDS - ASCENDING*/
/* STRING KEY FIELD*/
}MYLIB_T1520DD3_PURCHASE_key_t;
typedef _Packed struct {
unsigned char format_name[10];
unsigned char sequence_no[13];
} _LVLCHK_T[];
_LVLCHK_T MYLIB_T1520DD3_DD3FILE_lvlchk = {
"PURCHASE ", "322C4B361172D",
"", "" };
Record format functions are useful when working with display, ICF, and printer
files. Logical files can also contain more than one record format.
The record format name for a device file defaults to blank unless you explicitly set
it to a name with _Rformat(). You can reset the format name to blank by passing a
blank name to _Rformat().
If the record format does not contain fields that match the option specified (input,
output, both, key, indicators or nullflds) on the #pragma mapinc directive, the
following comment appears after the header description:
Note: Do not use #, @, or $ in record format names. These characters are not
allowed in Integrated Language Environment identifiers and cannot appear
in a typedef name. If you have record format names that contain #, @ or $,
these characters are translated to the lowercase characters p, a, and d,
respectively.
Some of the special characters that are supported in DDS variable names are not
supported by the Integrated Language Environment compiler and library. If you
use the special characters @, #, or $ in a field name, those characters are changed
to lowercase a, p and d in the typedef that is generated. If the format names
contain C characters that are not valid, they are translated to the underscore (_)
character.
If the fields defined in the DDS are aligned (for example, all are character fields),
you can use the typedef that is generated without packing the structure.
You can include external file descriptions for Distributed Data Management (DDM)
files using the same method described for database files if you specify either the
input, key, or both option. If you specify output or indicators, an error message is
issued.
If all the fields are defined as BOTH or INPUT, only one typedef structure is
generated in the typedef.
Key Fields
To include a separate structure type definition for the key fields in a format,
specify the key option on the #pragma mapinc directive. Comments are listed
beside the fields in the structure definition to indicate how the key fields are
defined in the externally described file.
Example
The following ILE C program contains the #pragma mapinc directive to include the
externally described database file CUSMSTL:
The following example contains the DDS for the file T1520DD8 in the library
MYLIB.
#include <stdio.h>
#include <recio.h>
#include <stdlib.h>
#include <string.h>
#include <decimal.h>
int main(void)
{
QGPL_T1520DD8_CUSREC_both_t x;
if (x.ARBAL<1000)
{
printf("%s has a balance less than $1000!\n", x.NAME);
}
Figure 76. T1520EDF — ILE C Source to Include an Externally Described Database File
The typedefs are created in your ILE C source listing that is based on the #pragma
directive that is specified in the ILE C source program.
Figure 77. Ouput Listing from Program T1520EDF — Customer Master Record
Input Fields
You specify the input option when you want to include the fields that are defined
as INPUT or BOTH in the externally described device file. Response indicators are
included when the DDS keyword INDARA is not specified.
#pragma mapinc("test","example/phonelist(phone)","input","")
#include "test"
A R PHONE
A CF03(03 ’EXIT’)
A 1 35’PHONE BOOK’
A DSPATR(HI)
A 7 28’Name:’
A NAME 11A I 7 34
A 9 25’Address:’
A ADDRESS 20A I 9 34
A 11 25’Phone #:’
A PHONE_NUM 8A I 11 34
A 23 34’F3 - EXIT’
A DSPATR(HI)
/* --------------------------------------------------------*/
/* DEVICE FILE: EXAMPLE/PHONELIST */
/* FILE CREATION DATE: 93/09/01 */
/* RECORD FORMAT: PHONE */
/* FORMAT LEVEL IDENTIFIER: 10D2D0DB2BEE8 */
/* --------------------------------------------------------*/
typedef struct {
char IN03; /* EXIT */
char NAME[11];
char ADDRESS[20];
char PHONE_NUM[8];
}EXAMPLE_PHONELIST_PHONE_i_t;
Output Fields
Specify the output option when you want to include fields that are defined as
OUTPUT or BOTH in the externally described device file. Option indicators are
included when the DDS keyword INDARA is not specified.
Both Fields
When you specify both, two typedef structs are generated. One typedef contains all
fields defined as INPUT or BOTH; the other contains all fields defined as
OUTPUT, or BOTH. One typedef structure is generated for each format that is
specified when all fields are defined as BOTH and a separate indicator area is not
specified. Option and response indicators are included in the typedef structs if the
keyword INDARA is not specified in the external file description.
If you are including the external file description for only one record format, a
typedef union is automatically created containing the two typedefs. The name of
this typedef union is LIBRARY_FILE_FMT_both_t. If you specify a
union-type-name on the #pragma mapinc directive, the name that is generated is
union-type-name_t.
A INDARA
A R FMT
A CF01(50)
A CF02(51)
A CF03(99 ’EXIT’)
A 1 35’PHONE BOOK’
A DSPATR(HI)
A 7 28’Name:’
A NAME 11A I 7 34
A 9 25’Address:’
A ADDRESS 20A O 9 34
A 11 25’Phone #:’
A PHONE_NUM 8A O 11 34
A 23 34’F3 - EXIT’
/* -----------------------------------------------------------------
/* DEVICE FILE: EXAMPLE/TEST
/* FILE CREATION DATE: 93/09/01
/* RECORD FORMAT: FMT
/* FORMAT LEVEL IDENTIFIER: 371E00A681EA7
/* -----------------------------------------------------------------
/* INDICATORS FOR FORMAT FMT
/* INDICATOR 50
/* INDICATOR 51
/* INDICATOR 99
/* -----------------------------------------------------------------
typedef struct {
char NAME[11];
}EXAMPLE_TEST_FMT_i_t;
typedef struct {
char ADDRESS[20];
char PHONE_NUM[8];
}EXAMPLE_TEST_FMT_o_t;
typedef union {
EXAMPLE_TEST_FMT_i_t EXAMPLE_TEST_FMT_i;
EXAMPLE_TEST_FMT_o_t EXAMPLE_TEST_FMT_o;
}EXAMPLE_TEST_FMT_both_t;
This shows the structure definitions that are created when the format FMT in the
device file EXAMPLE/TEST is included in your program. The external file
description contains three indicators IN50, IN51, and IN99, and the keyword
INDARA. The indicators will appear as comments and will not be included in the
structure as the option ″indicators″ was not specified in the #pragma mapinc.
Indicator Field
Indicators for a record format are allowed only for device files, and can be defined
as a separate indicator structure or as a member in the record format structure.
You must also set the address of the separate indicator area by using the
_Rindara() function for record files, before performing I/O operations. If you
specify indicators on the #pragma mapinc directive and do not use the keyword
INDARA in your external file description, you will receive a warning message at
compile time.
If indicators are requested and exist in the format, a 99-byte structure definition is
created that contains a field declaration for each indicator defined in the DDS. The
A INDARA
A R FMT
A CF01(50)
A CF02(51)
A CF03(99 ’EXIT’)
A 1 35’PHONE BOOK’
A DSPATR(HI)
A 7 28’Name:’
A NAME 11A I 7 34
A 9 25’Address:’
A ADDRESS 20A O 9 34
A 11 25’Phone #:’
A PHONE_NUM 8A O 11 34
A 23 34’F3 - EXIT’
#pragma mapinc("example/temp","exindic/test(fmt)","indicators","")
#include "example/temp"
When this DDS is included in your ILE C/C++ program, the following struct is
generated:
/* ------------------------------------------------------------------ */
/* DEVICE FILE: EXINDIC/TEST */
/* FILE CREATION DATE: 93/09/01 */
/* RECORD FORMAT: FMT */
/* FORMAT LEVEL IDENTIFIER: 371E00A681EA7 */
/* ------------------------------------------------------------------ */
/* INDICATORS FOR FORMAT FMT */
/* INDICATOR 50 */
/* INDICATOR 51 */
/* INDICATOR 99 */
/* ------------------------------------------------------------------ */
typedef struct {
char IN01_in49[49]; /* UNUSED INDICATOR(S) */
char IN50;
char IN51;
char IN52_in98[47]; /* UNUSED INDICATOR(S) */
char IN99;
}EXINDIC_TEST_FMT_indic_t;
This shows a typedef of a structure for the indicators in the format FMT of the file
EXINDIC/TEST. The external file description contains three indicators: IN50, IN51,
and IN99. The keyword INDARA is also specified in the DDS for the file.
If indicators are defined for a record format and the indicators option is not
specified on the #pragma mapinc directive, a list of the indicators in the DDS is
included as a comment in the header description. The following shows the header
/* ------------------------------------------------------------------ */
/* DEVICE FILE: EXINDIC/TEST */
/* CREATION DATE: 93/09/01 */
/* RECORD FORMAT: FMT */
/* FORMAT LEVEL IDENTIFIER: 371E00A681EA7 */
/* ------------------------------------------------------------------ */
/* INDICATORS FOR RECORD FORMAT FMT */
/* INDICATOR 50 */
/* INDICATOR 51 */
/* INDICATOR 99 */
/* ------------------------------------------------------------------ */
The typedef union contains structure definitions created for each format. Structure
definitions that are created for key fields when the key option is specified are not
included in the union definition. The name of the union definition is
union-type-name_t. The name you provide for the union-type-name is not folded
to uppercase.
The following shows the typedefs created for a logical file with two record formats
with the BOTH and KEY options specified. A typedef union with the tag buffer_t is
also generated.
#pragma mapinc("pay","lib1/pay(fmt1 fmt2)","both key","","buffer","Pay")
#include "pay"
typedef struct {
.
.
.
}Pay_FMT1_key_t;
/* --------------------------------------------------------*/
/* LOGICAL FILE: PAY */
/* FILE CREATION DATE: 93/09/01 */
/* RECORD FORMAT: FMT2 */
/* FORMAT LEVEL IDENTIFIER: 371E00A681EA7 */
/* --------------------------------------------------------*/
typedef struct {
.
.
.
}Pay_FMT2_both_t;
typedef struct {
.
.
.
}Pay_FMT2_key_t;
typedef union {
Pay_FMT1_both_t; Pay_FMT1_both;
Pay_FMT2_both_t; Pay_FMT2_both;
}buffer_t;
If you specify *ALL, or more than one record format on the format-name
parameter, structure definitions for multiple formats are created.
If you specify multiple formats, and the input, or output option, one structure is
created for each format. The following shows the structure definitions that are
created when you include the following statements in your program. The device
file TESTLIB/FILE contains two record formats, FMT1, and FMT2. Each record
format has fields defined as OUTPUT in its file description.
#pragma mapinc("example","testlib/file(fmt1 fmt2)","output","z","unionex")
#include "example"
/* ------------------------------------------------------------------ */
/* DEVICE FILE: TESTLIB/FILE */
/* CREATION DATE: 93/09/01 */
/* RECORD FORMAT: FMT2 */
/* FORMAT LEVEL IDENTIFIER: 371E00A681EA8 */
/* ------------------------------------------------------------------ */
typedef struct {
.
.
.
}TESTLIB_FILE_FMT2_o_t;
typedef union {
TESTLIB_FILE_FMT1_o_t TESTLIB_FILE_FMT1_o;
TESTLIB_FILE_FMT2_o_t TESTLIB_FILE_FMT2_o;
}unionex_t;
When both are specified as an option, two structure definitions are created for each
format. The following shows the structure definitions created when you include
two formats, FMT1 and FMT2, for the device file EXAMPLE/TEST and specify the
both option:
#pragma mapinc("test","example/test(fmt1 fmt2)","both","z","unionex")
#include "test"
If all the fields are defined as BOTH and there are to be no indicators in the
typedef struct, only one typedef struct is generated for each format specified. The
following shows a separate typedef structure for input and output fields.
These character arrays then need to be converted to an ILE C/C++ numeric data
type to be used in the ILE C/C++ program. If neither the d or p option is
specified, the d option is the default. See Chapter 15, “Using Packed Decimal Data
in Your C Programs” on page 309 for examples in using packed decimal data types.
Note: You must include the <decimal.h> header file if you have a DDS file with
packed decimal fields defined. You must also use the #pragma mapinc
directive d option in your ILE C/C++ source code.
The MI cpynv() function can also be used to convert packed or zoned decimal data
to an ILE C/C++ numeric data type. It can be used to convert an ILE C/C++
numeric data type to packed or zoned decimal data.
The conversion functions are included with the ILE C/C++ compiler so that EPM
C code that uses these functions can be maintained.
If you are doing database I/O operations, you can use a logical file with integer or
floating point fields to redefine packed and zoned fields in your physical file.
When you perform an input, or output operation through the logical file, the
iSeries 400 system converts the data for you automatically.
Long record field names are not mapped to a 10 character short name. When
lname option is specified it is assumed that the long name format for the file name
is being used. If the file name has more than 10 characters then internally this
name is converted to the associated short name. This short name is used to extract
the external file definition. When a regular short name of 10 characters or less is
specified, no conversion occurs.
The #pragma mapinc directive uses the 30 character record field names in the
typedefs that are generated, with or without the lname option that is specified. For
the filenames that are specified using a long name format, the typedefs generated
use the associated regular 10 character short filename.
Database files can be created and used as either physical files or logical files.
Database files can contain either data or source statements.
ILE C/C++ programs access files on remote systems through distributed data
management (DDM). DDM allows application programs on one system to use files
that are stored on a remote system as database files. No special statements are
required in ILE C/C++ programs to support DDM files.
A DDM file is created by a user or program on a local (source) system. This file
(with object type *FILE) identifies a file that is kept on a remote (target) system.
The DDM file provides the information that is needed for a local iSeries 400 to
locate a remote iSeries 400 and to access the data in the target file.
Records in database files can be described using either a field level description or
record level description.
A field-level description describes the fields in the record to the system. Database
files that are created with field level descriptions are referred to as externally
described files.
A record-level description describes only the length of the record, and not the
contents of the record. Database files that are created with record level descriptions
are referred to as program-described files. This means that your ILE C/C++
program must describe the fields in the record.
Logical files do not contain data. They contain a description of records that are
found in one or more physical files. A logical file is a view or representation of one
or more physical files. Logical files that contain more than one format are referred
to as multi-format logical files.
If your program processes a logical file which contains more than one record
format, you can use the _Rformat() function to set the format you wish to use.
Some operations cannot be performed on logical files. If you open a logical file for
stream file processing with open modes w, w+, wb or wb+, the file is opened but
not cleared. If you open a logical file for record file processing with open modes
wr or wr+, the file is opened but not cleared.
Records in iSeries database files can be described using either a field level
description or record level description.
The field-level description of the record includes a description of all fields and
their arrangement in this record. Since the description of the fields and their
arrangement is kept within a database file and not in your ILE C/C++ program,
database files created with a field-level description are referred to as externally
described files. See Chapter 10, “Using Externally Described Files in Your
Programs” on page 185.
Records in data files are grouped into members. All the records in a file can be in
one member, or they can be grouped into different members. Most database
commands and operations by default assume that database files which contain
data have only one member. This means that when your ILE C program works
with database files containing data you do not need to specify the member name
for the file unless your file contains more than one member.
Usually, database files that contain source programs are made up of more than one
member. Organizing source programs into members within database files allows
you to better manage your programs. These source members contain source
statements that the iSeries system uses to create iSeries objects. For example, a
source member which contains C++ statements is used to create a program object.
Records that are retrieved using an arrival sequence access path will be retrieved
in the same order in which they were added to the file. This is similar to
processing sequential files. New records are physically stored at the end of the file.
An arrival sequence access path is valid for both physical and logical files.
Records that are retrieved using a keyed sequence access path are retrieved based
on the contents of one or more key fields in the record. This is similar to
processing indexed or keyed files on other systems. A keyed sequence access path
is updated whenever records are added, deleted, or updated, or when the contents
of the key field are changed. This access path is valid for both physical and logical
files.
If a file defines more than one record format, each record format may have
different key fields. The default key for the file (for example, if no format is
specified) will be the key fields that all record formats have in common. If there is
no default key (for example, no common key fields), the first record in the file will
always be returned on an input operation that does not specify the format.
Note: When your ILE C/C++ program opens a file, the default is to process the
file with the access path that is used to create the file. If you specify
arrseq=N (the default), the file is processed the way it was created. This
means that if the file was created using a keyed sequence access path, your
ILE C/C++ program processes the file by using a keyed sequence access
path. If you specify arrseq=Y, the file is processed using arrival sequence.
This means that even though the file was created using a keyed sequence
access path, your ILE C/C++ program processes the file by using an arrival
sequence access path.
If you want an indication that your program is processing a record that contains a
duplicate key value, specify dupkey=y on the call to _Ropen() that opens the file. If
an I/O operation on a record is successful and a duplicate key value is found in
that record, the dup_key flag in the _RIOFB_T structure is set. (The _Rreadd()
function does not update this flag.)
Note: Using the dupkey=y option on the call to the _Ropen() function may cause
your I/O operations to be slower.
Chapter 11. Using Database Files and Distributed Data Management Files In Your Programs 203
You can avoid duplicate key values by specifying the keyword UNIQUE in the
DDS file.
Deleted Records
When a database record is deleted, the physical record is marked as deleted but
remains in the file. Deleted records can be overwritten by using the _Rwrite()
function. Deleted records can be removed from a file by using the RGZPFM
(Reorganize Physical File Member) command. They can also be reused on write
operations by specifying the REUSEDLT(*YES) parameter on the CRTPF command.
Deleted records can occur in a file if the file has been initialized with deleted
records using the Initialize Physical File Member (INZPFM) command. Once a
record is deleted, it cannot be read.
Locking
The iSeries database has built-in record integrity. The system determines the lock
conditions that are based on how your ILE C/C++ program opens the file. This
table shows the valid open modes and the lock states that are associated with
them:
Table 13. Lock States for Open Modes
Open Mode Lock State
r, rb shared for read (*SHRRD)
a, w, ab, wb, a+, r+, w+, ab+, rb+, wb+ shared for update (*SHRUPD)
You can change the lock state for a file by using the Override Database File
(OVRDBF) command or the Allocate Object (ALCOBJ) command before you open
the file. For example, your ILE C program can use the system() function to call the
ALCOBJ command:
If a file is opened for update, the database locks any record read or positioned to
provided the __NO_LOCK option is not specified. This means that the locked
record cannot be locked to any other open data path, whether that open data path
is opened by another program or even by the same program through another file
pointer.
Successfully reading and locking another record releases the lock on a previously
locked record. If the __NO_LOCK option is specified on any read then the lock on
the previously locked record is not released. You can also release a lock on a record
by using the _Rrlslck() function.
Sharing
If your application consists only of C and C++ modules, the preferred way to share
a file is by opening the file in one program and passing the file pointer to the other
programs. This eliminates the need to open the file more than once.
Sharing a file in the same job allows programs in that job to share the file’s status,
record position, and buffer. The SHARE(*YES) parameter on the create file, change
file, and override the file commands allows an Open Data Path (ODP) to be shared
You can share open data paths for streams processed a record at a time. You can
also share open data paths for record files. You should not share the open data
path for streams processed a character at a time, as unpredictable results can occur
when you perform I/O operations.
Note:
v If you want to share a file between your C/C++ programs and programs
that are written in other languages, you can do this by sharing an open
data path.
v The first open of a shared file determines the open mode for the file (for
example, whether it is open for INPUT, OUTPUT, UPDATE, and
DELETE). If a subsequent open specifies an open mode that was not
specified by the first open, the file will be opened the second time but the
open mode will be ignored. For example, if the first open specifies an
open mode of IO and the second open specifies IOUD, the file will be
opened the second time with a mode of IO.
If a file is opened with nullcap=Y, the database provides input and output null
maps as well as a key null map, if the file is keyed. The input and output null
maps consist of one byte for each field in the current record format of the file.
These null field maps are used to communicate between the database and your
program to indicate which specific fields should be considered null.
The _RFILE structure defined in the <recio.h> file contains pointers to the input,
output and key null field maps, and the lengths of these maps (null_map_len and
null_key_map_len).
When you write to a database file, you specify which fields are null with a
character ’1’. If a field is not null you specify the character ’0’. This is specified in
the null field map pointed to by the out_null_map pointer. If the file does not
contain any null capable fields, but has been opened with nullcap=Y, your
program must set each field in the null field map to the character ’0’. This must be
done prior to writing any data to the file.
When you read from a database file, the corresponding byte in the null field map
is indicated with a character ’1’ if the field is considered null. This is specified in
the null field map pointed to by the in_null_map pointer.
The null key field map consists of one byte for each field in the key for the current
record format. If you are reading a database file by key which has null fields, you
must first indicate in the null key map pointed to by null_key_map which fields
contain null. Specify character ’1’ for any field to be considered null, and character
’0’ for the other fields.
Chapter 11. Using Database Files and Distributed Data Management Files In Your Programs 205
When the _Rupdate() function is called to update a file which has been opened to
allow null field processing, the system input buffer is used. As a result, the
database requires that an input null field map be provided through the
in_null_map pointer. Prior to calling _Rupdate(), the user must clear and then set
the input null field map (using the in_null_map pointer) according to the data
which will be used to update the record.
You can use the #pragma mapinc directive to generate typedefs that correspond to
the null field maps. You can cast the null field map pointers in the _RFILE
structures to these types to manipulate these maps. Null field macros have also
been provided in the <recio.h> file to assist users in clearing and setting the null
field maps in their programs.
v rr v wr v ar
v rr+ v wr+ v ar+
The valid keyword parameters for database and DDM files are:
v rb v wb v ab
If you specify a database or a DDM file the parameter type must be ″record″.
Note: The physical database files that are created when the database file does not
exist (where the open mode is wb or ab) are equivalent to specifying the
following CL command:
CRTPF FILE(filename) RCDLEN(lrecl)
Records in this file are created with a record length that is based on the
keyword parameter lrecl.
The only way to create a DDM file is to use the Create DDM File
(CRTDDMF) command. If you use the fopen() function with a mode of wb
or ab and the DDM file exists on the source system, but the database file
does not exist on the remote system, a physical database file is created on
the remote system. If the DDM file does not exist on the source system, a
physical database file is created on the source system.
Binary stream record-at-a-time files cannot be processed by key. As well, they can
only be opened with the rb, wb, and ab modes.
Example
The following example copies data from the input file T1520ASI to the output file
T1520ASO by using the same order in which they are added to the file T1520ASI.
The _Rreadn() and _Rwrite() functions are used.
1. To create a physical file T1520ASI for the input, type:
CRTPF FILE(MYLIB/T1520ASI) RCDLEN(300)
2. Type the following sample data into T1520ASI:
Chapter 11. Using Database Files and Distributed Data Management Files In Your Programs 207
joe 5
fred 6
wilma 7
3. To create a physical file T1520ASO for the output, type:
CRTPF FILE(MYLIB/T1520ASO) RCDLEN(300)
4. To create the program T1520ASP using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520ASP) SCRFILE(QCLE/QACSRC)
#include <stdio.h>
#include <stdlib.h>
#include <recio.h>
int main(void)
{
_RFILE *in;
_RFILE *out;
_RIOFB_T *fb;
char record[_RCDLEN];
_Rclose(in);
_Rclose(out);
}
Figure 88. T1520ASP — ILE C Source to Process a Database Record File in Arrival
Sequence
This program uses the _Ropen() function to open the input file T1520ASI to
access the records in the same order that they are added. The _Ropen() function
Example
The following example updates data in the record file T1520DD3 by using the key
field SERIALNUM. The _Rupdate() function is used.
1. Type:
CRTPF FILE(MYLIB/T1520DD3) SRCFILE(QCLE/QADDSSRC)
To create the physical file T1520DD3 that uses the following DDS source:
A R PURCHASE
A ITEMNAME 10
A SERIALNUM 10
A K SERIALNUM
Although you enter the data as shown, the file T1520DD3 is accessed by the
program T1520KSP in keyed sequence. Therefore the program T1520KSP reads
the file T1520DD3 in the following sequence:
grape 1000222010
cherry 1000222020
apple 1000222030
orange 1000222200
3. Type:
CRTBNDC PGM(MYLIB/T1520KSP) SRCFILE(QCLE/QACSRC)
Chapter 11. Using Database Files and Distributed Data Management Files In Your Programs 209
/* This program illustrates how to update a record in a file using */
/* the _Rupdate() function. */
#include <stdio.h>
#include <stdlib.h>
#include <recio.h>
int main(void)
{
_RFILE *in;
char new_purchase[21] = "PEAR 1002022244";
_Rfeod(in);
_Rclose(in);
}
Figure 90. T1520KSP — ILE C Source to Process a Database Record File in Keyed
Sequence
This program uses the _Ropen() function to open the record file T1520DD3. The
default access path which is the keyed sequence access path is used to create
the file T1520DD3. The _Rlocate() function locks the first record in the keyed
sequence. The _Rupdate() function updates the record that is locked by
_Rlocate() to PEAR 1002022244. (The first record becomes the second record in
the keyed sequence access path because the key has changed.)
4. To run the program T1520KSP, type:
CALL PGM(MYLIB/T1520KSP)
Since grape is the first record in the keyed sequence, it is updated, and the data
file T1520DD3 is as follows:
orange 1000222200
PEAR 1002022244
apple 1000222030
cherry 1000222020
Example
A R PURCHASE
A ITEMNAME 10
A SERIALNUM 10
A K SERIALNUM
Type:
CRTPF FILE(MYLIB/T1520DD4) SRCFILE(QCLE/QADDSSRC)
2. Type the following sample data into T1520DD4:
orange 1000222200
grape 1000222010
apple 1000222030
cherry 1000222020
3. Type:
CRTBNDC PGM(MYLIB/T1520REC) SRCFILE(QCLE/QACSRC).
#include <stdio.h>
#include <stdlib.h>
#include <recio.h>
int main(void)
{
char buf[21];
_RFILE *fp;
_XXOPFB_T *opfb;
/* Open the file for processing in arrival sequence. */
opfb = _Ropnfbk ( fp );
printf ( "Library: %10.10s\nFile: %10.10s\n",
opfb->library_name,
opfb->file_name);
Figure 92. T1520REC — ILE C Source to Process a Database File Using Record I/O
Functions (Part 1 of 2)
Chapter 11. Using Database Files and Distributed Data Management Files In Your Programs 211
/* Get the last record. */
_Rrlslck ( fp );
/* Read the same record. */
_Rclose ( fp );
}
Figure 92. T1520REC — ILE C Source to Process a Database File Using Record I/O
Functions (Part 2 of 2)
The _Ropen() function opens the file T1520DD4. The _Ropnfbk() function gets
the library name MYLIB and file name T1520DD4. The _Rreadl() function
reads the fourth record "cherry 1000222020". The _Rreadp() function reads the
third record " apple 1000222030". The _Rrlslck() function releases the lock on
this record so that _Rreads() can read it again. The _Rreadd() function reads
the second record "grape 1000222010" without locking it. The _Rreadf()
function reads the first record "orange 1000222200". The _Rdelete() function
deletes the second record. All records are then read and printed.
4. To run the program T1520REC, type:
CALL PGM(MYLIB/T1520REC)
Before you can start commitment control, you must ensure that all the database
files you want processed as one unit are in one commitment control environment.
All the files within this environment must be journaled to the same journal. Use
the CL commands Create Journal Receiver (CRTJRNRCV), Create Journal
(CRTJRN) and Start Journal Physical File (STRJRNPF) to prepare for the journaling
environment.
You can use commitment control to define and process several changes to database
files as a single transaction.
Example
The following example uses commitment control. Purchase orders are entered and
logged in two files, T1520DD5 for daily transactions, and T1520DD6 for monthly
transactions. Journal entries that reflect the changes that are made to T1520DD5
and T1520DD6 are kept in the journal JRN.
1. To create the physical file T1520DD5 using the DDS source shown below, type:
CRTPF FILE(QTEMP/T1520DD5) SRCFILE(QCLE/QADDSSRC)
A R PURCHASE
A ITEMNAME 30
A SERIALNUM 10
2. To create the physical file T1520DD6 using the DDS source shown below, type:
CRTPF FILE(QTEMP/T1520DD6) SRCFILE(QCLE/QADDSSRC)
Chapter 11. Using Database Files and Distributed Data Management Files In Your Programs 213
A R PURCHASE
A ITEMNAME 30
A SERIALNUM 10
Notification text is sent to the file NFTOBJ when the ILE C program
T1520COM that uses commitment control is run.
4. To create the display file T1520DD7 using the DDS source shown below, type:
CRTDSPF FILE(QTEMP/T1520DD7) SRCFILE(QCLE/QADDSSRC)
A DSPSIZ(24 80 *DS3)
A REF(QTEMP/T1520DD5)
A INDARA
A CF03(03 ’EXIT ORDER ENTRY’)
A R PURCHASE
A 3 32’PURCHASE ORDER FORM’
A DSPATR(UL)
A DSPATR(HI)
A 10 20’ITEM NAME :’
A DSPATR(HI)
A 12 20’SERIAL NUMBER :’
A DSPATR(HI)
A ITEMNAME R I 10 37
A SERIALNUM R I 12 37
A 23 34’F3 - Exit’
A DSPATR(HI)
A R ERROR
A 6 28’ERROR: Write failed’
A DSPATR(BL)
A DSPATR(UL)
A DSPATR(HI)
A 10 26’Purchase order entry ended’
#include <stdio.h>
#include <recio.h>
#include <stdlib.h>
#include <string.h>
#define PF03 2
#define IND_OFF ’0’
#define IND_ON ’1’
int main(void)
{
char buf[40];
int rc = 1;
_SYSindara ind_area;
_RFILE *purf;
_RFILE *dailyf;
_RFILE *monthlyf;
Figure 96. T1520COM — ILE C Source to Group File Operations Using Commitment Control
(Part 1 of 2)
Chapter 11. Using Database Files and Distributed Data Management Files In Your Programs 215
while ( rc && ind_area[PF03] == IND_OFF )
{
rc = (( _Rwrite ( dailyf, buf, sizeof(buf) ))->num_bytes );
rc = rc && ( _Rwrite ( monthlyf, buf, sizeof(buf) ))->num_bytes;
if ( rc )
{
_Rcommit ( "Transaction complete" );
}
else
{
_Rrollbck ( );
_Rformat ( purf, "ERROR" );
}
_Rwrite ( purf, "", 0 );
_Rreadn ( purf, buf, sizeof(buf), __DFT );
}
}
Figure 96. T1520COM — ILE C Source to Group File Operations Using Commitment Control
(Part 2 of 2)
The _Ropen() function opens the purchase display file, the daily transaction
file, and the monthly transaction file. The _Rindara() function identifies a
separate indicator area for the purchase file. The _Rformat() function selects
the purchase record format defined in T1520DD7. The _Rwrite() function
writes the purchase order display. Data that is entered updates the daily and
monthly transaction files T1520DD5 and T1520DD6. The transactions are
committed to these database files that use the _Rcommit() function.
9. To run program T1520COM under commitment control, type:
STRCMTCTL LCKLVL(*CHG) NFYOBJ(MYLIB/NFTOBJ (*FILE)) CMTSCOPE(*JOB)
CALL PGM(MYLIB/T1520COM)
ITEM NAME :
SERIAL NUMBER :
F3 - Exit
10. Type in the following sample data into the Purchase Order Form display:
After an item and serial number are entered, T1520DD5 and T1520DD6 files
are updated. The sample data shows that the contents of the daily transaction
file T1520DD5 file after three purchase order items are entered.
11. To end commitment control, type:
ENDCMTCTL
The journal JRN contains entries that correspond to changes that are made to
T1520DD5 and T1520DD6.
Blocking Records
You can use record blocking to improve the performance of I/O operations on files
that are opened for input or output only. Specify the blksize=value parameter on a
call to the fopen() function or the blkrcd=y on a call to the _Ropen() function to
turn on record blocking. In some situations, the operating system will return only
one record in the block when processing a file. In these cases there is no
performance gain.
You can turn off record blocking without changing your program by specifying
SEQONLY(*YES) on the OVRDBF command.
Note: When record blocking is in effect, the I/O feedback structure is only
updated when a block of records is transferred between your program and
the system.
Chapter 11. Using Database Files and Distributed Data Management Files In Your Programs 217
218 ILE C/C++ Programmer’s Guide
Chapter 12. Using Device Files in Your Programs
This chapter describes how to:
v Specify indicators as part of the file buffer to be read or written
v Return indicators in a separate indicator area
v Establish a default program device
v Change a default program device
v Use the _Riofbk() function to obtain feedback information
v Write source statements to a tape file
v Write source statements to a diskette file
v Use save files
I/O Considerations
Indicators allow information to be passed from a program to the system or from
the system to a program. Display, ICF, and printer files can make use of indicators.
Indicators are boolean data items that can contain a value of either 1 or 0
(character). There are two types of indicators:
Option Indicators pass information from a program to the system. For example,
they can control which fields in a record can be displayed.
To use indicators, the display, ICF, and printer files must be defined as an
externally described file. The data description specification (DDS) for the externally
described display file must contain a one-character INDICATOR field for each
indicator. Indicators are either in the records read or written by the program (the
indicators are in the file buffer) or in a separate indicator area.
The display, ICF, and printer files must be opened with the keyword indicators=y
for the indicators to be specified in a separate indicator area. Use the _Rindara()
function to identify the separate indicator buffer associated with the file.
The Application Display Programming manual describes major and minor return
codes and their meanings for display files. The Printer Device Programming manual
describes major and minor return codes and their meanings for printer files.
Your program should test the return code after each I/O operation and define any
error handling operations that are based on the major and minor return codes. If
the major return code is 00, the operation completed successfully. If an error occurs
with a display, ICF, or printer file your program should handle it as it occurs.
A subfile is a display file that contains a group of records with the same record
format that can be accessed by relative record number. The records of a subfile can
be displayed on a display station. The system sends the entire group of records to
the display in a single operation and receives the group from the display in
another operation. The object type for both is *FILE.
v rb v ab+ v wb and ab
The CRTDSPF command is the only way to create a display file. If you use the
fopen() function and the display file does not exist, a physical database file is
created.
You can change the default program device in the following ways:
v Use the Racquire() function to explicitly acquire another program device. The
device that is just acquired becomes the current program device.
v Use the _Rpgmdev() function to change the current program device that is
associated with a file to a previously-acquired device. This program device can
be used for subsequent input and output operations to the file.
v The actual program device that is read becomes the default device if you read
from an invited device using the _Rreadindv() function.
v Use the _Rrelease() function to release a device from the file. When you release
the device, it is no longer available for I/O operations.
To read the next changed subfile record, use the _Rreadnc() function. This function
searches for the next changed record from the current position in the file. If this is
the first read operation, the first changed record in the subfile is read. If the
end-of-file is reached before finding a changed record, EOF is returned in the
num_bytes field of the _RIOFB_T structure.
Example
The following example shows how to specify an indicator in a record that is read
by program T1520ID1. The indicator is placed in the file buffer of an externally
described file. The DDS for the externally described file contains one character
indicator field.
1. To create the display file T1520DD9 using the DDS source shown below, type:
CRTDSPF FILE(MYLIB/T1520DD9) SRCFILE(QCLE/QADDSSRC)
A R PHONE
A CF03(03 ’EXIT’)
A 1 35’PHONE BOOK’
A DSPATR(HI)
A 7 28’Name:’
A NAME 11A I 7 34
A 9 25’Address:’
A ADDRESS 20A I 9 34
A 11 25’Phone #:’
A PHONE_NUM 8A I 11 34
A 23 34’F3 - EXIT’
A DSPATR(HI)
2. To create the program T1520ID1 using the program source shown below, type:
CRTBNDC PGM(MYLIB/T1520ID1) SRCFILE(QCLE/QACSRC)
typedef struct{
char in03;
char name[11];
char address[20];
char phone_num[8];
}info;
Figure 98. T1520ID1 — ILE C Source to Specify Indicators as Part of the File Buffer (Part 1
of 2)
Figure 98. T1520ID1 — ILE C Source to Specify Indicators as Part of the File Buffer (Part 2
of 2)
This program uses a response indicator IND_ON ’1’ to inform the ILE C
program T1520ID1 that a user pressed F3.
3. To run the program T1520ID1, type:
CALL PGM(MYLIB/T1520ID1)
Example
2. To create the program T1520ID2 using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520ID2) SRCFILE(QCLE/QACSRC)
int main(void)
{
_RFILE *fp;
_RIOFB_T *rfb;
info phone_list;
_SYSindara indicator_area;
if (( fp = _Ropen ( "*LIBL/T1520DD0", "ar+ indicators=y" )) == NULL )
{
printf ( "display file open failed\n" );
exit ( 1 );
}
_Rindara ( fp, indicator_area );
_Rformat ( fp, "PHONE" );
rfb = _Rwrite ( fp, "", 0 );
rfb = _Rreadn ( fp, &phone_list, sizeof(phone_list), __DFT );
if ( indicator_area[F3] == IND_ON )
{
printf ( "user pressed F3\n" );
}
_Rclose ( fp );
}
Figure 100. T1520ID2 — ILE C Source to Specify Indicators in a Separate Indicator Area
PHONE BOOK
Name:
Address:
Phone #:
F3 - EXIT
Example
Note: To run this example you must use a display device that is defined on your
system in place of DEVICE2.
1. To create the display file T1520DDD using the DDS shown below, type:
CRTDSPF FILE(MYLIB/T1520DDD) SRCFILE(QCLE/QADDSSRC) MAXDEV(2)
A DSPSIZ(24 80 *DS3)
A R EXAMPLE
A OUTPUT 5A O 5 20
A INPUT 20A I 7 20
A 5 10’OUTPUT:’
A 7 10’INPUT:’
2. To override the file STDOUT with the printer file QPRINT, type:
OVRPRTF FILE(STDOUT) TOFILE(QPRINT)
3. To create the program T1520DEV using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520DEV) SRCFILE(QCLE/QACSRC)
#include <stdio.h>
#include <recio.h>
#include <signal.h>
#include <stdlib.h>
int main(void)
{
_RFILE *fp;
_RIOFB_T *rfb;
char buf[21];
rfb = _Rreadn ( fp, buf, 20, __DFT ); /* Read from the default */
/* program device. */
OUTPUT: Hello
INPUT: __________________
Example
The following example illustrates how to change the default program device using
the _Rpgmdev() function.
Note: To run this example you must use two display devices that are defined on
your system in place of DEVICE1 and DEVICE2.
1. To create the display file T1520DDE using the DDS shown below, type:
CRTDSPF FILE(MYLIB/T1520DDE) SRCFILE(QCLE/QADDSSRC) MAXDEV(2)
A DSPSIZ(24 80 *DS3)
A INVITE
A R FORMAT1
A 9 13’Name:’
A NAME 20A I 9 20
A 11 10’Address:’
A ADDRESS 25A I 11 20
A R FORMAT2
A 9 13’Name:’
A NAME 8A I 9 20
A 11 10’Password:’
A PASSWORD 10A I 11 20
Figure 103. T1520DDE — DDS Source for Name and Password Display
2. To override the file STDOUT with the printer file QPRINT, type:
OVRPRTF FILE(STDOUT) TOFILE(QPRINT)
3. To create the program T1520CDV using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520CDV) SRCFILE(QCLE/QACSRC)
#include <stdio.h>
#include <recio.h>
#include <string.h>
#include <stdlib.h>
typedef struct{
char name[20];
char address[25];
}format1;
typedef struct{
char name[8];
char password[10];
}format2 ;
typedef union{
format1 fmt1;
format2 fmt2;
}formats ;
Figure 104. T1520CDV — ILE C Source to Change the Default Device (Part 1 of 2)
io_error_check(rfb);
iofb = _Riofbk ( fp );
if ( !strncmp ( "FORMAT1 ", iofb -> rec_format, 10 ))
{
_Rrelease ( fp, "DEVICE1" );
}
else
{
_Rrelease(fp, "DEVICE2" );
}
return(0);
}
Figure 104. T1520CDV — ILE C Source to Change the Default Device (Part 2 of 2)
When the application is run, a different display appears on each device. Data
may be entered on both displays, but the data that is first entered is returned to
the program. The output from the program is in QPRINT. For example, if the
name SMITH and the address 10 MAIN ST is entered on DEVICE1 before any
data is entered on DEVICE2, then the file QPRINT contains:
Data displayed on DEVICE1 is SMITH 10 MAIN ST
Note: There are two record formats that are created in the above example. One has
a size of 45 characters (fmt1), and the other a size of 18 characters (fmt2).
The union buf contains two record format declarations.
Example
A DSPSIZ(24 80 *DS3)
A R EXAMPLE
A OUTPUT 5A O 5 20
A INPUT 20A I 7 20
A 5 10’OUTPUT:’
A 7 10’INPUT:’
2. To override the file STDOUT with the printer file QPRINT, type:
OVRPRTF FILE(STDOUT) TOFILE(QPRINT)
3. To create the program T1520FBK using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520FBK) SRCFILE(QCLE/QACSRC)
io_feedbk = _Riofbk ( fp );
dsp_io_feedbk = (_XXIOFB_DSP_ICF_T *)( (char *)(io_feedbk) +
io_feedbk->file_dep_fb_offset );
printf ( "Acquire failed\n" );
printf ( "Major code: %2.2s\tMinor code: %2.2s\n",
dsp_io_feedbk->major_ret_code,dsp_io_feedbk->minor_ret_code );
exit ( 1 );
}
int main(void)
{
char buf[20];
_RIOFB_T *rfb;
_Rclose ( fp );
return(0);
}
OUTPUT: Hello
INPUT:
Using Subfiles
You can use subfiles to read or write a number of records to and from a display in
one operation.
Example
The following subfile example uses DDS from T1520DDG and T1520DDH to
display a list of names and telephone numbers.
1. To create the display file T1520DDG using the DDS source shown below, type:
CRTDSPF FILE(MYLIB/T1520DDG) SRCFILE(QCLE/QADDSSRC)
A DSPSIZ(24 80 *DS3)
A R SFL SFL
A NAME 10A B 10 25
A PHONE 10A B +5
A R SFLCTL SFLCTL(SFL)
A SFLPAG(5)
A SFLSIZ(26)
A SFLDSP
A SFLDSPCTL
A 22 25’<PAGE DOWN> FOR NEXT PAGE’
A 23 25’<PAGE UP> FOR PREVIOUS PAGE’
typedef struct{
char name[LEN];
char phone[LEN];
}pf_t;
#define RECLEN sizeof(pf_t)
int main(void)
{
_RFILE *pf;
_RFILE *subf;
/* Open the subfile and the physical file. */
if ((pf = _Ropen(PFILENAME, "rr")) == NULL)
{
printf("can’t open file %s\n", PFILENAME);
exit(1);
}
if ((subf = _Ropen(SUBFILENAME, "ar+")) == NULL)
{
printf("can’t open file %s\n", SUBFILENAME);
exit(2);
}
/* Initialize the subfile with records from the physical file. */
init_subfile(pf, subf);
This program uses _Ropen() to open subfile T1520DDG and physical file
T1520DDH. The subfile is then initialized with records from the physical file.
Subfile records are written to the display using the _Rwrited() function.
5. To run the program T1520SUB and see the output, type:
CALL PGM(MYLIB/T1520SUB)
David 435-5634
Florence 343-4537
Irene 255-5235
Carrie 747-5347
Michele 643-4557
v rb v wb and ab v ab+
Note: The only way to create an ICF file is to use the CRTICFF command. If you
use the fopen() function and the ICF file does not exist, a physical database
file is created.
v type v indicators
You can change the default program device in the following ways:
v Use the _Racquire() function to explicitly acquire another program device. The
device that is just acquired becomes the current program device.
v Use the _Rpgmdev() function to change the current program device associated
with a file to a previously-acquired device. This program device can be used for
subsequent input and output operations to the file.
v The actual program device read becomes the default device if you read from an
invited device using the _Rreadindv() function.
v Use the _Rrelease() function to release a device from the file. When you release
the device, it is no longer available for I/O operations.
To release a program device, use the _Rrelease() function (the program device
must have been previously acquired). This detaches the device from an open file;
I/O operations can no longer be performed for this device. If you wish to use the
device after releasing it, it must be acquired again.
All program devices are implicitly released when you close the file. If the device
file has a shared open data path, the last close operation releases the program
device.
Example
The following example gets a user ID and password from a source program and
sends it to a target program. The target program checks the user ID and password
for errors and sends a response to the source program.
Note: To run this example the target program T1520TGT must exist on a remote
system. A communications line between the source system with program
T1520ICF and the target system with program T1520TGT must be active.
You also need Advanced Program to Program Communications (APPC).
1. To create the physical file T1520DDA, type:
CRTPF FILE(MYLIB/T1520DDA) SRCFILE(QCLE/QADDSSRC)
A UNIQUE
A R PASSWRDF
A USERID 8A
A PASSWRD 10A
A K USERID
2. To create the ICF file T1520DDB using the DDS source shown below:, type:
CRTICFF FILE(MYLIB/T1520DDB) SRCFILE(QCLE/QADDSSRC)
ACQPGMDEV(CAPPC2)
A R SNDPASS
A FLD1 18A
A R CHKPASS
A FLD1 1A
A R EVOKPGM
A EVOKE(MYLIB/T1520TGT)
A SECURITY(2 ’PASSWRD’ +
A 3 ’USRID’)
3. To create the ICF file T1520DDC using the DDS source shown below, type:
CRTICFF FILE(MYLIB/T1520DDC) SRCFILE(QCLE/QADDSSRC) ACQPGMDEV(CAPPC1)
_RFILE *fp;
void ioCheck(char *majorRc)
{
if ( memcmp(majorRc, "00", 2) != 0 )
{
printf("Fatal I/O error occurred, program ends\n");
_Rclose(fp);
exit(1);
}
}
int main(void)
{
_RIOFB_T *fb;
char idPass[RCD_SIZE];
char buf[RCD_SIZE + 1];
char passwordCheck=ERROR;
Figure 112. T1520ICF — ILE C Source to Send and Receive Data (Part 1 of 2)
if ( passwordCheck == ERROR )
{
_Rclose(fp);
exit(3);
}
else if ( passwordCheck == VALID )
{
printf("Password valid\n");
}
else
{
printf("Password invalid\n");
}
_Rclose(fp);
return(0);
}
Figure 112. T1520ICF — ILE C Source to Send and Receive Data (Part 2 of 2)
The _Ropen() function opens the record file T1520DDB. The _Rformat()
function accesses the record format EVOKPGM in the file T1520DDB. The
EVOKE statement in T1520DDB calls the target program T1520TGT. The
_Rformat() function accesses the record format SNDPASS in the file
T1520DDB. The user ID and password is sent to the target program
#include <stdio.h>
#include <recio.h>
#include <string.h>
#include <stdlib.h>
#define ID_SIZE 8
#define PASSWD_SIZE 10
#define RCD_SIZE ID_SIZE + PASSWD_SIZE
#define ERROR ’2’
#define VALID ’1’
#define INVALID ’0’
int main(void)
{
_RFILE *icff;
_RFILE *pswd;
_RIOFB_T *fb;
char rcv[RCD_SIZE];
char pwrd[RCD_SIZE];
char vry;
_Racquire(icff, "DEV1");
_Rformat(icff, "RCVPASS");
fb = _Rreadn(icff, &rcv, RCD_SIZE, __DFT);
Figure 113. T1520TGT — ILE C Source to Check Data is Sent and Returned (Part 1 of 2)
if ( memcmp(fb->sysparm->_Maj_Min.major_rc, "00", 2) != 0 )
{
vry = ERROR;
}
else
{
fb = _Rreadk(pswd, &pwrd, RCD_SIZE, __DFT, &rcv, ID_SIZE);
Figure 113. T1520TGT — ILE C Source to Check Data is Sent and Returned (Part 2 of 2)
The _Ropen() function opens the file T1520DDC. The _Ropen() function opens
the password file T1520DDA. The _Rformat() function accesses the record
format RCVPASS in the file T1520DDC. The _Rreadn() function reads the
password and user ID from the source program T1520ICF. Errors are checked,
and a response is sent to the source program T1520ICF.
10. To run the program T1520ICF, type:
CALL PGM(MYLIB/T1520ICF)
After calling the program, you may enter a user ID and password. If the
password is correct, ″Password valid″ appears on the display; if it is incorrect,
″Password invalid″ appears.
Program-described files allow first character forms control (FCFC). To use this,
include the first character forms control code in the first position of each data
record in the printer file. You must use a printer stream file and the fwrite()
function.
v wb v ab
Note: The only way to create a printer file is to use the CRTPRTF command. If
you use the fopen() function and the printer file does not exist, a physical
database file is created.
v wr v ar
Example
int main(void)
{
FILE *dbf;
FILE *prtf;
char buf [BUF_SIZE];
char tmpbuf [BUF_SIZE];
/* Open the printer file using the first character forms control. */
/* recfm and lrecl are required. */
prtf = fopen ("*LIBL/T1520FCP", "wb type=record recfm=fa lrecl=53" );
dbf = fopen ("*LIBL/T1520FCI", "rb type=record blksize=0" );
Figure 114. T1520FCF — ILE C Source to Use First Character Forms Control (Part 1 of 2)
Figure 114. T1520FCF — ILE C Source to Use First Character Forms Control (Part 2 of 2)
The fopen() function opens the printer stream file T1520FCP using record at a
time processing. The fopen() function also opens the physical file T1520FCI for
record at a time processing. The strncpy() function copies the records into the
print buffer. The fwrite() function prints out the employee records.
5. To run the program T1520FCF, type:
CALL PGM(MYLIB/T1520FCF)
EMPLOYEE INFORMATION
--------------------
NAME SERIAL NUMBER
---- -------------
Jim Roberts 1234567890
Karen Smith 2314563567
John Doe 5646357324
EMPLOYEE INFORMATION
--------------------
v rb v wb v ab
Note: The only way to create a tape file is to use the CRTTAPF command. If you
use the fopen() function and the tape file does not exist, a physical database
file is created.
Note: The value you specify on the blksize parameter for the fopen() function
overrides the one you specified on the CRTTAPF or CHGTAPF commands.
You can still override the BLKLEN parameter with the OVRTAPF command.
If you specify 0 on either BLKLEN or blksize the system calculates a block size for
you. You can specify a value on either parameter of between 0 and 32 767
characters.
v rr v wr v ar
Blocking Record Tape Files: If your program processes tape files, performance
can be improved if I/O operations are blocked. To block records, use the blkrcd=Y
keyword on the _Ropen() function.
Example
The CRTSRCPF command creates the physical file QCSRC with member
CSOURCE in MYLIB. The following statements are copied to the tape file:
3. To create the program T1520TAP using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520TAP) SRCFILE(QCLE/QACSRC)
#include <stdio.h>
#include <string.h>
#include <recio.h>
#include <stdlib.h>
#define RECLEN 80
int main(void)
{
_RFILE *tape;
_RFILE *fp;
char buf [92];
int i;
_Rclose ( fp );
_Rclose ( tape );
return(0);
}
This program opens the source physical file T1520TPF. The _Ropen() function
file QCSRC contains the member CSOURCE with the source statements. The
_Ropen() function opens the tape file T1520TPF to receive the C source
statements. The _Rreadn() function reads the C source statements, finds their
sizes, and adds them to the tape file T1520TPF.
4. To run the program T1520TAP, type:
CALL PGM(MYLIB/T1520TAP)
A diskette file is a device file that is used for a diskette unit. The object type is
*FILE. The Tape and Diskette Device Programming manual contains information on
diskette files.
The concept of clearing a file or opening a file using append mode does not apply
to diskette files.
The diskette file label name is required when the file is opened. You specify this
label name using the Override Diskette File (OVRDKTF) command.
Note: Output may not always result in an I/O operation to a diskette file. The I/O
buffer must contain enough data to fill an entire track on a diskette.
When opening a diskette file for output, any files existing on the diskette are
deleted if the data file expiration date is less than or equal to the system
date.
v rb v wb
Note: The only way to create a diskette file is to use the CRTDKTF command. If
you use fopen() and the diskette file does not exist, a physical database file
is created.
v rr v wr
Read and Write Record Diskette Files: If you read from a diskette file, the next
sequential record in the diskette file is processed. Use the _Rreadn() function for
reading diskette files and the _Rwrite() function for writing to diskette files.
Example
To create the physical file T1520DDI using the following DDS source:
A R CUST
A NAME 20A
A AGE 3B
A DENTAL 6B
4. To select only records that have a value greater than 400 in the DENTAL field,
type:
OPNQRYF FILE((MYLIB/T1520DDI)) QRYSLT(’DENTAL *GT 400’) OPNSCOPE(*JOB)
5. To create the program T1520DSK using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520DSK) SRCFILE(QCLE/QACSRC)
#include <stdio.h>
#include <stdlib.h>
#include <recio.h>
#define BUF_SIZE 30
int main(void)
{
_RFILE *dktf;
_RFILE *dbf;
char buf [BUF_SIZE];
Figure 117. T1520DSK — ILE C Source to Write Records to a Diskette File (Part 1 of 2)
Figure 117. T1520DSK — ILE C Source to Write Records to a Diskette File (Part 2 of 2)
The _Ropen() function opens the diskette file T1520DKF and the database file
T1520DDI. The _Rreadn() function reads all database records. The _Rwrite()
function copies all database records that have a value > 400 in the dental field
to the diskette file T1520DKF.
6. To run the program T1520DSK, type:
CALL PGM(MYLIB/T1520DSK)
After you run the program, the diskette file contains only the records that
satisfied the selection criteria.
v rb v wb v rb
v lrecl v type
v rr v wr v ar
RV3W101-0
For OPM programs, language specific error handling provides one or more
handling routines for each call stack entry. The system calls the appropriate routine
when an exception is sent to an OPM program.
Language-specific error handling in ILE C/C++ provides the same capabilities. ILE
C/C++, however, has additional types of exception handlers. These types of
handlers allow you to change the exception message to indicate that the exception
is handled and to bypass the language-specific error handling. The additional types
of handlers for ILE C/C++ are:
v Direct monitor handler
v Integrated Language Environment condition handler
The call message queue facilitates the sending and receiving of informational
messages and exception messages between calls on the call stack. A call appears on
A message queue exists for every call stack entry within each iSeries job. As soon
as a new entry appears in the call stack, the system creates a new call message
queue. In ILE C/C++, the name of the procedure identifies the call message queue.
If the procedure names are not unique, you can specify the module name, program
name, or service program as well. If you determine that your handler does not
recognize an exception message, the exception message can be percolated to the
next handler.
If the function check message is percolated to the control boundary, ILE considers
the application to have ended with an unexpected error. ILE defines a generic
failure exception message for all languages. This message is CEE9901 and ILE
sends this message to the caller of the control boundary.
The following are the only types of messages that are considered to be exception
messages
(*ESCAPE) Indicates an error that caused a program to end
abnormally.
(*STATUS) Describes the status of work that the program is in
the process of doing.
(*NOTIFY) Describes a condition that requires corrective action
or reply from calling program.
Function Check Describes an ending condition that the program
has not expected.
You can monitor all of these exception message types by using the #pragma
exception handler directive. 1
To process exceptions, the system uses a handle cursor and a resume cursor.
The handle cursor describes the location of the current exception handler. As the
system searches for an available exception handler, it moves the handle cursor to
the next handler in the exception handler list. The list may contain direct monitor
handlers, Integrated Language Environment condition handlers, and HLL-specific
handlers.
The resume cursor describes the point at which a program will resume after
handling an exception. This is initially set to the instruction following the suspend
point of the call stack entry that caused the exception.
1. See the Record Input and Output Error Macro to Exception Mapping table in the Run-Time Considerations chapter of the ILE C
for AS/400 Run-Time Library Reference. It provides a list of exception messages generated by the ILE C/C++ record I/O functions.
Note: For illustration, many of the examples refer to the main() function as a
control boundary. In actuality, the PEP is the control boundary for the
program, if the program is running in a *NEW activation group.
Call Stack
OPM
Program A
Send
Terminating
Exception
CEE9901
Activation Group
ILE
Procedure P1
ILE
Percolate
Procedure P2 Function
Check
ILE
Percolate
Unhandled Procedure P3
Exception Resume Point
ILE
Procedure P4
RV2W1043-1
Handling Exceptions
You can check the return value of a function, the errno value, the major/minor
return code to detect stream file errors, and the values in the _RIOFB_T structure
to detect record file errors.
Your program should check the function return value to verify that the function
has completed successfully.
Example
The following example illustrates how to check the return value of a function.
Figure 120. ILE C Source to Check for the Return Value of fopen()
Your program can use the strerror() and perror() functions to print the value of
errno. The strerror() function returns a pointer to an error message string that is
associated with errno. The perror() function prints a message to stderr. The
perror() and strerror() functions should be used immediately after a function is
called since subsequent calls might alter the errno value.
Note: Your program should always initialize errno to 0 (zero) before calling a
function because errno is not reset by any library functions. Check for the
value of errno immediately after calling the function that you want to check.
You should also initialize errno to zero after an error has occurred.
Example
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
void main(void)
{
FILE *fp;
errno = 0;
fp = fopen("Nofile", "r");
if ( errno != 0 ) {
perror("Error occurred while opening file.\n");
exit(1);
}
}
Figure 121. ILE C Source to Check the errno Value for fopen()
Note: Signal handlers that are registered for SIGIO are not called for exceptions
that are generated when processing stream files.
typedef struct {
unsigned char *key;
_Sys_Struct_T *sysparm;
unsigned long rrn;
long num_bytes;
short blk_count;
char blk_filled_by;
int dup_key : 1;
int icf_locate: 1;
int reserved1 : 6;
char reserved2[20];
} _RIOFB_T;
If your program processes display, ICF, or printer files as record files, you can
check the num_bytes field in the _RIOFB_T structure and the major/minor return
code fields in the sysparm area of the _RIOFB_T structure. If your program
processes database files as stream files, you can check the values in some fields in
the _RIOFB_T structure, which is defined in <recio.h>.
The sysparm field points to a structure that contains the major and minor return
code for display, ICF, or printer files. The definition of _Sys_Struct_T structure is
shown below:
typedef struct {
char major_rc[2];
char minor_rc[2];
} _Maj_Min_rc_T;
Example
A DSPSIZ(24 80 *DS3)
A INDARA
A R PHONE
A CF03(03 ’EXIT’)
A CF05(05 ’REFRESH’)
A 7 28’Name:’
A NAME 11A B 7 34
A 9 25’Address:’
A ADDRESS 20A B 9 34
A 11 25’Phone #:’
A PHONE_NUM 8A B 11 34
A 1 35’PHONE BOOK’
A DSPATR(HI)
A 16 19’<ENTER> : Saves changes’
A 17 21’f3 : Exits with changes saved’
A 18 21’f5 : Brings back original fiel-
A d values’
A 05 21 32’Screen refreshed’
A 05 DSPATR(HI)
2. To create the program T1520EHD using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520EHD) SRCFILE(QCLE/QACSRC)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <signal.h>
#include <recio.h>
/* M A I N L I N E */
int main(void)
{
FILE *dspf;
PHONE_LIST_T phone_inp_rec,
phone_out_rec = { "Smith, John",
"2711 Westsyde Rd. ",
"721-9729" };
errno = 0;
memset(indicator_area,IND_OFF,sizeof(indicator_area));
do
{
ret_code = fwrite(&phone_out_rec,1,sizeof(phone_out_rec),dspf);
error_check(); /* Write the records to the display file. */
ret_code = fread(&phone_inp_rec,1,sizeof(phone_inp_rec),dspf);
error_check(); /* Read the records from the display file. */
if (indicator_area[EXIT] == IND_ON)
phone_inp_rec = phone_out_rec;
}
while (indicator_area[REFRESH] == IND_ON);
_Rclose((_RFILE *)dspf);
}
ILE ILE
ILE Condition
Procedure P2 Handler
Exception Occurs Procedure P5
.
. Last in
First out
ILE
.
ILE
Procedure P3 ILE Condition
Handler
Procedure P6
ILE
HLL - Specific
Handler
Procedure P7
Standard Language
Default
RV2W1041-3
For portable code across multiple platforms, only the signal() function should be
used. ILE condition handlers should be used if a consistent mechanism for
handling exceptions across ILE enabled languages is required. If portability across
ILE-enabled platforms is a concern, then ILE condition handlers and the signal()
function can be used. Otherwise, all three types of handlers may be used.
Using the signal() function will always handle the exception implicitly (unless the
signal action is SIG_DFL, in which case it would percolate the exception); with
direct monitor handlers you either have to specify a control action that will
implicitly handle the exception (_CTLA_HANDLE, _CTLA_HANDLE_NO_MSG,
_CTLA _IGNORE, or _CTLA_IGNORE_NO_MSG), or you have to handle the
exception explicitly within the handler function (when the control action
_CTLA_INVOKE is specified), using either QMHCHGEM or an ILE condition
handling API.
The following example illustrates that if you do not want to change the state of a
signal handler when the signal function returns, then you must manage the state of
the signal handler explicitly.
#include <signal.h>
void f(void)
{
void (*old_state)(int);
/* Save old state of signal action */
old_state = signal(SIGALL,handlr);
/* Other code in your application */
/* Reset state of signal */
signal(SIGALL,old_state);
}
Unhandled Exceptions
If you do not handle an exception in the call stack entry that caused the exception,
it is percolated (moved) to the caller’s call message queue. If the caller does not
handle the exception then the message is percolated again. This continues until the
exception reaches a control boundary call stack entry, at which point the system
takes the default action for the unhandled exception.
If the message type is *STATUS the program resumes without logging the
exception. If the message type is *NOTIFY the default reply is sent. If the message
type is *ESCAPE then a function check is sent to the call stack entry that is pointed
to by the resume cursor. If the message is a function check then the call stack is
cancelled to the control boundary and CEE9901 is sent to the caller of the control
boundary (the resume cursor then points to the caller of the control boundary).
Example
The system fills in the structure prior to giving control to the label. 2 If the handler
is a function, the system passes a pointer to a structure of type
_INTRPT_Hndlr_Parms_T to the function. A pointer to the communications area is
available inside the structure.
The direct monitor handlers are scoped at compile time to the code between the
#pragma exception_handler directive and the #pragma disable_handler directive.
For example, the #pragma exception_handler directive is scoped to a block of code
independent of the program logic.
Exception Classes
Exception classes indicate the type of exception (for example, *ESCAPE, *NOTIFY,
*STATUS, function check) and, for machine exceptions, the low level type (for
example, pointer not valid, divide by zero). Direct monitor handlers monitor for
exceptions that are based on exception classes and message identifiers. The handler
will get control if the exception falls into one or more of the exception classes that
are specified on the #pragma exception_handler.
2. If the storage that is required for the exception handler parameter block exceeds the storage that is defined by com_area then the
remaining bytes are truncated.
All machine exceptions are mapped to the *ESCAPE type exception. To monitor for
machine exceptions you can either specify the machine exception class, or specify
all *ESCAPE exceptions. Macros for the iSeries machine exception classes are
defined in the ILE C/C++ include file <except.h>.
You can monitor for the exception class values for class1 and class2. The value of
class2 can only be one of _C2_MH_ESCAPE, _C2_MH_STATUS, _C2_MH_NOTIFY,
or _C2_MH_FUNCTION_CHECK as defined in the <except.h> include file.
The Run-Time Considerations section of the ILE C for AS/400 Run-Time Library
Reference contains a table of the exception classes.
Control Actions
The #pragma exception_handler directive allows you to specify a control action
that is to be taken during exception processing. The five control actions that can be
specified, as defined in the <except.h> header file, are:
_CTLA_INVOKE
This control action will cause the function that is named on the directive to
be called and will not handle the exception. The exception will remain
active and must be handled by using QMHCHGEM or one of the ILE
condition-handling APIs.
_CTLA_HANDLE
This control action will cause the function or label that is named on the
directive to get control and it will handle and log the exception implicitly.
The exception will no longer be active when the handler gets control.
_CTLA_HANDLE_NO_MSG
This control action is the same as _CTLA_HANDLE except that the
exception is NOT logged. The message reference key in the parameter
block that is passed to the handler will be zero.
_CTLA_IGNORE
This control action will handle and log the exception implicitly and will
not pass control to the handler function named on the directive; that is, the
function named will be ignored. The exception will no longer be active,
and processing will resume at the instruction immediately following the
instruction that caused the exception.
_CTLA_IGNORE_NO_MSG
This control action is the same as _CTLA_IGNORE except that *NOTIFY
messages will be logged.
#include <except.h>
#include <stdio.h>
void myhandler(void) {
printf("In handler - something’s wrong!\n");
return;
}
int main(void) {
int *ip;
volatile int com_area;
#pragma exception_handler(myhandler, com_area, 0, _C2_ALL, \
_CTLA_IGNORE)
*ip = 5;
printf("Passed the exception.\n");
}
To specify message identifiers on the directive, you have to specify a control action
to be taken. The class of the exception must be in one of the classes specified on
the directive. The following example shows a #pragma exception_handler directive
that enables a monitor for a single specific message, MCH3601:
#pragma exception_handler (myhandler, com_area, 0, _C2_ALL, \
_CTLA_HANDLE, "MCH3601")
The ability to specify generic message identifiers can be used to simplify the
directive in the previous example. In the example that follows, a monitor is
enabled for any exception whose identifier begins with ″MCH12″:
#pragma exception_handler (myhandler, com_area, _C1_ALL, _C2_ALL, \
_CTLA_IGNORE, "MCH1200")
Examples
Figure 130. T1520XH1 — ILE C Source to Use Direct Monitor Handlers — main()
The following example shows the source for the service program HANDLERS:
#include <signal.h>
#include <stdio.h>
/* HANDLERS *SRVPGM (created with activation group *CALLER) */
void my_handler(_INTRPT_Hndlr_Parms_T * __ptr128 parms)
{
return;
}
void main_handler(_INTRPT_Hndlr_Parms_T * __ptr128 parms)
{
printf("In main_handler\n");
}
Figure 131. T1520XH2 — ILE C Source to Use Direct Monitor Handlers — Service Program
In the example, the procedure main() in MYPGM registers the direct monitor
handler main_handler followed by a call to fred() which registers the direct
monitor handler my_handler. The fred() function gets an exception which causes
my_handler to get control, followed by main_handler. The main() function is a
control boundary.
The following example illustrates direct monitor handlers using labels instead of
functions as the handlers:
Figure 132. T1520XH3 — ILE C Source to Use Direct Monitors with Labels as Handlers
The following example shows you how to use the #pragma exception_handler and
the signal() function together. This example also shows how an exception is
handled using SIGIO. An end-of-file message is mapped to SIGIO. The default for
SIGIO is SIG_IGN. It also shows that when both a HLL-specific handler and direct
monitor handler are defined, the direct monitor handler is called first.
1. To create the program T1520ICA, using the following source, type:
CRTBNDC PGM(MYLIB/T1520ICA) SRCFILE(QCLE/QACSRC)
Figure 133. T1520ICA — ILE C Source to Use Direct Monitor Handlers (Part 1 of 4)
Figure 133. T1520ICA — ILE C Source to Use Direct Monitor Handlers (Part 2 of 4)
Figure 133. T1520ICA — ILE C Source to Use Direct Monitor Handlers (Part 3 of 4)
Figure 133. T1520ICA — ILE C Source to Use Direct Monitor Handlers (Part 4 of 4)
Note: If a nested exception causes the program to end, the exception handler for
the first exception may not complete.
Example
#include <signal.h>
void hdlr_hdlr(_INTRPT_Hndlr_Parms_T * __ptr128 parms)
{
/* Handle exception 2 using QMHCHGEM. */
}
void main_hdlr(_INTRPT_Hndlr_Parms_T * __ptr128 parms)
{
#pragma exception_handler(hdlr_hdlr,0,0,_C2_MH_ESCAPE)
/* Generate exception 2. */
/* Handle exception 1 using QMHCHGEM. */
}
int main(void)
{
#pragma exception_handler(main_hdlr,0,0,_C2_MH_ESCAPE)
/* Generate exception 1. */
}
As this example illustrates, you can get an exception within an exception handler.
To prevent exception recursion, exception handler call stack entries act like control
boundaries with regards to exception percolation. Therefore it is recommended that
you monitor for exceptions within your exception handlers.
A cancel handler may be enabled around a body of code inside a function. When a
cancel handler is enabled it only gets control if the suspend point of the call stack
entry is inside that code (within the #pragma cancel_handler and #pragma
disable_handler directives), and the call stack entry is canceled.
Cancel handlers provide an important function by allowing you to get control for
clean-up and recovery actions when call stack entries are ended by something
other than a normal return.
On the #pragma cancel_handler directive, the name of the cancel handler routine (a
bound ILE procedure) is specified, along with a user-defined communications area.
It is through the communications area that information is passed from the
application to the handler function. When the cancel handler function is called, it is
passed a pointer to a structure of type _CNL_Hndlr_Parms_T which is defined in
the <except.h> header file. This structure contains a pointer to the communications
area in addition to some other useful information that is passed by the system.
This additional information includes the reason why the call was cancelled.
Example
The following simple example illustrates the use of the ILE Cancel Handler
mechanism. This capability allows an application (program) the opportunity to
have a user-provided function called to perform things such as error
reporting/logging, closing of files, etc. when a particular function invocation is
cancelled. The usual ways that cause cancelation to occur are: using the exit()
function or the abort() function, using the longjmp() function to jump to an
earlier call and having a CEE9901 Function Check generated from an unhandled
exception.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <except.h>
/*-------------------------------------------------------------------*/
/* The following function is called a "cancel handler". It is */
/* registered for a particular invocation (function) with the */
/* #pragma cancel_handler directive. The variable identified */
/* on this directive as the "communications area" can be accessed */
/* using the ’Com_Area’ member of the _CNL_Hndlr_Parms_T structure. */
/* */
/*-------------------------------------------------------------------*/
void CancelHandlerForReport( _CNL_Hndlr_Parms_T *cancel_info ) {
printf("In Cancel Handler for function ’Report’ ...\n");
/* Changing the value in the communications area will update the */
/* ’return_code’ variable in the invocation being cancelled */
/* (in function ’Report’ in this example). Note that the */
/* ILE C compiler will issue a warning for the following */
/* statement since it uses a non-ANSI C compliant technique. */
/* However, this will not affect the expected run-time behavior. */
/* Set "return_code" in Report to an arbitrary number. */
*( (volatile unsigned *)cancel_info->Com_Area ) = 500;
printf("Communication Area now has the value: %d \n",
*( (volatile unsigned *)cancel_info->Com_Area) );
printf("Leaving Cancel Handler for function ’Report’...\n");
}
Condition handlers are exception handlers that are registered at run time by using
the Register ILE Condition Handler (CEEHDLR) bindable API. They are used to
handle, percolate or promote exceptions. The exceptions are presented to the
condition handlers in the form of an ILE condition.
Use the ILE bindable API CEEHDLR if you want to have a consistent mechanism
of condition handling across several ILE languages; or for scoping exception
handling to a call stack entry. CEEHDLR is scoped to the function that calls it,
unlike the signal handler which is scoped to the activation group.
The ILE condition handler uses ILE conditions to allow greater cross-system
consistency. An ILE condition is a system-independent representation of an error
condition in an HLL.
Figure 136. T1520XH5 — ILE C Source to Use ILE Condition Handlers — main()
The following example shows the source for the service program HANDLERS:
#include <signal.h>
#include <stdio.h>
#include <lecond.h>
/* HANDLERS *SRVPGM (*CALLER) */
void my_handler(_FEEDBACK *cond, _POINTER *token, _INT4 *rc, _FEEDBACK *new)
{
return;
}
void main_handler(_FEEDBACK *cond, _POINTER *token, _INT4 *rc, _FEEDBACK *new)
{
printf("In main_handler\n");
}
Figure 137. T1520XH6 — ILE C Source to Use ILE Condition Handlers — Service Program
In the example, the procedure main() in MYPGM registers the condition handler
main_handler followed by a call to the function fred() which registers the
condition handler my_handler. Function fred() gets an exception causing
my_handler to get control, followed by main_handler. The main() function is a
control boundary. The exception is considered unhandled, so a function check is
sent to function fred(). Handlers my_handler and main_handler are called again,
this time for the function check. Neither of them handle the function check, so the
program ends abnormally and CEE9901 is sent to the caller of the main() function.
2. To run the program T1520IC6 and receive the output shown below, type:
CALL PGM(MYLIB/T1520IC6)
condition was raised: Facility_ID = MCH, MsgNo = 0x1211
The condition was handled.
Press ENTER to end terminal session.
The next example shows the use of condition handlers. Using bindable API
CEEHDLR, main_hdlr is registered in function main(), and fred_hdlr is registered
in function fred(). An MCH1211 (divide by zero) exception occurs. Handler
fred_hdlr is called to test if the exception is an MCH1211. The result code in the
condition handler is set to percolate to the next condition handler. Handler
fred_hdlr returns without handling the exception, causing main_hdlr to be called.
The user-supplied token is updated to the value ’1’ and the result code is set to
handle the exception. Handler main_hdlr returns, and the exception is handled.
Control resumes in fred() following the statement that caused the divide by zero.
1. To create the program T1520IC7 using the following source, type:
CRTBNDC PGM(MYLIB/T1520IC7) SRCFILE(QCLE/QACSRC)
int main(void)
{
_HDLR_ENTRY hdlr = main_hdlr;
_FEEDBACK fc;
volatile _INT4 token=0, *tokenp = &token;
/* Register the handler with a token of type _INT4. */
CEEHDLR(&hdlr, (_POINTER *)&tokenp, &fc);
Figure 139. T1520IC7 — ILE C Source to Percolate a Message to Handle a Condition (Part 1
of 2)
Figure 139. T1520IC7 — ILE C Source to Percolate a Message to Handle a Condition (Part 2
of 2)
2. To run the program T1520IC7 and receive the output shown below, type:
CALL PGM(MYLIB/T1520IC7)
in fred_hdlr, percolate exception.
in main hdlr: Facility_ID = MCH, MsgNo = 0x1211
Resume here because resume cursor not moved and main_hdlr handled the exception.
A condition was percolated from fred() to main() and was then handled.
Press ENTER to end terminal session.
Figure 140. T1520IC8 — ILE C Source to Promote a Message to Handle a Condition (Part 1
of 3)
Figure 140. T1520IC8 — ILE C Source to Promote a Message to Handle a Condition (Part 2
of 3)
Figure 140. T1520IC8 — ILE C Source to Promote a Message to Handle a Condition (Part 3
of 3)
2. To run the program T1520IC8 and receive the output shown below, type:
CALL PGM(MYLIB/T1520IC8)
in fred_hdlr: moving resumes. Facility_ID = MCH, MsgNo = 0x1211
promoting condition....
A condition was promoted from MCH1211 to CEE9902 by fred() and was handled by
the condition handler enabled in main().
Press ENTER to end terminal session.
iSeries system exceptions are mapped to C and C++ signals by the ILE C/C++
run-time environment. A signal handler determines the course of action for a
signal. You cannot register a signal handler in an activation group that is different
from the one you wish to call it from. If a signal handler is in a different activation
group from the occurrence of the signal it is handling, the behavior is undefined.
Signals are raised implicitly or explicitly. To explicitly raise a signal, use the
raise() function. Signals are implicitly raised by the iSeries system when an
exception occurs. For example, if you call a program that does not exist, an implicit
signal is raised indicating that the program object could not be found.
The header file <signal.h> contains a number of function prototypes that are
associated with signal handling.
The following functions can be used with signal handling in your program:
The signal() function specifies the action that is performed when a signal is
raised. There are ten signals that are represented as macros in the <signal.h>
header file. In addition, the macro SIGALL has the semantics of a signal but with
some unique characteristics. The ten signals are as follows:
SIGABRT Abnormal program end.
SIGFPE Arithmetic operation error, such as dividing by zero.
SIGILL An instruction that is not allowed.
SIGINT System interrupt, such as receiving an interactive attention signal.
SIGIO Record file error condition.
SIGOTHER All other *ESCAPE and *STATUS messages that do not map to any
other signals.
SIGSEGV The access to storage is not valid.
SIGTERM A end request is sent to the program.
SIGUSR1 Reserved for user-defined signal handler.
SIGUSR2 Reserved for user-defined signal handler.
SIG_IGN and SIG_DFL are signal actions that are also included in the <signal.h>
header file.
SIG_IGN Ignore the signal.
SIG_DFL Default action for the signal.
SIGALL is an ILE C/C++ extension that allows you to register your own
default-handling function for all signals whose action is SIG_DFL. This
default-handling function can be registered by using the signal() function with
SIGALL, as shown in the example section. A function check is not a signal and
cannot be monitored for by the signal function. SIGALL cannot be signaled by the
raise() function.
When a signal is received, the ILE C/C++ run-time environment handles the signal
in one of three ways:
v If the value of the function is SIG_IGN, then the signal is ignored, because the
exception is handled by the run-time environment and no signal handler is
called. If the message that is mapped to the signal is an *ESCAPE or *NOTIFY
message, then it is placed in the job log.
v If the value of the function is a pointer to a function, then the function that is
addressed by the pointer is called.
v If the value of the function is SIG_DFL, then the system uses the value
registered for SIGALL (choosing one of the three ways described here). If the
value of the function for SIGALL is SIG_DFL then the exception is percolated.
Note: The value of the function is the function argument on the call to the
signal() function.
The default can be reset to SIG_IGN, another handler, or the same handler. You
can recursively call the signal handler. Once stacked, multiple signal handler
calls behave like any other calls. For example, if the action signal to the previous
caller is chosen, the control will not be returned to the preceding caller (even if
that call is another signal handler) but goes back to the previous caller.
v The signal() function returns the address of the previous signal handler for the
specified signal, and sets the address of the new signal handler. You can stack
the signal handlers yourself using the value returned by signal(). For example,
Example
The following example shows how to set up a signal handler. The example
illustrates that when there is no signal handler set up the default action for SIGIO
is SIG_IGN. The exception is ignored. When a signal handler is set up for SIGIO,
the signal handler is called.
1. To create the program T1520SIG, using the following source, type:
CRTBNDC PGM(MYLIB/T1520SIG) SRCFILE(QCLE/QACSRC)
2. To run the program T1520SIG and receive the output shown below, type:
CALL PGM(MYLIB/T1520SIG)
The example in Figure 144 on page 293 shows how a control boundary is defined
in a simple ILE C/C++ application.
int main() {
g();
}
void g(void) {
f();
}
Figure 144. Example of Control Boundary When There is One Activation Group
This application runs in a single *NEW activation group (also called a system
named activation group). The application consists of an ILE C program called
TEST which is bound by reference to a service program SRV1. The service program
SRV1 was created with the ACTGRP(*CALLER) option.
If an escape exception occurs in function f() and no handlers are enabled, then the
exception percolates from the call stack entry for f() to the call stack entry for g(),
and then to the call stack entry for main(). Since main() is the control boundary,
and the exception is unhandled, it will be turned into a function check and be
re-driven. The function check starts at call stack entry f() (where the exception
occurred), and is percolated to the call stack entry for g(), and then to the call
stack entry for main(). At this point, the function check has reached a control
The example in Figure 145 shows how control boundaries are set when calls are
made between different activation groups.
ILE C program
int main(void) {
a();
}
void c(void) {
d();
void e(void) {
}
f();
}
void d(void) {
e();
void f(void) {...} }
Figure 145. Example of Control Boundaries When You Have Multiple Activation Groups
This application consists of one ILE C/C++ program and two ILE C/C++ Service
Programs (SRV1 and SRV2) that run in named activation groups (AG1 and AG2).
In this example, the functions main(), a(), b(), and e() are all potential control
boundaries. For example, when you are running in procedure c(), then b() would
be the nearest control boundary.
The call stack entry for main() is a control boundary since it is the first call stack
entry in the activation group. The call stack entries for a(), b(), and e() are control
boundaries since the immediately preceding call stack entry for each runs in a
different activation group.
If an escape exception occurs in function d() and no handlers are enabled, then the
exception percolates from the call stack entry for d() to the call stack entry for c(),
and then to the call stack entry for b(). Since the call stack entry b() is a control
If exit() was called from within function a(), then it will cancel up the call stack
entry for a(), control will return to the call stack entry for main(), and the
activation group AG1 will be ended. At this time, any atexit() routines for
activation group AG1 are called.
If exit() was called from within function f(), then it will cancel the call stack
entries until it reaches the control boundary at the call stack entry for e() (for
example, the call stack entries for both e() and f() are canceled). Since this control
boundary is not the first one in activation group AG1, the activation group will
NOT be taken down. Control will return to the call stack entry for function d().
Note: If an atexit() was registered by function f(), it would not be called, since
the activation group was not ended.
The example in Figure 146 on page 296 shows how control boundaries would be
set for an application that is running in the default activation group. The
application consists of two OPM programs A and C, and two ILE C programs B
and D which were created using the ACTGRP(*CALLER) option on the CRTPGM
command. In this example, the function main() in program B, and the function
main() in program D are potential control boundaries.
The call stack entries for main() in programs B and D are control boundaries since
the immediately preceding call stack entry for each is that of an OPM program.
Note: The activation group will not be taken down, since the default activation
group only goes away at the time the job ends.
CALL B
ILE C Program B
int main() {
g();
}
void g(void) {
C();
}
OPM Program C
CALL D
ILE C Program D
In ANSI C, a pointer type is derived from a function type, a data object type, or an
incomplete type. On the iSeries system, pointer types can also be derived from
other iSeries entities such as system objects (for example, programs), code labels,
and process objects. These pointer types are usually referred to as iSeries pointers
and they are used extensively in the ILE C Machine Interface Library and in the
ILE C/C++ exception handling structures and functions. The ILE C/C++ for AS/400
MI Library Reference contains information on ILE C Machine Interface Library
Functions.
These pointer types, as well as pointers to data objects and incomplete types, are
not data type (assignment or comparison) compatible with each other. For
example, a variable declared as a pointer to a data object cannot be assigned the
value of a function pointer or system pointer. A system pointer cannot be
compared for equality with an invocation pointer or pointer to a data object. The
above is not true for open pointers.
Note:
v Label pointers are only used by the setjmp macro.
v An open pointer is a pseudo-pointer type. It may contain any other
pointer type, but it is not a pointer type unto itself.
Note: Open pointers inhibit optimization. Use them only when absolutely
necessary.
These types are defined as pointers to void (void *), and the #pragma pointer
directives in the header file cause the ILE C compiler to associate these types with
the iSeries 400 pointer types.
Examples
The following example shows you how to declare a pointer to a bound procedure
(a function that is defined within the same ILE program object):
The following example shows you how to declare a pointer to an iSeries 400
program as a function pointer with OS-linkage. If the #pragma linkage OS directive
is omitted from the code, the ILE C compiler assumes that os_fct_ptr is a pointer to
a bound C function returning void, and will issue a compile error for incompatible
pointer types between os_fct_ptr and the system pointer returned by rslvsp()..
#include <miptrnam.h>
#include <stdio.h>
#pragma datamodel(p128)
typedef void (OS_fct_t) ( void );
#pragma linkage(OS_fct_t,OS)
#pragma datamodel(pop)
int main ( void )
{
OS_fct_t *os_fct_ptr;
char pgm_name[10];
printf("Enter the program name : \n");
scanf("%s", pgm_name);
/* Dynamic assignment of a system pointer to program "MYPGM" */
/* in *LIBL. The rslvsp MI library function will resolve to */
/* this program at runtime and return a system pointer to */
/* the program object. */
os_fct_ptr = rslvsp(_Program, pgm_name, "*LIBL", _AUTH_OBJ_MGMT);
os_fct_ptr(); /* OS-linkage *PGM call using a */
/* pointer. */
Figure 151. ILE C Source to Declare a Pointer to an iSeries 400 Program as a Function
Pointer
Figure 152. ILE C++ Source to Declare a Pointer to an iSeries Program as a Function
Pointer
#include <pointer.h>
#pragma datamodel(p128)
#pragma linkage(TESTPTR, OS)
#pragma datamodel(pop)
void TESTPTR(void); /* System pointer to this program */
_SYSPTR sysp; /* System pointer */
_OPENPTR opnp; /* open pointer */
void (*fp)(void); /* function pointer */
int i = 1; /* integer */
int *ip = &i; /* Space pointer */
void main (void) {
fp = &main; /* initialize function pointer */
sysp = &TESTPTR; /* initialize system pointer */
Figure 154. T1520DL8 — ILE C Source that Uses iSeries Pointers (Part 1 of 2)
#pragma datamodel(pop)
void T1520DL9 (PtrStructure *, _SPCPTR, _SYSPTR, void (*)());
void function1(void) /* A function definition. */
{
printf("Hello!\n");
}
int main(void)
{
int i = 4;
PtrStructure ptr_struct;
/* Make assignments to the fields of ptr_struct. */
ptr_struct.spcptr = (_SPCPTR)&i; /* A space pointer. */
ptr_struct.sysptr = (_SYSPTR)T1520DL9; /* A system pointer. */
ptr_struct.fnptr = &function1; /* A function pointer. */
Figure 154. T1520DL8 — ILE C Source that Uses iSeries Pointers (Part 2 of 2)
Figure 155. T1520DL9 — ILE C Source that Uses iSeries Pointers (Part 1 of 2)
openptr = (_OPENPTR)argv[3];
sysptr = openptr; /* A system pointer. */
openptr = (_OPENPTR)argv[4];
fnptr = openptr; /* A function pointer. */
if (spcptr != ptr_struct_ptr->spcptr)
++error_count;
if (sysptr != ptr_struct_ptr->sysptr)
++error_count;
if (fnptr != ptr_struct_ptr->fnptr)
++error_count;
if (error_count > 0)
printf("Pointers not passed correctly.\n");
else
printf("Pointers passed correctly.\n");
return;
}
Figure 155. T1520DL9 — ILE C Source that Uses iSeries Pointers (Part 2 of 2)
The ILE C compiler supports the packed decimal data type, as an extension to
ANSI C. This is strictly a C data type. C++ decimal support is provided in a class.
For more information, refer to ILE C/C++ Language Reference.
You can use the packed decimal data type to represent large numeric quantities
accurately, especially in business and commercial applications for financial
calculations. For example, the fractional part of a dollar can be represented
accurately by two digits that follow the decimal point. You do not have to use
floating point arithmetic which is more suitable for scientific and engineering
computations (which often use numbers that are much larger than the largest
packed decimal variable can store).
Note: To use the decimal, digitsof, and precisionof macros in your code you must
specify the <decimal.h> header file in your ILE C source.
If assignment causes truncation in the integral part, then there is a run-time error.
A run-time exception occurs when an integral value is lost during conversion to a
different type, regardless of what operation requires the conversion. See
“Understanding Packed Decimal Data Type Errors” on page 320 for an example of
run-time exceptions.
Examples
The following example shows conversion from one packed decimal type to another
with a smaller precision. Truncation on the fractional part results.
#include <decimal.h>
int main(void)
{
decimal(7,4) x = 123.4567D;
decimal(7,1) y;
y = x; /* y = 123.4D */
}
Figure 157. ILE C Source to Convert a Packed Decimal to a Packed Decimal with Smaller
Precision
The next example shows conversion from one packed decimal type to another with
a smaller integral part. Truncation on the integral part results. The #pragma
nosigtrunc directive turns off exceptions generated because of overflow.
#pragma nosigtrunc
#include <decimal.h>
int main (void)
{
decimal(8,2) x = 123456.78D;
decimal(5,2) y;
y = x; /* y = 456.78D */
}
Figure 158. ILE C Source to Convert a Packed Decimal to a Packed Decimal with Smaller
Integral Part
#pragma nosigtrunc
#include <decimal.h>
int main (void)
{
decimal(8,2) x = 123456.78D;
decimal(4,1) y;
y = x; /* y = 456.7D */
}
Figure 159. ILE C Source to Convert a Packed Decimal to a Packed Decimal with Smaller
Integral Part and Smaller Precision
Examples
The following example shows the conversion from a packed decimal type that has
a fractional part to an integer type.
#include <decimal.h>
int main (void)
{
int op;
decimal(7,2) op1 = 12345.67d;
op = op1; /* Truncation on the fractional */
/* part. op=12345 */
}
Figure 160. ILE C Source to Convert a Packed Decimal with a Fractional Part to an Integer
The following example shows the conversion from a packed decimal type that has
less than 10 digits in the integral part to an integer type.
#include <decimal.h>
int main(void)
{
int op;
decimal(3) op2=123d;
op = op2; /* No truncation and op=123 */
}
Figure 161. ILE C Source to Convert a Packed Decimal with Less than 10 Digits in the
Integral Part to an Integer
The following example shows the conversion from a packed decimal type that has
more than 10 digits in the integral part to an integer type.
Figure 162. ILE C Source to Convert a Packed Decimal with More than 10 Digits in the
Integral Part to an Integer
The following example shows conversion from a packed decimal type that has a
fractional part, and an integral part having more than 10 digits to an integer type.
#include <decimal.h>
int main (void)
{
int op;
long long op_2;
decimal(15,2) op_1 = 1234567890123.12d;
op = op_1; /* High-order bits will be truncated. */
op_2 = op_1; /* op_2 = 1234567890123, op = 0x71FB04CB */
}
Figure 163. ILE C Source to Convert a Packed Decimal with More than 10 Digits in Both
Parts to an Integer
The following example shows the conversion from a packed decimal type to a
floating point type.
#include <decimal.h>
#include <stdio.h>
int main(void)
{
decimal(5,2) dec_1=123.45d;
decimal(11,5) dec_2=-123456.12345d;
float f1,f2;
f1=dec_1;
f2=dec_2;
printf("f1=%f\nf2=%f\n\n",f1,f2); /* f1=123.449997 */
/* f2=-123456.125000 */
}
Overflow Behavior
The following table describes the overflow behavior when a packed decimal
number is assigned to a smaller target. An exception is not generated when:
v A packed decimal is assigned to a smaller target with integral type.
v A packed decimal is assigned to a smaller target of floating point type.
The following example shows how to pass packed decimal variables to a function.
#include <decimal.h>
#include <stdio.h>
decimal(3,1) d1 = 33.3d;
decimal(10,5) d2 = 55555.55555d;
decimal(28) d3 = 8888888888888888888888888888d;
void func1( decimal(3,1), decimal(10,5),
decimal(10,5), decimal(28));
Figure 165. ILE C Source to Pass Packed Decimal Variable to a Function (Part 1 of 2)
Figure 165. ILE C Source to Pass Packed Decimal Variable to a Function (Part 2 of 2)
int main(void)
{
/* Call function with pointer to packed decimal argument. The */
/* value that it returns is also a pointer to a packed decimal. */
if(func_1(p)!=p)
{
printf("Function call not successful\n\n");
}
else
{
printf("The packed decimal number is: %D(5,2)\n",*func_1(p));
}
}
decimal(5,2) *func_1(decimal(5,2) *q)
{
return q;
}
Figure 166. ILE C Source to Pass a Pointer to a Packed Decimal Value to a Function
Example
The following example shows an ILE C program that calls an OPM COBOL
program and then passes a packed decimal data.
#include<stdio.h>
#include <decimal.h>
void CBLPGM(decimal(9,7));
#pragma datamodel(p128)
#pragma linkage(CBLPGM,OS)
#pragma datamodel(pop)
int main(void)
{
decimal(9,7) arg=12.1234567d;
/* Call an OPM COBOL program and pass a packed */
/* decimal argument to it. */
CBLPGM(arg);
printf("The COBOL program was called and passed a packed decimal value\n");
}
Figure 167. ILE C Source for an ILE C Program that Passes Packed Decimal Data
The following example shows COBOL source for passing a packed decimal
variable to an ILE C program.
Figure 168. COBOL Source that Receives Packed Decimal Data from an ILE C Program
The COBOL program was called and passed a packed decimal value.
Press ENTER to end terminal session.
Examples
The following example shows you how to use the va_arg macro to accept a packed
decimal data of the form decimal(n,p). The va_arg macro returns the current
packed decimal argument.
num2=vargf(stream_2,fmt_2,arr_2[0],
arr_2[1],
arr_2[2],
arr_2[3],
arr_2[4],
arr_2[5]);
Figure 169. ILE C Source to Use the va_arg Macro with a Packed Decimal Data Type (Part 1
of 2)
Figure 169. ILE C Source to Use the va_arg Macro with a Packed Decimal Data Type (Part 2
of 2)
The following example shows you how to write packed decimal constants to a file,
and how to scan them back. In addition, the example shows you how to pass a
packed decimal array to a function.
Figure 170. ILE C Source to Write Packed Decimal Constants to a File and Scan Them Back
(Part 1 of 2)
Figure 170. ILE C Source to Write Packed Decimal Constants to a File and Scan Them Back
(Part 2 of 2)
The following example shows how to use the %D(*,*) specifier with the printf()
function. If n and p of the variable to be printed do not match with the n and p in
the conversion specifier %D(n,p), the behavior is undefined. Use the unary
operators digitsof (expression) and precisionof (expression) in the argument list to
replace the * in D(*,*) whenever the size of the resulting type of a packed decimal
expression is not known.
#include <decimal.h>
#include <stdio.h>
int main(void)
{
decimal(6,2) op_1=1234.12d;
decimal(10,2) op_2=-12345678.12d;
printf("op_1 = %*.*D(*,*)\n", 6, 2, digitsof(op_1),
precisionof(op_1), op_1);
printf("op_2 = %*.*D(*,*)\n", 10, 2, digitsof(op_2),
precisionof(op_2), op_2);
}
Example
The following example shows all the warnings and error conditions that are issued
by the ILE C compiler for packed decimal expressions.
a2 = 1234567891234567891234567.12345d + 12345.1234567891d;
/* a2 = (31,5) + (15,10) */
/* Generates an error for */
/* an expression requiring */
/* decimal point alignment.*/
a3 = 123456789123456.12345d * 1234567891234.12d;
/* a3 = (20,5) * (15,2) */
/* Generate a warning for */
/* multiplication in this */
/* expression. */
/* Note: Need (35,7) but */
/* use (31,2), for example,*/
/* keep the integral part. */
/* Run-time errors are */
/* generated. */
Note:
v For assignments to a smaller target field than can hold the packed
decimal number, there is no compile-time warning or error issued for
static, external or automatic initialization.
v For expressions requiring decimal point alignment, namely addition,
subtraction or comparison, there is a compile-time error issued for static,
external, and automatic initialization if during alignment, the maximum
number of allowed digits is exceeded.
v For multiplication, if the evaluation of the expression results in a value
larger than the maximum number of allowed digits:
– A compile-time error is issued for static or external initialization; no
module is created.
– A compile-time warning is issued if the expression is within a function;
a run-time exception is generated.
– Truncation occurs on the fractional part, preserving as much of the
integral part as possible.
v For division, a compile-time error is generated when ((n1- p1) + p2) >31.
The ILE C for AS/400 Language Reference contains information on the multiplication
and division operators for packed decimals.
Examples
You can use the #pragma nosigtrunc directive to suppress a run-time exception
that occurs as a result of overflow. The ILE C for AS/400 Language Reference contains
information on the #pragma nosigtrunc directive.
The following example shows how to suppress the run-time exception created
when a packed decimal variable overflows on assignment, in a function call, and
in an arithmetic operation.
Program processing within ILE occurs at the procedure level. ILE programs consist
of one or more modules which in turn consist of one or more procedures. C and
C++ modules may contain only one main() procedure, but can contain many other
procedures (functions). Other ILE languages, however, may allow only one
procedure. A dynamic program call is a special form of procedure call; it is a call
to the program entry procedure. A program entry procedure is the procedure
designated at program creation time to receive control when a program is called.
This is the same as calling another program’s main() function.
Calling Programs
When executing dynamic calls, the called program’s name is resolved to an
address at run time, just before the calling program passes control to the called
program for the first time program calls are dynamic calls.
You need to know the conventions to call programs. Table 15 shows program call
conventions.
Table 15. Program Calling Conventions
Action Program Call Convention
ILE C calling *PGM where #pragma linkage (PGMNAME, OS)
*PGM is
For example,
v ILE C
#pragma linkage (PGMNAME, OS)
v OPM COBOL for iSeries
void PGMNAME(void);
v OPM RPG for iSeries /* Other code */
v OPM CL /* Dynamic call to program PGMNAME */
v OPM BASIC PGMNAME();
v OPM PL/1
v EPM C
v EPM PASCAL
v EPM FORTRAN/400®
v ILE COBOL for iSeries
v ILE RPG for iSeries
v ILE CL
v C++
ILE C calling an EPM #pragma linkage (QPXXCALL, OS)
entry point
For example,
#include <xxenv.h>
/* The xxenv.h header file holds */
/* the prototype for QPXXCALL */
/* The #pragma linkage (QPXXCALL, OS) */
/* is in this header file. */
/* Other code. */
/* Dynamic call to program QPXXCALL. */
/* Dynamic call to EPM entry point using QPXXCALL: */
/* the name of the entry point is entname, envid */
/* names the user-controlled environment, the */
/* program and library name is given by envpgm, */
/* parm1 and parm2 are arguments passed to entname. */
QPXXCALL(entname, envid, &envpgm, parm1, parm1);
If you have an ILE C/C++ program calling a program (*PGM) use the #pragma
linkage (PGMNAME, OS) directive in your ILE C/C++ source to tell the compiler
that PGMNAME is an external program, not a bound Integrated Language
Environment procedure.
The value that is returned on the call is the return code for the program call. If
the program being called is an Integrated Language Environment program, this
return code can also be accessed using the _LANGUAGE_RETURN_CODE
macro defined in the header file <milib.h>. If the program being called is an
EPM or OPM program, this return code can be accessed using the Retrieve Job
Attributes (RTVJOBA) command.
If you have an ILE C program calling an EPM default entry point, then use the
#pragma linkage (PGMNAME, OS) directive in your ILE C source to tell the
compiler that PGMNAME is an external program, not a bound Integrated
Language Environment procedure. QPXXCALL can also be used to call EPM
default entry points.
If you have an ILE C program calling an EPM non-default entry point, you must
use the EPM API QPXXCALL. Since QPXXCALL is an OPM program, you must
use the #pragma linkage (QPXXCALL, OS) directive in your ILE C source.
When an ILE C program calls an EPM C program and the EPM program explicitly
raises a signal (through the raise() function), the ILE program does not monitor
for this signal as the EPM environment generates a diagnostic message as a result
of the *ESCAPE message generated by the raise function. Therefore, the ILE
program doesl not receive any function checks from an EPM program. However, if
an ILE C program calls an EPM C program which implicitly raises a signal (as a
result of an *ESCAPE message), the *ESCAPE message can be monitored for and
handled by the ILE program.
Example
User Input
OPM CL CMD
Program
T1520CM2
OPM CL
Program
T1520CL2
ILE C/400
Program
T1520IC5
Displays
Output OPM COBOL/400 OPM RPG/400
Program Program
T1520CB1 T1520RP1
Audit File
T1520DD2
In the following example the CL, COBOL, and RPG programs are activated within
the user default activation groups. A new activation group is started when the CL
Note: When a CRTPGM parameter does not appear in the CRTBNDC command,
the CRTPGM parameter default is used.
calc_and_format() write_audit_trail()
1. To create a physical file T1520DD2 using the source shown below, type:
CRTPF FILE(MYLIB/T1520DD2) SRCFILE(QCLE/QADDSSRC) MAXMBRS(*NOMAX)
This source file contains the audit trail for the ILE C program T1520IC5. The
DDS source defines the fields for the audit file.
This program passes the CL variables item name, price, quantity, and user ID
by reference to an ILE C program T1520IC5. The Retrieve Job Attributes
(RTVJOBA) command obtains the user ID for the audit trail. Arguments are
passed by reference. They can be changed by the receiving ILE C program. The
variable item_name is null ended in the CL program.
CL variables and numeric constants are not passed to an ILE C program with
null-ended strings. Character constants and logical literals are passed as
null-ended strings, but are not widened with blanks. Numeric constants such as
packed decimals are passed as 15,5 (8 bytes). Floating point constants are
passed as double precision floating point values, for example 1.2E+15.
3. To create a CL command prompt T1520CM2 using the source shown below,
type:
CRTCMD CMD(MYLIB/T1520CM2) PGM(MYLIB/T1520CL2) SRCFILE(QCLE/QACMDSRC)
You use this CL command prompt to enter the item name, price, and quantity
for the ILE C program T1520IC5.
4. To create the program T1520IC5 using the source shown below, type:
CRTBNDC PGM(MYLIB/T1520IC5) SRCFILE(QCLE/QACSRC) OUTPUT(*PRINT) FLAG(30)
OPTION(*EXPMAC *SHOWINC *NOLOGMSG) MSGLMT(10) CHECKOUT(*PARM) DBGVIEW(*ALL)
Figure 179. T1520IC5 — ILE C Source to Call COBOL AND RPG (Part 1 of 2)
char rpg_item_name[20];
char null_formatted_cost[22];
char success_flag = ’N’;
int i;
/* Call the COBOL for iSeries 400 program to do the calculation, and return a */
/* Y/N flag, and a formatted result. */
calc_and_format(price,
quantity,
taxrate,
formatted_cost,
&success_flag);
memcpy(null_formatted_cost,formatted_cost,sizeof(formatted_cost));
/* Remove null end for the RPG for iSeries 400 program. */
if (*(item_name+i) == ’\0’)
{
rpg_item_name[i] = ’ ’;
}
else
{
rpg_item_name[i] = *(item_name+i);
}
}
Figure 179. T1520IC5 — ILE C Source to Call COBOL AND RPG (Part 2 of 2)
332 ILE C/C++ Programmer’s Guide
The main() function in this program receives incoming arguments from CL
program T1520CL2 that are verified by CL command prompt T1520CM2 and
null ended within CL program T1520CL2. All incoming arguments are pointers.
The main() function also calls calc_and_format(), which is mapped to a
COBOL name. It passes by OS-linkage convention the price, quantity, taxrate,
formatted cost, and a success flag.
Because the OPM COBOL program is not expecting widened parameters (the
default for ILE C), nowiden is used in the #pragma linkage directive. The
formatted cost and the success flag values are updated in program T1520IC5.
The ILE Compiler by default converts a short integer to an integer unless the
nowiden parameter is specified on the #pragma linkage directive. For example,
the short integer in the ILE C program is converted to an integer, and then
passed to the OPM RPG program. The RPG program is expecting a 4 byte
integer for the quantity variable.
FLAG(30) specifies that you want severity level 30 messages to appear in the
listing. MSGLMT(10) specifies that you want compilation to stop after 10
messages at severity level 30. CHECKOUT(*PARM) shows a list of function
parameters that are not used. DBGVIEW(*ALL) specifies that you want all three
views and debug data to debug this program.
5. To create an OPM COBOL program using the source shown below, type:
CRTCBLPGM PGM(MYLIB/T1520CB1) SRCFILE(QCLE/QALBLSRC)
IDENTIFICATION DIVISION.
PROGRAM-ID. T1520CB1.
******************************************************
* parameters: *
* incoming: PRICE, QUANTITY *
* returns : TOTAL-COST (PRICE*QUANTITY*1.TAXRATE) *
* SUCCESS-FLAG. *
******************************************************
ENVIRONMENT DIVISION. 0
CONFIGURATION SECTION. 0
SOURCE-COMPUTER. AS-400. 0
OBJECT-COMPUTER. AS-400. 0
DATA DIVISION. 0
WORKING-STORAGE SECTION.
01 WS-TOTAL-COST PIC S9(13)V99 COMP-3.
01 WS-TAXRATE PIC S9V99 COMP-3
Figure 180. T1520CB1 — OPM COBOL Source to Calculate Tax and Format Cost (Part 1 of
2)
Figure 180. T1520CB1 — OPM COBOL Source to Calculate Tax and Format Cost (Part 2 of
2)
This program receives pointers to the values of the variables price, quantity,
and taxrate, and pointers to formatted_cost and success_flag.
The ILE C for AS/400 Run-Time Library Reference contains information on how to
compile an OPM COBOL program.
6. To create an OPM RPG program using the source shown below, type:
CRTRPGPGM PGM(MYLIB/T1520RP1) SRCFILE(QCLE/QARPGSRC) OPTION(*SOURCE *SECLVL)
Figure 181. T1520RP1 — OPM RPG Source to Write the Audit Trail
Hammers
1.98
5000
Nails
0.25
2000
This example is an ILE version of the small transaction processing program that is
given in the previous example.
In addition to the source for CMD, ILE CL, ILE C, Integrated Language
Environment RPG and ILE COBOL you need DDS source for the output file which
is the same as the previous example.
In the following example the CL and C programs are activated within a new
activation group. The Integrated Language Environment CL program is created
with the CRTPGM default for the ACTGRP parameter, ACTGRP(*NEW). The ILE C
program is created with ACTGRP(*CALLER)
1. To create a physical file T1520DD2 using the source shown below, type:
CRTPF FILE(MYLIB/T1520DD2) SRCFILE(QCLE/QADDSSRC) MAXMBRS(*NOMAX)
This file contains the audit trail for the ILE C program T1520ICB.
2. To create a CL program T1520CL3 that the source shown below, type:
CRTCLMOD MODULE(MYLIB/T1520CL3) SRCFILE(QCLE/QACLSRC)
CRTPGM PGM(MYLIB/T1520CL3) MODULE(MYLIB/T1520CL3) ACTGRP(*NEW)
This program passes the CL variables item name, price, quantity, and user ID
by reference to an ILE C program T1520ICB. The Retrieve Job Attributes
(RTVJOBA) command obtains the user ID for the audit trail. Arguments are
passed by reference. They can be changed by the receiving ILE C program.
The variable item_name is null ended in the CL program.
3. To create a CL command prompt T1520CM2 using the source below, type:
CRTCMD CMD(MYLIB/T1520CM2) PGM(MYLIB/T1520CL3) SRCFILE(QCLE/QACMDSRC)
Figure 185. T1520ICB — ILE C Source to Call COBOL and RPG Procedures (Part 1 of 2)
memcpy(null_formatted_cost,formatted_cost,sizeof(formatted_cost));
formatted_cost[21] = ’\0’;
if (success_flag == ’Y’)
{
for (i=0; i<20; i++)
{
/* Remove the null end for the RPG for iSeries 400 program. */
if (*(item_name+i) == ’\0’)
{
rpg_item_name[i] = ’ ’;
}
else
{
rpg_item_name[i] = *(item_name+i);
}
}
write_audit_trail(user_id,
rpg_item_name,
price,
quantity,
formatted_cost);
}
else
{
printf("Calculation failed\n");
}
}
Figure 185. T1520ICB — ILE C Source to Call COBOL and RPG Procedures (Part 2 of 2)
The main() function in this module receives the incoming arguments from the
Integrated Language Environment CL program T1520CL3 that are verified by
the CL command T1520CM2 and null ended within the CL program
T1520CL3. All the incoming arguments are pointers.
IDENTIFICATION DIVISION.
PROGRAM-ID. T1520CB2 INITIAL.
******************************************************
* parameters: *
* incoming: PRICE, QUANTITY *
* returns : TOTAL-COST (PRICE*QUANTITY*1.TAXRATE) *
* SUCCESS-FLAG. *
* TAXRATE : An imported value. *
******************************************************
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
SOURCE-COMPUTER. AS-400.
OBJECT-COMPUTER. AS-400.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TOTAL-COST PIC S9(13)V99 COMP-3.
01 WS-TAXRATE PIC S9V99 COMP-3
VALUE 1.
01 TAXRATE EXTERNAL PIC SV99 COMP-3.
LINKAGE SECTION.
01 LS-PRICE PIC S9(8)V9(2) COMP-3.
01 LS-QUANTITY PIC S9(4) COMP-4.
01 LS-TOTAL-COST PIC $$,$$$,$$$,$$$,$$$.99
DISPLAY.
01 LS-OPERATION-SUCCESSFUL PIC X DISPLAY.
Figure 186. T1520CB2 — ILE COBOL Source to Calculate Tax and Format Cost (Part 1 of 2)
Figure 186. T1520CB2 — ILE COBOL Source to Calculate Tax and Format Cost (Part 2 of 2)
This program receives pointers to the values of the variables price and
quantity, and pointers to formatted_cost and success_flag.
FT1520DD2 O A E DISK
D TAXRATE S 3P 2 IMPORT
D QTYIN DS
D QTYBIN 1 4B 0
C *ENTRY PLIST
C PARM USER 10
C PARM ITEM 20
C PARM PRICE 10 2
C PARM QTYIN
C PARM TOTAL 21
C EXSR ADDREC
C SETON LR
C ADDREC BEGSR
C MOVEL UDATE DATE
C MOVE QTYBIN QTY
C MOVE TAXRATE TXRATE
C WRITE T1520DD2R
C ENDSR
Figure 187. T1520RP2 — ILE RPG Source to Write the Audit Trail
The T1520SP3 service program exports taxrate. The export list is specified in
T1520SP3 in QASRVSRC.
9. To create the service program T1520SP4 from the module T1520RP2, type:
CRTSRVPGM SRVPGM(MYLIB/T1520SP4) MODULE(MYLIB/T1520RP2) +
EXPORT(*SRCFILE) SRCFILE(QCLE/QASRVSRC)
The T1520SP4 service program exports procedure T1520RP2. The export list is
specified in T1520SP4 in QASRVSRC.
10. To create the program T1520ICB type:
CRTPGM PGM(MYLIB/T1520ICB) MODULE(MYLIB/T1520ICB MYLIB/T1520CB2) +
BNDSRVPGM(MYLIB/T1520SP3 MYLIB/T1520SP4) ACTGRP(*CALLER)
T1520ICB is considered the applications main program. It will run in the new
activation group that was created when T1520CL3 was called.
11. To enter data for the program T1520ICB, type T1520CM2and press F4 (Prompt).:
Type the following data into T1520CM2:
Hammers
1.98
5000
Nails
0.25
2000
Example
The following example shows how to retrieve a return value from main(). A CL
command called SQUARE calls an ILE C program SQITF. The program SQITF calls
another ILE C program called SQ. The program SQ returns a value to program
SQITF.
Note: Returning an integer value from an ILE C program may affect performance.
1. To create a CL command prompt SQUARE usng the source shown below, type:
CRTCMD CMD(MYLIB/SQUARE) PGM(MYLIB/SQITF) SRCFILE(MYLIB/QCMDSRC)
You use the CL command prompt SQUARE to enter the item name, price, and
quantity for the ILE C program SQITF.
2. To create a program SQIFT using the source shown below, type:
CRTBNDC PGM(MYLIB/SQIFT) SRCFILE(MYLIB/QCSRC)
The program SQ calculates an integer value and returns the value to the calling
program SQITF.
4. To enter data for the program SQITF, type:
SQUARE
In contrast to static procedure calls, which are resolved and bound at compile time,
symbols for dynamic program calls are resolved to addresses when the call is
performed. As a result, a dynamic program call uses more system resources at run
time (specifically at program activation) than a static procedure call.
Note: EPM C and Pascal procedures or functions cannot call ILE C procedures.
OPM programs cannot call any ILE procedures (including ILE C
procedures).
A static procedure call can call a procedure within the same module, a procedure
in a separate module within the same ILE C program or service program, or a
procedure in a separate ILE C service program.
Table 16 on page 346 shows the default argument that passes methods on
procedure calls.
Example
The following example shows you how to call a Dynamic Screen Manager (DSM)
ILE bindable API to display a DSM session. This DSM session echoes back data
that you enter during the DSM session.
1. To create module T1520API using the source shown below, type:
CRTCMOD MODULE(MYLIB/T1520API) SRCFILE(QCLE/QACSRC) OUTPUT(*PRINT)
typedef struct{
Qsn_Ssn_Desc_T sess_desc;
char buffer[300];
}storage_t;
int main(void)
{
int i;
storage_t storage;
Qsn_Inp_Buf_T input_buffer = 0;
Q_Bin4 input_buffer_size = 50;
char char_buffer[100];
Q_Bin4 char_buffer_size;
Qsn_Ssn_T session1;
Qsn_Ssn_Desc_T *sess_desc = (Qsn_Ssn_Desc_T *) &storage;
Qsn_Win_Desc_T win_desc;
Q_Bin4 win_desc_length = sizeof(Qsn_Win_Desc_T);
char *botline = BOTLINE;
Q_Bin4 botline_len = sizeof(BOTLINE) - 1;
Q_Bin4 sess_desc_length = sizeof(Qsn_Ssn_Desc_T) +
botline_len;
Q_Bin4 bytes_read;
sess_desc->cmd_key_desc_line_1_offset = sizeof(Qsn_Ssn_Desc_T);
sess_desc->cmd_key_desc_line_1_len = botline_len;
memcpy( storage.buffer, botline, botline_len );
sess_desc->cmd_key_desc_line_2_offset = sizeof(Qsn_Ssn_Desc_T) +
botline_len;
/* Set up the session type. */
sess_desc->EBCDIC_dsp_cc = ’1’;
sess_desc->scl_line_dsp = ’1’;
sess_desc->num_input_line_rows = 1;
sess_desc->wrap = ’1’;
win_desc.top_row = 3;
win_desc.left_col = 3;
win_desc.num_rows = 13;
win_desc.num_cols = 45;
sess_desc->cmd_key_action[2] = F3Exit;
session1 = QsnCrtSsn( sess_desc, sess_desc_length,
NULL, 0,
’1’,
&win_desc, win_desc_length,
NULL, 0,
NULL, NULL);
if(input_buffer == 0)
{
input_buffer = QsnCrtInpBuf(100, 50, 0, NULL, NULL);
}
for (;;)
{
Example
Figure 192. ILE C Source that Declares a Function that Requires Operational Descriptors
A function that is named func() is declared. The #pragma descriptor for func()
specifies that the ILE C compiler must generate string operational descriptors for
the first and fourth arguments of func() whenever func() is called.
The following shows that the #pragma descriptor for func1 with a #pragma
descriptor directive for the function in a header file oper_desc.h.
A function that is named func1() is declared. The #pragma descriptor for func1()
specifies that the ILE C compiler must generate string operational descriptors for
the three arguments.
The following shows an ILE C program that calls func1(). When the function
func1() is called, the compiler generates operational descriptors for the three
arguments that are specified on the call.
#include "oper_desc.h"
...
main()
{
char a[5] = {’s’, ’t’, ’u’, ’v’, ’\0’};
char *c;
c = "EFGH";
...
func1(a, "ABCD", c);
}
The following shows the ILE C source code of func1() that contains the call to the
Integrated Language Environment API. The API is used to determine the string
type, length, and maximum length of the string arguments declared in func1().
The values for typeCharZ and typeCharV2 are found in the ILE API header file
<leod.h>.
#include <string.h>
#include <stdio.h>
#include <leawi.h>
#include <leod.h>
#include "oper_desc.h"
int func1(char a[5], char b[5], char *c)
{
int posn = 1;
int datatype;
int currlen;
int maxlen;
_FEEDBACK fc;
char *string1;
/* Call to ILE API CEEGSI to determine string type, length */
/* and the maximum length. */
CEEGSI(&posn, &datatype, &currlen, &maxlen, &fc);
Figure 195. ILE C Source to Determine the Strong Arguments in a Function (Part 1 of 2)
Figure 195. ILE C Source to Determine the Strong Arguments in a Function (Part 2 of 2)
Use the #pragma linkage and #pragma argument directives to specify the linkage
convention. See Table 15 on page 326 and “Calling Procedures for ILE C” on
page 345 for more information on using these directives.
A C++ program uses the standard OS linkage calling convention. Use #pragma
linkage to flag the function call as an external program call.
When you call C++ functions, you must make sure that the sender and receiver
both agree on the type of parameter being passed, whether it is by pointer or by
value, and whether parameters are widened. For example, if the function you are
calling was declared as extern "C nowiden", you must use the #pragma
argument(func, nowiden) directive in the function declaration in ILE C.
You can declare a C++ function as external by either explicitly declaring the
function within the C++ code using extern "C" or extern "C nowiden". You can
add #ifdef statements to the function declarations in the header files used by both
C and C++ modules, as follows:
These statements are declared with C linkagel.
#ifdef __cplusplus
extern "C" {
#endif
function declarations
#ifdef __cplusplus
}
#endif
Note: The term static procedure call does not refer to static storage class but refers
to a bound procedure call within a bound module or service program. If the
static call is to a procedure written in a language other that C or C++,
operational descriptors can be used to resolve the differences in the
representation of character strings, if values of this data type are passed as
arguments.
Procedure pointer calls provide a way to call a procedure dynamically. You can pass
a procedure pointer as a parameter to another procedure which then runs the
procedure specified. You can manipulate arrays of procedure names or addresses
to dynamically route a procedure call to different procedures. If the called
procedure is in the same activation group, the cost of a procedure pointer call is
almost identical to the cost of a static procedure call.
Using either type of procedure call, you can call a procedure in a separate module
within the same ILE program or service program, or a procedure in a separate ILE
service program. Any procedure that can be called using a static procedure call can
also be called through a procedure pointer.
When an ILE program is called, the program entry procedure is first added to the
call stack. After the program entry procedure is called, control is given to the main
entry point in the program (main() for C or C++) which is pushed onto the stack.
Figure 196 on page 353 shows a call stack for an program consisting of an OPM
program which calls an ILE program consisting of two modules: a C++ module
containing the program entry procedure and the associated user entry procedure,
and a C module containing a regular procedure. The most recent entry is at the
bottom of the stack.
OPM OPM
Program A
Procedure Call
C Module ILE
Procedure Procedure
Note: In a dynamic program call, the calls to the program entry procedure and the
user entry procedure (UEP) occur together, since the call to the UEP is
automatic. In later diagrams involving the call stack, the two steps of a
dynamic program call are combined.
An ILE C++ procedure which is on the call stack can be called before it returns to
its caller. This is a recursive procedure call. This is different from an ILE COBOL
procedure which when on the call stack cannot be called until it returns to its
caller. This is a non-recursive procedure call. Therefore, be careful not to call from
ILE C++ procedures another ILE COBOL procedure which might call an already
active ILE COBOL procedure.
Assume that procedure A is an ILE C++ procedure, procedure B and C are ILE
COBOL procedures, and that these procedures are in the same program. If
procedure A calls procedure B, then procedure B can call neither procedure A nor B.
If procedure B returns and if procedure A then calls procedure C, procedure C can
call procedure B but not procedure A or C. See Figure 197.
PROC A PROC A
PROC B PROC C
Figure 197. ILE C++ Procedures Cannot Call Other Active ILE COBOL Procedures
If you want a C++ program to call an ILE, OPM, or EPM program (*PGM), use the
extern "OS" linkage specification in your C++ source to tell the compiler that the
called program is an external program, not a bound ILE procedure. For example, if
you want a C++ program to call an OPM COBOL program (*PGM) this extern "OS"
linkage specification in your C++ source tells the compiler that COBOL_PGM is an
external program, not a bound ILE procedure.
extern "OS" void COBOL_PGM(void);
If you want an ILE, OPM or EPM program to call a C++ program, use the ILE,
OPM, or EPM language-specific call statement.
C++ provides a linkage specification to enable procedure calls and the sharing of
data between the C++ caller and the called procedure. See “Calling a Program
Using a Linkage Specification” on page 353 for the syntax.
The valid string literals for the linkage specification to call ILE procedures are:
Linkage Specification Type of Procedure Called
"C++" ILE C++ procedure (default)
"C" ILE C procedure
"C nowiden" ILE C procedure without widened parameters
"RPG" ILE RPG procedure
Passing Parameters
To share data between programs or between procedures, you need to pass the
called program or procedure parameters which both programs can use. In C++ you
use the linkage specification to tell the compiler which parameter passing
convention to use on the external call.
The extern keyword followed by the string literals "RPG", "COBOL", or "CL" are
used to specify that the function has "ILE" linkage. These string literals perform
the same function as the #pragma argument directive in ILE C. The "VREF" linkage
also performs the same as the VREF parameter on the #pragma argument
directive.
To override a function but not to override extern "OS" use a type cast:
extern "ILE"
{
typedef void (*ILE) ();
}
extern "C++"
{
typedef void (*CPP) ();
}
ILE pILE;
CPP pCPP = (CPP) pILE;
Functions that take function pointers as parameters may not be overloaded based
on the linkage of the function pointers:
// Using the typedef declarations above
void foo (OS);
void foo (CPP); // undefined behavior, foo already declared
Functions that are defined with non-C++ linkage specifications accept parameters
using the appropriate convention for that linkage. You do not need to widen
parameters:
extern "C" void foo (char); // chars are widened in C
// In another compilation unit we then have
extern "C" void foo (char c) // this parameter is correctly widened
{
// implementation of foo (char);
}
Attempting to define a function with either extern "OS", extern "OS nowiden", or
extern "builtin" linkage results in undefined behavior:
extern "OS" void FOOPGM (char); // declaration: OK
extern "OS" void FOOPGM (char c) // definition: undefined behavior
{
// implementation of FOOPGM
}
Functions FOO1(), FOO2(), and FOO3() are all declared as OS linkage functions.
Functions FOO1() and FOO2() are declared by using the declaration-list syntax.
FOO3() is declared by using the simple declaration.
extern "OS" {
void FOO1 (char, char *);
void FOO2 (int, int *);
}
extern "OS" double FOO3 (double, double *);
The widening rules for all the linkage specifications shown in Table 17 on page 355
are:
v Any data type that is smaller than int is widened to int
v float is widened to double
v Address and Data pointers are not widened
v struct has the same structure and information as the struct parameter passed
In C++ linkage specifications, function identifiers are mangled. In all other linkage
specifications, all function identifiers are identical to the exported names unless
changed by the #pragma map directive.
OS Linkage
The extern specifier followed by the string-literal "OS" or the string-literal "OS
nowiden" is used to declare external programs. These programs may then be called
in the same way as a regular function.
The program name that the C++ dynamic program calls must be in uppercase. You
can use the #pragma map directive to map an internal identifier longer than 10
characters to an OS/400-compliant object name (10 characters or less) in your
program. See “Changing the Names of Programs and Procedures” on page 371.
The return code for the dynamic program call can be retrieved by declaring the
program to return an integer:
The value returned on the call is the return code for the dynamic program call. If
the program being called is a C++ program, this return code can be accessed using
the _LANGUAGE_RETURN_CODE macro defined in the header file <milib.h>. A C++
program returns four bytes in the _LANGUAGE_RETURN_CODE. If the program being
called is an EPM or OPM program, this return code can be accessed using the
iSeries Retrieve Job Attributes (RTVJOBA) command.
Nonpointer arguments are passed by value reference, and changes made to the
variables in the called program are not reflected in the calling C++ program.
C Linkage
Specifying C linkage for a function tells the compiler:
v Parameters are passed using C++ conventions
v Parameters for functions declared with extern "C" are widened
v The function name is not mangled
The extern keyword followed by the string-literal "C" or the string-literal "C
nowiden" is used to specify that the function is declared to have "C" linkage instead
of "C++" linkage.
VREF Linkage
Specifying a VREF linkage is identical to specifying an ILE linkage except that
pointer parameters are stored in a temporary variable and the address of the
temporary variable is passed as the actual argument.
Table 18 shows the common parameter-passing methods for the ILE programs.
Table 18. Default Argument Passing Style for ILE Programs
ILE HLL Pass Argument Receive Argument
ILE C by value, directly or by reference by value, directly or by reference
ILE C++ by value, directly or by value, by value, directly, or by value,
indirectly or by reference indirectly or by reference
ILE COBOL by reference or by value, indirectly by reference or by value, indirectly
ILE RPG by reference by reference
ILE CL by reference by reference
Table 19 shows the common parameter passing methods for the ILE procedures.
Table 19. Default Argument Passing Style for ILE Procedures
ILE HLL Pass Argument Receive Argument
ILE C by value, directly by value, directly
ILE C++ by value, directly or by value, by value, directly, or by value,
indirectly or by reference indirectly or by reference
ILE COBOL by reference or by value, indirectly by reference
ILE RPG by reference by reference
ILE CL by reference by reference
See the VisualAge for C++ for AS/400 C++ Language Reference for information on
operational descriptors.
Some data types in the ILE C++ programming language have no direct equivalent
in other languages. You can simulate data types in other languages using ILE C++
data types.
Note: No data-type compatibility tables are shown for C. You can use the C++
tables since C and C++ data types are the same except for the packed
decimal data type. In C++ the packed decimal data type is implemented as
a class. The packed decimal data type in ILE C and the binary coded
decimal class in C++ are binary compatible.
Table 20 shows the ILE C++ data type compatibility with ILE RPG.
Table 20. ILE C++ Data-Type Compatibility with ILE RPG
ILE C++ ILE RPG D spec, Length Comments
declaration in columns 33 to 39
prototype
char[n] nA n An array of characters where n=1
to 32766
char * * 16 A pointer
char 1A 1 An indicator which is a variable
starting with *IN
char[n] nS 0 n A zoned decimal
char[2n] nG 2n A graphic added
char[2n+2] Not supported 2n+2 A graphic data type
_Packed struct data structure n+2 A variable length field where i is
{short i; the intended length and n is the
char[n]} maximum length
char[n] D 8, 10 A date field
char[n] T 8 A time field
char[n] Z 26 A timestamp field
short int 5I 0 2 An integer field
short unsigned 5U 0 2 An unsigned integer field
int
int 10I 0 4 An integer field
Table 21 shows the ILE C++ data-type compatibility with ILE COBOL.
Table 21. ILE C++ Data-Type Compatibility with ILE COBOL
ILE C++ ILE COBOL LINKAGE Length Comments
declaration in SECTION
prototype
Table 22 shows the ILE C++ data-type compatibility with ILE CL.
Table 22. ILE C++ Data-Type Compatibility with ILE CL
ILE C++ CL Length Comments
declaration in
prototype
Table 24 shows the ILE C++ data-type compatibility with OPM COBOL.
Table 24. ILE C++ Data-Type Compatibility with OPM COBOL
ILE C++ OPM COBOL Length Comments
declaration in LINKAGE SECTION
prototype
CL variables and numeric literals are not passed to an ILE C++ program with
null-terminated strings. Character literals and logical literals are passed as
null-terminated strings but are not padded with blanks. Numeric literals such as
packed decimals are passed as 15,5 (8 bytes).
The parameters are null-terminated within the the CL program CLPROG1. They are
passed by reference. All incoming arguments to MYPROG1 are pointers.
/* CLPROG1
PGM PARM(&V &D &H &I &J)
DCL VAR(&V) TYPE(*CHAR) LEN(10)
DCL VAR(&VOUT) TYPE(*CHAR) LEN(11)
DCL VAR(&D) TYPE(*DEC) LEN(10 1)
DCL VAR(&H) TYPE(*CHAR) LEN(10)
DCL VAR(&HOUT) TYPE(*CHAR) LEN(11)
DCL VAR(&I) TYPE(*CHAR) LEN(10)
DCL VAR(&IOUT) TYPE(*CHAR) LEN(11)
DCL VAR(&J) TYPE(*LGL) LEN(1)
DCL VAR(&JOUT) TYPE(*LGL) LEN(2)
DCL VAR(&NULL) TYPE(*CHAR) LEN(1) VALUE(X’00’)
/* ADD NULL TERMINATOR FOR THE C++ PROGRAM */
CHGVAR VAR(&VOUT) VALUE(&V *TCAT &NULL)
CHGVAR VAR(&HOUT) VALUE(&V *TCAT &NULL)
CHGVAR VAR(&IOUT) VALUE(&V *TCAT &NULL)
CHGVAR VAR(&JOUT) VALUE(&V *TCAT &NULL)
CALL PGM(MYPROG1) PARM(&VOUT &D &HOUT &IOUT &JOUT)
ENDPGM
The CL program CLPROG1 receives its input values from a CL Command Prompt
MYCMD1 which prompts the user to input the desired values. The source code for
MYCMD1 is:
After the CL program CLPROG1 has received the user input from the command
prompt MYCMD1, it passes the input values on to a C++ program MYPROG1. The
source code for this program is contained in myprog1.cpp:
// myprog1.cpp
#include <iostream.h>
#include <stdlib.h>
#include <string.h>
#include <bcd.h>
v = argv[1];
d = *((_DecimalT <10,1> *) argv[2]);
h = argv[3];
i = argv[4];
j = argv[5];
cout << " v= " << v
<< " d= " << d
<< " h= " << h
<< " i= " << i
<< " j= " << j
<< endl;
}
If the CL program CLPROG1 passes the following parameters to the C++ program
MYPROG1:
’123.4’, 123.4, ’Hi’, LO, and ’1’
You can use the #pragma map directive to map an internal identifier to an
OS/400-compliant name (10 characters or less for program names and 1 or more
characters for ILE procedure names) in your program.
\\ # pragma map \
This pragma tells the compiler that all references to identifier are to be converted
to ″name″. The pragma can appear anywhere in the source file within a single
compilation unit. It can appear before any declaration or definition of the named
object, function or operator. The identifiers appearing in the directive, including
any type names used in the prototype argument list, are resolved as though the
directive had appeared at file scope, independent of its actual point of occurrence:
int func(int);
class X
{
public:
void func(void);
#pragma map(func, "funcname1") //maps ::func
#pragma map(X::func, "funcname2") //maps X::func
};
There are two functions named func() in this code. One is a regular function,
prototyped int func(int) and the other is a class member function void
func(void). To avoid confusion, they are renamed funcname1, and funcname2 using
the #pragma map directive.
Mapping can be based on parameter type as well as scope. For example, void
func(int); void func(char); #pragma map(func(int),″intFunc″) #pragma
map(func(char), ″charFunc″). This doesl not work with *PRV option.
A shared C/C++ header for class MyClass might look like the following:
/* myclass.h */
#ifdef __cplusplus
class MyClass {
To access a C++ class from a C program you need to write your own functions to
inspect and manipulate the class data members directly.
Note: While data members in the C++ class can be public, protected, or private,
the variables in the corresponding C structure are always publicly accessible.
Be careful, you may eliminate the safeguards built into the C++ language.
You can use C++ operators on this class if you supply your own definitions of
these operators in the form of member functions.
This program shows how you can access the data members in C++ classes from
source code written in C.
Program Structure
The program consists of these files:
v A C++ source file hourclas.cpp which contains:
– Definitions of one base class, HourMin, and two derived classes, HourMinSec1
and HourMinSec2
– Three function prototypes with extern "C" linkage:
- extern "C" void CSetHour(HourMin *)
- extern "C" void CSetSec(HourMin *)
- extern "C" void CSafeSetHour(HourMin *)
– The definition of a function with extern "C" linkage, extern "C" void
CXXSetHour(HourMin * x)
– A main() function containing the program logic
v A C source file hour.c which contains:
– A structure CHourMin that maps to the C++ class HourMin in file hourclas.cpp
– Definitions of the three functions with extern "C" linkage declared in
hourclas.cpp
– The definition of a function CSafeSetHour()
Program Description
In its main() function the program:
1. Instantiates an object hm of the base class HourMin
2. Assigns a value to the h variable (hour) in the base class
3. Passes the address of the base class to the function CSetHour() defined in the
C source file hour.c, which assigns a new value to h in the base class
4. Displays the value of h in the base class
5. Instantiates an object hms1 of the derived class HourMinSec1
6. Passes the address of this object class to the function CSetSec() defined in the
C source file hour.c, which assigns a value to s in the object
7. Displays the value of s in the object
8. Instantiates an object hms2 of the class HourMinSec2 which contains the class
HourMin
9. Passes the address of this new object to the function CSetSec() defined in the
C source file hour.c, which assigns a value to s in the object
10. Displays the value of s in the object
public:
void set_hour(int hour) { h = hour % 24; } // keep it in range
int get_hour() { return h; }
void set_min(int min) { m = min % 60; } // keep it in range
int get_min() { return m; }
HourMin(): h(0), m(0) {}
void display() { cout << h << ’:’ << m << endl; }
};
public:
void set_sec(int sec) { s = sec % 60; } // keep it in range
int get_sec() { return s; }
HourMinSec2() { s = 0; }
void display() {
cout << a.get_hour() << ’:’ << a.get_min() << ’:’ << s << endl; }
};
main() {
HourMin hm;
hm.set_hour(18); // supper time;
CSetHour(&hm); // pass address of object to C function
hm.display(); // hour is out of range
HourMinSec2 hms2;
CSetSec(&hms2);
hms2.display();
struct CHourMin {
int hour;
int min;
};
void CSetHour(void * v) {
struct CHourMin * p;
p = (struct CHourMin *) v; // force it to the type we want
p->hour = 99; // with power comes responsibility (oops!)
}
struct CHourMinSec {
struct CHourMin hourMin;
int sec;
};
Program Output
The program output is:
99:0
0 -:0 :45
0 -:0 :45
3 -:0
Press ENTER to end terminal session.
Program Description
The program is a small transaction-processing program that takes as input the item
name, price, and quantity for one or more products. As output, the program
displays the total cost of the items specified on the display and writes an audit
trail of the transactions to a file.
User Input
OPM CL CMD
Program
T2123CM2
OPM CL
Program
T2123CL2
ILE C++
Program
T2123IC5
Audit File
T2123DD2
Program Structure
The program consists of these components:
v A CL command T2123CM2 that accepts the users input and passes it to an OPM
CL program
v An OPM CL program T2123CL2 that processes the input and passes it to an ILE
C++ program
v An ILE C++ program T2123IC5 that calls an OPM COBOL program to process
the input, and an OPM RPG program to write the audit trail to an externally
described file
Program Activation
The ILE C++ program T2123IC5 is created with the CRTPGM default for the
ACTGRP parameter, ACTGRP(*NEW). When the CL program calls the ILE C++
program, a new activation group is started.
The OPM CL, COBOL, and RPG programs are activated within the OPM default
activation group.
CalcAndFormat() WriteAuditTrail()
Program Files
The source code for each of the files that compose this program are an externally
described file, a CL program, a CL command prompt, a C++ source file, and OPM
COBOL program and an OPM RPG program.
CL Program T2123CL2
The CL program T2123CL2 passes the CL variables item_name, price, quantity and
user_id by reference to an ILE C++ program T2123IC5.
The Retrieve Job Attributes (RTVJOBA) command obtains the user ID for the audit
trail. Arguments are passed by reference. They can be changed by the receiving ILE
C++ program. The variables containing the user and item names are explicitly
null-terminated in the CL program.
Note: CL variables and numeric literals are not passed to an ILE C++ program
with null-terminated strings. Character literals and logical literals are passed
as null-terminated strings but are not widened with blanks. Numeric literals
such as packed decimals are passed as 15,5 (8 bytes). Floating point
constants are passed as double precision floating point values (1.2E+15).
The formatted_cost and the success_flag values are updated in the C++ program
T2123IC5.
Note: By default, the compiler converts a short integer to an integer unless the
nowiden parameter is specified on the extern linkage specification. The short
integer in the C++ program is converted to an integer, and then passed to
the OPM RPG program. The RPG program is expecting a 4 byte integer for
the quantity variable. See “Understanding Data-Type Compatibility” on
page 361 for information on data-type compatibility.
// This program is called by a CL program that passes an item
// name, price, quantity and user ID.
// COBOL is called to calculate and format the total cost.
// RPG is called to write an audit trail.
#include <iostream.h>
#include <stdlib.h>
#include <string.h>
#include <bcd.h>
// The #pragma map directive maps a new program name to the existing
// program name so that the purpose of the program is clear.
// Tell the compiler that there are dynamic program calls so
// arguments are passed by value-reference.
#pragma map(CalcAndFormat,"T2123CB1")
#pragma map(WriteAuditTrail,"T2123RP1")
char *user_id;
char *item_name;
short int quantity;
_DecimalT <10, 2> price;
_DecimalT <2,2> taxrate = __D(".15");
char formatted_cost[22];
char rpg_item_name[20];
char null_formatted_cost[22];
char success_flag = ’N’;
int i;
memcpy(null_formatted_cost,formatted_cost,sizeof(formatted_cost));
formatted_cost[21] = ’\0’;
if (success_flag == ’Y’)
{
for (i=0; i<20; i++)
{
if (*(item_name+i) == ’\0’)
{
rpg_item_name[i] = ’ ’;
}
else
{
rpg_item_name[i] = *(item_name+i);
}
}
The CalcAndFormat() function in program T2123CB1 calculates and formats the total
cost. Parameters are passed from the ILE C++ program to the OPM COBOL
program to do the tax calculation.
IDENTIFICATION DIVISION.
PROGRAM-ID. T2123CB1.
******************************************************
* parameters: *
* incoming: PRICE, QUANTITY *
* returns : TOTAL-COST (PRICE*QUANTITY*1.TAXRATE) *
* SUCCESS-FLAG. *
******************************************************
ENVIRONMENT DIVISION. 0
CONFIGURATION SECTION. 0
LINKAGE SECTION.
MAINLINE.
MOVE "Y" TO LS-OPERATION-SUCCESSFUL.
PERFORM CALCULATE-COST.
PERFORM FORMAT-COST.
EXIT PROGRAM.
CALCULATE-COST.
MOVE LS-TAXRATE TO WS-TAXRATE.
ADD 1 TO WS-TAXRATE.
COMPUTE WS-TOTAL-COST ROUNDED = LS-QUANTITY *
LS-PRICE *
WS-TAXRATE
ON SIZE ERROR
MOVE "N" TO LS-OPERATION-SUCCESSFUL
END-COMPUTE.
FORMAT-COST.
MOVE WS-TOTAL-COST TO LS-TOTAL-COST.
Program Description
This program is an ILE version of the small transaction-processing program
described in the “Calling OPM Programs” on page 376.
Program Structure
The program consists of these components:
v A CL command T2123CM3 that accepts the user input and passes it to an ILE CL
program
v An ILE CL program T2123CL3 that processes the input and passes it to an ILE
program
v An ILE program T2123ICB in which the main() function of a C++ module
T2123ICB calls a procedure CalcAndFormat in an ILE COBOL module T2123CB2
v A service program T2123SP3, created from a C++ source file t2123icc.cpp, that
exports the variable TAXRATE
v A service program T2123SP4, created from an ILE RPG module object T2123RP2,
that writes an audit trail of all transactions to a file
v An externally described file T2123DD2 that receives the audit trail data
Command
T2123CM3
ILE CL Program
T2123CL3
Audit File
T2123DD2
Program Activation
The programs T2123CL3 and T2123ICB are created with the CRTPGM default for the
ACTGRP parameter, ACTGRP(*NEW). When the CL program calls the ILE C++
program, a new activation group is started.
main()
Module T2123CB2
T2123CB2
CalcAndFormat
Figure 201 on page 383 shows the basic object structure used in this example.
Program Files
The source files for this program include an externally described file, a CL
program, a command prompt, two C++ source files, and ILE COBOL source file
and an ILE RPG source file.
See “Externally Described File T2123DD2” on page 377 for the DDS source of the
audit file T2123DD2.
CL Program T2123CL3
The CL program T2123CL3 passes the CL variables item_name, price, quantity, and
user_id by reference to an ILE C++ program T2123IC5.
The source code for program T2123CL3 is identical to the source code shown in
“CL Program T2123CL2” on page 377.
#include <iostream.h>
#include <stdlib.h>
#include <string.h>
#include <bcd.h>
#pragma map(CalcAndFormat,"T2123CB2")
#pragma map(WriteAuditTrail,"T2123RP2")
char *user_id;
char *item_name;
short int quantity;
_DecimalT<10, 2> price;
char formatted_cost[22];
char rpg_item_name[20];
char null_formatted_cost[22];
char success_flag = ’N’;
int i;
memcpy(null_formatted_cost,formatted_cost,sizeof(formatted_cost));
formatted_cost[21] = ’\0’;
if (success_flag == ’Y’)
{
for (i=0; i<20; i++)
{
if (*(item_name+i) == ’\0’)
{
rpg_item_name[i] = ’ ’;
}
else
{
rpg_item_name[i] = *(item_name+i);
}
}
cout <<"plus tax =" << quantity << item_name << null_formatted_cost
<<endl <<endl;
}
else
{
cout <<"Calculation failed" <<endl;
}
}
The CalcAndFormat() function calculates and formats the total cost. Parameters are
passed from the ILE C++ program to the ILE COBOL procedure to do the tax
calculation.
LINKAGE SECTION.
01 LS-PRICE PIC S9(8)V9(2) COMP-3.
01 LS-QUANTITY PIC S9(4) COMP-4.
01 LS-TOTAL-COST PIC $$,$$$,$$$,$$$,$$$.99
DISPLAY.
01 LS-OPERATION-SUCCESSFUL PIC X DISPLAY.
MAINLINE.
MOVE "Y" TO LS-OPERATION-SUCCESSFUL.
PERFORM CALCULATE-COST.
PERFORM FORMAT-COST.
EXIT PROGRAM.
CALCULATE-COST.
ADD TAXRATE TO WS-TAXRATE.
COMPUTE WS-TOTAL-COST ROUNDED = LS-QUANTITY *
ON SIZE ERROR
MOVE "N" TO LS-OPERATION-SUCCESSFUL
END-COMPUTE.
FORMAT-COST.
MOVE WS-TOTAL-COST TO LS-TOTAL-COST.
To enter data for the program T2123ICB enter the command: T2123CM2 and press F4
(Prompt). You can enter the sample data in “Invoking the ILE-OPM Program” on
page 382.
The output is the same as for the OPM version of this program.
The physical file T2123DD2 contains the same data as shown in the OPM version in
“Invoking the ILE-OPM Program” on page 382.
Note: Returning an integer value from an ILE C++ program may impact
performance.
The ILE C++ program calls another ILE C++ program called SQ:
// This program SQITF is called by the command SQUARE. This
// program then calls another ILE C++ program SQ to perform
// calculations and return a value.
#include <iostream.h>
x = (int *) argv[1];
result = SQ(*x);
The ILE C++ program SQ calculates an integer value and returns the value to the
calling program SQITF:
// This program is called by a ILE C++ program called SQITF.
// It performs the square calculations and returns a value to SQITF.
To enter data for the program SQITF enter the command SQUARE and press F4
(Prompt). Type 10, and press Enter. The output is:
The SQUARE of 10 is 100
Press ENTER to end terminal session.
The ILE C++ program T2123DL2 passes two integers and two characters to an EPM
C program T2123DL3:
// t2123dl2.cpp
#include <string.h>
#include <iostream.h>
int main(void)
{
int i; // Integer parameter to pass to callee.
int rtn; // Return value from main in T2123DL3.
char a,b; // Character parameters to pass.
i = 5; // Initialize parameters to be passed.
a = ’a’;
b = ’b’;
T2123DL3(&rtn,&i,&a,&b);
cout << "Values returned are rtn = " <<rtn << ", i=" <<i
<< ", a=" <<a << ", b=" <<b <<endl;
}
The EPM C program T2123DL3 receives and return values to the ILE C++ program:
/* This program illustrates how this EPM C program retrieves */
/* t2123dl3.c */
#include <stdio.h>
* (int *) argv[2] = 8;
printf ( "character passed was %c\n", *argv[3]);
*argv[3] = ’D’;
#include <stddef.h>
#include <string.h>
#include <iostream.h>
#include <qsnapi.h>
typedef struct{
Qsn_Ssn_Desc_T sess_desc;
char buffer[300];
}storage_t;
int main(void)
{
int i;
storage_t storage;
Qsn_Inp_Buf_T input_buffer = 0;
Q_Bin4 input_buffer_size = 50;
char char_buffer[100];
Q_Bin4 char_buffer_size;
Qsn_Ssn_T session1;
Qsn_Ssn_Desc_T *sess_desc = (Qsn_Ssn_Desc_T *) &storage;
Qsn_Win_Desc_T win_desc;
Q_Bin4 win_desc_length = sizeof(Qsn_Win_Desc_T);
char *botline = BOTLINE;
Q_Bin4 botline_len = sizeof(BOTLINE) - 1;
Q_Bin4 sess_desc_length = sizeof(Qsn_Ssn_Desc_T) +
botline_len;
Q_Bin4 bytes_read;
sess_desc->cmd_key_desc_line_1_offset = sizeof(Qsn_Ssn_Desc_T);
sess_desc->cmd_key_desc_line_1_len = botline_len;
memcpy( storage.buffer, botline, botline_len );
sess_desc->EBCDIC_dsp_cc = ’1’;
sess_desc->scl_line_dsp = ’1’;
sess_desc->num_input_line_rows = 1;
sess_desc->wrap = ’1’;
win_desc.top_row = 3;
win_desc.left_col = 3;
win_desc.num_rows = 13;
win_desc.num_cols = 45;
sess_desc->cmd_key_action[2] = F3Exit;
session1 = QsnCrtSsn( sess_desc, sess_desc_length,
NULL, 0,
’1’,
&win_desc, win_desc_length,
NULL, 0,
NULL, NULL);
if(input_buffer == 0)
{
input_buffer = QsnCrtInpBuf(100, 50, 0, NULL, NULL);
}
for (;;)
{
The source code for the header file op_desc.h shows an operational descriptor for
func1 with a #pragma descriptor directive for the function:
/* op_desc.h */
/* containing function prototype */
extern "C" int func1( char a[5], char b[5],
char *c );
#pragma descriptor(void func1( "", "", "" ))
The source code t2123cp1.cpp shows that the program T2123CP1 calls function
func1(). When the function is called, the compiler generates operational
descriptors for the three arguments specified on the call:
// t2123cp1.cpp
#include "op_desc.h"
main()
{
char a[5] = {’s’, ’t’, ’u’, ’v’, ’\0’};
char *c;
c = "EFGH";
func1(a, "ABCD", c);
}
The source code t2123cp2.cpp for module T2123CP2 defines function func1(). It
contains the call to the ILE API that is used to determine the string type, length,
and maximum length of the string arguments declared in function func1().
The System API Programming contains information about _FEEDBACK. The values for
typeCharZ and typeCharV2 are found in the ILE API header file <leod.h>.
// t2123cp2.cpp
#include <string.h>
#include <iostream.h>
#include <leawi.h>
#include <leod.h>
#include "op_desc.h"
char *string1;
switch(datatype)
{
case typeCharZ:
string1 = a;
break;
case typeCharV2:
string1 = a + 2;
break;
}
/* Use string1.*/
The teraspace storage (TERASPACE), storage model (STGMDL), and data model
(DTAMDL) options of the C/C++ compilers determine the environment for
teraspace. For more information about these options, see ILE C/C++ Compiler
Reference.
Before trying to use teraspace in your C/C++ programs, see the chapter on
Teraspace and single-level store in ILE Concepts.
The same rule also applies to the third parameter, envp, in main().
Moreover, the resulting pointer sizes of argv and envp must also be the
same.
Variable Argument Lists
A function declared with a variable argument list is governed by the data
model at the site of declaration. The variable portion of an argument list is
#include <stdlib.h>
#include <new.h>
temp_new_handler = set_new_handler(0);
set_new_handler(temp_new_handler);
}
if (temp_new_handler != NULL)
{
temp_new_handler();
/*
* C++ global operator delete
*/
(free)(ptr);
}
Reference Types
Reference type are 8 bytes in length when the data model is LLP64.
Reference types can not be conditioned by the __ptr64 modifier.
″this″ Pointer
The size of ″this″ pointer depends on the effective data model of the class.
The prototype for the constructors, destructors and member functions of
the ″this″ pointer will be marked accordingly with either __ptr64 or
__ptr128.
#pragma datamodel(P128)
template <class T>
class FooT {
public:
T bar(const char * a, T x) {return x; }
};
#pragma datamodel (pop)
Function Overloading
A function signature is affected by the data model governing the
class/function declaration. For example, int Bar::foo(const char *) is
mangled to:
v foo__3BarFPCc when the data model is P128
v foo__3BarZFPCc when the data model is LLP64
It is possible to create overloaded methods which are identical in every
way except the size of a pointer argument. For example:
class Bar {
int foo(const char __ptr128);
int foo(const char __ptr64);
};
Class Inheritance
A derived class must be of the same data model as the base class.
The ILE C/C++ compiler recognizes source code that is written in most
single—byte EBCDIC CCSID (Coded Character Set Identifier). CCSID 290 is not
recognized because it does not have the same code points for the lowercase letters
a to z. All of the other EBCDIC CCSIDs do have the same code points for the
lowercase letters a to z. String literals can be converted back to CCSID 290 by
using the #pragma convert directive. A file with CCSID 290 still compiles because
the ILE C/C++ compiler converts the file to CCSID 037 before compiling.
CCSID 905 and 1026 are not recognized because the " character varies on these
CCSIDs.
Note: You should tag the source physical file with a CCSID value number if the
CCSID (determined by the primary language) is other than CCSID 037 (US
English).
Graphic characters are symbols, such as letters, numbers, and punctuation marks.
Code points are binary values that are assigned to each graphic character, to be
used for entering, storing, changing, viewing, or printing information.
If any secondary source files are tagged with CCSIDs different from the root source
member, their contents are automatically converted to the CCSID of the root source
member as they are read by the ILE C/C++ compiler.
If the primary source physical file has CCSID 65535, the job CCSID is assumed for
the source physical file. If the source physical file has CCSID 65535 and the job is
CCSID 65535, and the system has non-65535, the system CCSID value is assumed
for the source physical file. If the primary source physical file, job, and system
have CCSID 65535, then CCSID 037 is assumed. If the secondary file, job, and
system CCSID is 65535, then the CCSID of the primary source physical file is
assumed, and no conversion takes place.
This figure shows you what happens when you create a program object that has a
root source member with CCSID 273 and include files with different CCSIDs. The
ILE C compiler converts the include files to CCSID 273. The program object is
created with the same CCSID as the root source member.
273 273
Complied ’C’
Module Object
273
Note: Some combinations of the root source member CCSID and the include file
CCSID are not supported.
The following example shows you how to specify CCSID 273 for the source
physical file QCSRC in library MYLIB.
Example
The following example shows you how to change the first member in a source file
with CCSID 037 to CCSID 273. The source file NEWCCSID with CCSID 037 must
exist with a member.
The first member in the file NEWCCSID is copied to QCSRC with CCSID 273.
Note: If CCSID 65535 or *HEX is used, it indicates that character data in the fields
is treated as bit data and is not converted.
If a CCSID with the value 65535 is specified, the CCSID of the root source member
is assumed. If the source file CCSID value is 65535, CCSID 037 is assumed. The
CCSID of the string literals before conversion is the same CCSID as the root source
member. The CCSID can be either EBCDIC or ASCII.
Example
The following example shows you how to convert the string literals in T1520CCS
to ASCII CCSID 850 even though the CCSID of the source physical file is EBCDIC.
is equivalent to:
wchar_t wcs[] = {0x0020, 0x0009, 0x0020, 0x000A, 0x0000};
Numeric Escape Codes (\xnnnn, \ooo)
Numeric escape codes are not converted to Unicode. Instead, the numeric
portion of the literal is preserved and assumed to be a Unicode character
represented in hexadecimal or octal form. For example:
wchar_t *wcs = L" \x4145";
is equivalent to:
wchar_t wcs[] = {0x0020, 0x4145, 0x0000};
Note: Numeric hexadecimal escape codes are not validated other than to
ensure type size-limits are not exceeded.
DBCS Characters
DBCS characters entered as hexadecimal escape sequences are not
converted to Unicode. They are stored as received.
Example
Running the program would result in output similar to that shown below.
str1 = 4a 5a
str2 = ba bb
wcs1 = 005b 005d
wcs2 = 005b 005d
The source CCSID is provided to the compiler via the source file’s character set.
The target CCSID is not dependent on the source’s CCSID, but variable, requiring
the TGTCCSID parameter in the compiler’s commands. TGTCCSID allows you to
enter the desired target module CCSID. When the TGTCCSID differs from the
source file’s CCSID, the source files are converted to the TGTCCSID and processed.
This ensures that the target module and all it’s character data components (for
example, listing, string pool) are in the desired TGTCCSID. You can then develop
in one character set and target another. The argument defaults to the source file’s
character set so the default behavior is backward compatible (with the exception of
290, 930 and 5026).
Providing support for more source character sets, increases the NLS usability of the
compilers. CCSIDs 290, 930 and 5026 are now supported. The TGTCCSID
parameter provides solutions to more complex NLS programming issues. For
example, several modules with different module CCSIDs may be compiled from
the same source by simply recompiling the source with different TGTCCSID
values.
For example, if the root source file has a CCSID of 500 and the compiler parameter
TGTCCSID default value is not changed (i.e., *SOURCE), the behavior is as before
with the resulting module CCSID of 500. All string and character literals, both
single and wide-character, are as described in the source file’s CCSID. Translations
may still occur as before for literals, comments and identifiers.
However, if the TGTCCSID parameter is set to 37 and the same source recompiled,
the resulting module CCSID is 37; all literals, comments, and identifiers are
translated to 37 where needed and stored as such in the module.
Restrictions
Debug Listing View
Format Strings
When coding format strings for C runtime I/O functions (for example,
printf(″%d\n″, 1234);) the format string must be compatible with CCSID 37. When
targetting CCSIDs 290, 930, 5026 which are not CCSID 37 compatible, a #pragma
convert(37) is required around the format string literal to ensure the runtime
function processes the format string correctly.
TGTCCSID values are restricted to CCSIDs with encoding schemes 1100 or 1301.
An error message is issued by the command if any other value is entered.
1301 = EBCDIC, mixed single-byte and double-byte, using shift-in (SI) and shift-out
(SO) code extension method, Number of States = 2.
Locales
A locale is a system object that specifies how language-specific data is processed,
printed and displayed. A locale is made up of categories that describe the character
set, collating sequence, date and time representation and monetary representation
of the language environment in which it will be used. Using locales and
locale-sensitive functions, applications can be created that are independent of
language, cultural data, or character set, yet are sensitive to the language
environment of the user.
The POSIX standard defines a much more comprehensive set of functions and
locale data for application internationalization that is compared to that available
for *CLD locales. By supporting the POSIX specification for locales in the ILE
C/C++ run time and introducing new functions which comply with the XPG4,
POSIX and ISO/IEC standards, ILE C/C++ programs using locales of type
*LOCALE become more portable to and from other operating systems.
If you wish to convert your application from using locales of type *CLD to locales
of type *LOCALE, the only changes required to your C source code are in calls to
setlocale(). However, there are many differences between the locale definition
source for *CLD and *LOCALE objects. The *LOCALE definition source members
for many language environments are provided by the system in the optionally
installable library QSYSLOCALE. You may also convert your existing *CLD locale
source to the *LOCALE source definition. See Table 29 for a mapping of the
commands in a source file for creating *CLD objects to the corresponding
keywords in a source file for creating *LOCALE objects.
An application may use either locales of type *CLD or *LOCALE, but not both. If
an ILE C program attempts to use both types of locales, the results are undefined.
ILE C++ does not use *CLD. Also, some locale-sensitive functions are only
supported when locales of type *LOCALE are used. See Table 31 on page 419 for a
list of locale-sensitive functions.
The CL command format for enabling the run time that support locales of type
*LOCALE is:
CRTCMOD MODULE(MYLIB/MYMOD) SRCFILE(MYLIB/QCSRC) LOCALETYPE(*LOCALE)
CRTBNDC PGM(MYLIB/MYPGM) SRCFILE(MYLIB/QCSRC) LOCALETYPE(*LOCALE)
When the *LOCALE keyword is specified for the LOCALETYPE option, the ILE
C/C++ compiler defines the macro __POSIX_LOCALE__. When
__POSIX_LOCALE__ is defined, the locale-sensitive C run-time functions are
remapped to functions that are sensitive to locales that are defined in *LOCALE
objects. In addition, certain ILE C/C++ run-time functions can only be used with
locales of type *LOCALE and do not work with *CLD locales. These functions are
available only in V3R7 and later releases of the ILE C/C++ runtime. The list of
locale-sensitive functions later in this chapter indicates which functions are
sensitive only to locales of type *LOCALE.
Note: The default has changed. Prior to V5R1, *CLD was the default value for ILE
C. As of V5R1, the default has been changed to *LOCALE.
Creating Locales
On iSeries systems, *LOCALE objects are created with the CRTLOCALE command,
specifying the name of the file member containing the locale’s definition source,
and the CCSID to be used for mapping the characters of the locale’s character set
to their hexadecimal values.
If the active locale is not set explicitly by a call to setlocale(), it is implicitly set
by the C run time at program activation time. Here is how the C run time sets the
active locale when a program is activated:
v If the user profile has a value for the LOCALE parameter other than *NONE
(the default) or *SYSVAL, that value is used for the application’s active locale.
v If the value of the LOCALE parameter in the user profile is *NONE, the default
″C″ locale becomes the active locale.
v If the value of the LOCALE parameter in the user profile is *SYSVAL, the locale
associated with the system value QLOCALE will be used for the program’s
active locale.
v If the value of QLOCALE is *NONE, the default ″C″ locale becomes the active
locale.
For setlocale(LC_ALL, ""), if the locale names found identify valid locales on the
system, setlocale() returns a string naming the locale associated with each locale
category. Otherwise, setlocale() returns NULL, and the program’s locale remains
unchanged.
If you wish to migrate your application from locales of type *CLD to locales of
type *LOCALE, but you want to be compatible with the default ″C″ locale of type
*CLD, use the ″SAA″ locale definition source member in the QSYSLOCALE library
to create a locale with the CRTLOCALE command. Then use the name of this
locale when you call setlocale() in your application.
The differences between the ″SAA″ and ″POSIX″ locale definitions are as follows:
v For the LC_CTYPE category, the ″SAA″ locale has all the EBCDIC control
characters defined in the ’cntrl’ class, whereas the ″POSIX″ locale only includes
the ASCII control characters. Also, ″SAA″ has the cent character and the broken
vertical line as ’punct’ characters whereas ″POSIX″ does not include these two
characters in its ’punct’ characters.
v For the LC_COLLATE category, the default collation sequence for ″SAA″ is the
EBCDIC sequence whereas ″POSIX″ uses the ASCII sequence. This is
independent of the CCSID mapping of the character set. For the ″POSIX″ locale,
the first 128 ASCII characters are defined in the collation sequence, and the
remaining EBCDIC characters are at the end of the collating sequence. Also, in
the ″SAA″ locale definition, the lowercase letters collate before the uppercase
letters, whereas in the ″POSIX″ locale definition, the lowercase letters collate
after the uppercase letters.
v For the LC_TIME category, the ″SAA″ locale specifies the date and time format
(d_t_fmt) as ″%Y/%M/%D %X″ whereas the ″POSIX″ locale uses ″%a %b %d
%H %M %S %Y″.
Use performance analysis tools to find out where your performance problems are,
and then try and apply different appropriate tips to try and achieve the best
performance for your program.
Note: Before trying different tips, first compile and benchmark your programs
using full optimization.
Data Types
There are several ways to improve performance through data types. Replacing bit
fields with other data types and minimizing the use of static and global variables
are some of the ways.
Note: You can still obtain a run-time improvement if the bit-field is smaller than
the integral type. The extra time required for bit-field manipulation code
offsets the performance gain due to space saved in data.
If you use the register storage class, you cannot rely on the value displayed from
within the debugger, since you may be referencing an older value that is still in
storage.
Classes
When you use the IBM Open Class Library or IBM Access Class Library for
OS/400 to create classes, use a high level of abstraction. After you establish the
type of access to your class, you can create more specific implementations. This can
result in improved performance with minimal code changes. See the VisualAge for
C++ for AS/400 IBM Open Class Library User’s Guide for information on using
classes.
When you define structures or data members within a class, define the largest data
types first to align them on the largest natural boundary. Define pointers first to
reduce the padding necessary to align them on quadword (16-byte) boundaries.
Follow them, in order, with the double-word, and half-word items to avoid
padding or improve load/store time.
Performance Measurement
You can use a native compiler option to include performance hooks in your
generated code.
The extra expense of capturing data on a leaf procedure is mainly due to the fact
that the leaf procedures being hooked lose their ’leaf-ness’ in the process. This
happens due to the fact that hooks are basically calls to collection routines and leaf
procedures by definition do not make calls.
See the VisualAge for C++ for AS/400 C++ User’s Guide for information on these
options.
Exception Handling
Reducing exceptions, turning off C2M messages during record I/O and using
direct monitor handlers are some of ways to improve exception handling
performance.
Reduce Exceptions
Exceptions are expensive to process. Try to minimize the number of exceptions in
your programs.
If you use record I/O, the rtncode=y option can be used on _Ropen() to help
reduce exceptions. Files that are opened with this option do not have exceptions
generated for the "Record not found" (CPF5006) and the "End-of-File" (CPF5001)
conditions. When these conditions occur, the num_bytes field of the _RIOFB_T
structure is updated and errno is set, but no exceptions are generated. For the
"Record not found" condition, this field is set to zero. For the "End-of-File"
condition, it is set to EOF.
If your program generates either of these conditions many times, then a significant
performance improvement may be seen using this option.
When a function is only called in a few places but executed many times, changing
the function to an inline function saves many function calls and results in
performance improvement.
The INLINE compile-time option allows you to request that the compiler replace a
function call with that function’s code in place of the function call. If the compiler
allows the inlining to take place, the function call is replaced by the machine code
that represents the source code in the function definition.
Inlining is a method that allows you to improve the run time performance of a
C++ program by eliminating the function call overhead. Inlining allows for an
expanded view of the program for optimization. Exposing constants and flow
constructs on a global scale allows the optimizer to make better choices during
optimization.
Function call performance can be improved if the system has all of the arguments
passed in registers. Since there are only a limited number of registers, in order to
increase the chance of having all arguments passed in registers, combine several
arguments into a class and pass the address of the class to the function. Since an
address is being passed, pass-by-reference semantics are used, which may not have
been the case when the arguments were being passed as individual variables.
You can use static class members, a better programming practice that gives better
performance.
The two types of record I/O supported by the ILE C/C++ run time are ANSI C
record I/O and ILE C record I/O.
#include <stdio.h>
#include <recio.h>
#define MAX_LEN 80
int main(void)
{
_RFILE *fp;
_RIOFB_T *iofb;
char buf[MAX_LEN + 1];
fp = _Ropen("MY_LIB/MY_FILE", "rr");
iofb =_Rreadn(fp, buf, MAX_LEN, __DFT);
while ( iofb->num_bytes != EOF )
{
buf[iofb->num_bytes] = "\0";
printf("%s\n", buf);
iofb =_Rreadn(fp, buf, MAX_LEN, __DFT);
}
_Rclose(fp);
}
If your program does not use all these values, you can improve your application's
performance by opening a file as shown:
By specifying riofb = N, only the num_bytes field, the number of bytes read or
written in the _RIOFB_T structure is updated. If you specify riofb = Y, all the fields
in the _RIOFB_T structure are updated.
Block Records
You can improve record I/O performance by blocking records. When blocking is
specified, the first read causes a whole block of records to be placed into a buffer.
Subsequent read operations return a record from the buffer until the buffer is
empty. At that time, the next block is fetched.
If you wish to block records when the FILE data type is used, open the file with
blksize=value specified, where value indicates the block size. If blksize is
specified with a value of 0, a block size is calculated for you when you open a file.
If you wish to block records when the _RFILE data type is used, specify blkrcd = Y
when you open the file.
_Rclose(fp);
The example code above prints up to 75 characters of each record that is contained
in the file. The second parameter for the _Rreadn(), NULL, allows you to
manipulate the record in the system buffer. An _RFILE structure contains the
in_buf and out_buf fields, which point to the system input buffer and system
output buffer, respectively. The example above prints each record by accessing the
system's input buffer.
The system buffer should always be accessed through the in_buf and out_buf
pointer in the _RFILE structure that is located in the <recio.h> header file.
Unpredictable results can occur if the system buffer is not accessed through the
in_buf and out_buf pointers.
By changing this example to the following, one call to _Ropen, and one call to
_Rclose is saved:
You can use IFS stream files, with performance similar to record I/O functions, by
specifying SYSIFCOPT(*IFSIO) on the CRTCMOD or CRTBNDC commands.
You should use the macro version of getc instead of fgetc() to read characters
from a file. See “Reduce the Number of Function Calls and Function Arguments”
on page 426. The macro version of getc() reads all the characters in the buffer
until the buffer is empty. At this point, getc() calls fgetc() to get the next record.
For the same reason, you should use putc() instead of fputc(). The macro version
of putc() writes all the characters in the buffer until the buffer is full. At this point,
putc() calls fputc() to write the record into the file.
Since stream I/O functions cause many function calls; reducing their use in your
application improves performance. The following illustrates calls to printf():
The two calls to printf() can be combined into a single call so that one call is
saved as follows:
Pointers
Using and comparing pointers can impact performance.
Open Pointers
Avoid using open pointers. Open pointers inhibit optimization. Note that pointers to
void (void*) are open pointers in ILE C++.
Pointer Comparisons
Since pointers take up 16 bytes of space, pointer comparisons are less efficient than
comparisons using other data types. You may want to replace pointer comparisons
with comparisons using other data types, such as int.
This is a program that constructs a linked list, processes all the elements in the list,
and finally frees the linked list. Each element in the link list holds one record from
a file:
#include <string.h>
#include <stdlib.h>
#include <recio.h>
#define MAX_LEN 80
struct link
{
struct link *next;
char record[MAX_LEN];
};
int main(void)
{
struct link *start, *ptr;
_RFILE *fp;
int i;
#define MAX_LEN 80
int i;
struct link
{
struct link *next;
short last;
char record[MAX_LEN];
};
int main(void)
{
struct link *start, *ptr;
_RFILE *fp;
if ( start != NULL )
{
for ( ptr = start; !ptr->last; ptr = ptr->next )
{
// Code to process the element pointed to by
while ( !start->last )
{
ptr = start->next;
free(start);
start = ptr;
}
free(start);
}
}
Note: Care must be taken that objects which use shallow copy do not destroy
objects pointed to more than once.
Space Considerations
You can improve the performance of a program by reducing the space it requires.
Reducing the space requirement helps reduce page faults, segment faults, and
effective address overflows.
Note: When choosing data types, consider all the platforms that your code must
support. You may not know all the data types and sizes at the beginning of
your code design. Since the data types can hold the same size data on
various platforms, you can use typedefs, enums, or classes depending on the
use of the data type.
In the code above, 96 bytes are taken from the heap (including 64 bytes for
bookkeeping and 16 bytes for padding) and new is used twice. This code can be
rewritten as:
ptr1 = new char[16];
ptr2 = ptr1 + 12;
Only 48 bytes are taken from the heap and the new operator is only used once.
Since you reduce the dynamic space allocation requirement, less storage is taken
from the heap. You may gain other benefits such as a reduction in page faults.
Since there are fewer calls to the new operator, function call overhead is reduced as
well.
Each data type is aligned on its natural boundary. pointers are aligned on 16 byte
boundaries, integers are aligned on 4 byte boundaries and packed decimals are
aligned on a 1 byte boundary. The alignment of a structure or a union is
determined by the largest alignment among its members. This class aligns on a 4
byte boundary because of the float member.
Note: To support portable code, you can place all the members of the same type
together (this may affect readability) starting with the largest (on most
systems) going to the smallest. An array has the same alignment as its
element type and must be positioned appropriately. This program that
follows shows this rule implicitly.
The size of the above structure is 12 bytes (not 8 bytes). This is because there is 1
byte of padding after member flag1 since member num aligns on a 2 byte
boundary, and there are 3 bytes of padding after member flag2 since the structure
is aligned on a 4 byte boundary.
The structure takes 176 bytes, of which 57 bytes are used for padding. It can be
rearranged as:
class ItemT
{
char *name; // 16 bytes
char *address; // 16 bytes
char *next; // 16 bytes
char *previous; // 16 bytes
char *title; // 16 bytes
double value; // 8 bytes
int quantity; // 4 bytes
int number; // 4 bytes
short rating; // 2 bytes
char flag; // 1 byte
_DecimalT<25,5> tot_order; // 13 bytes
_DecimalT<12,5> unit_price; // 7 bytes plus 9 bytes
}
itemT;
The class only takes 128 bytes, with 9 bytes for padding. The saving of space is
even more substantial when you have arrays of the above structure type.
As a general rule, the space used for padding can be minimized if 16 byte
variables are declared first, 8 byte variables are declared second, 4 byte variables
are declared third, 2 byte variables are declared fourth, and 1 byte variables are
declared fifth. _DecimalT template class objects should be declared last, after all
other variables have been declared. The same rule can be applied to structure or
class definitions.
Remove Observability
A module has observability when it contains data that allows it to be changed
without being compiled again. Two types of data can make a module observable:
Create Data This data is necessary to translate the code into machine
instructions. The object must have this data before you can change
the optimization level. It is represented by the *CRTDTA value on
the RMVOBS parameter of the Change Program (CHGPGM)
command.
Debug Data This data enables an object to be debugged. It is represented by the
*DBGDTA value on the RMVOBS parameter of the CHGPGM
command.
The addition of these types of data increases the size of the object. Consequently,
you may at some point want to remove the data in order to reduce object size.
However, once the data is removed, so is the object’s observability. To regain it,
you must recompile the source and re-create the program.
To remove either kind of data from a program or module, use the CHGMOD or
the CHGPGM command. Again, once you have removed the data, it is not possible
to change the object in any way unless you re-create it. Therefore, ensure that you
have access to all source required to create the program, or that you have a
comparable program object with create data.
Compress an Object
The Create Data (*CRTDTA) value associated with an ILE program or module may
make up more than half of the object’s size. By removing or compressing this data,
you reduce the secondary storage requirements for your programs significantly.
Activation Groups
Using activation groups can impact performance.
If a service program was created to run in a named activation group (using the
ACTGRP(name) parameter of the CRTSRVPGM command) then any calls to that
function from a program or service program would be calling across an activation
group and would therefore be slower. Sometimes it makes sense to run programs or
service programs in other activations groups (for storage isolation, exception
handling) but it should be noted that call-performance suffers in that arrangement.
Program Control
Virtual functions and operators can impact performance.
Optimization is the process through which the system looks for processing
shortcuts that reduce the amount of system resources necessary to produce output.
Processing shortcuts are translated into machine code, allowing the procedures in a
module to run more efficiently. A highly optimized program or service program
should run faster than it would without optimization.
You control the level of optimization through the OPTIMIZE option on the Create
Module and Create Bound Program commands. Changing the desired optimization
level requires recompiling your source code. Changing the optimization of a
module can also be accomplished through a CHGMOD command. Note that if the
optimization level is 10, you can not change the optimization level without
recompiling your source code.
Use the guidelines in Table 32, except where they are contradicted. Intrinsic
functions may improve performance, but they increase the size of your module.
Unless noted, these options are not set by default.
Table 32. Compiler Options for Performance
Option Optimize for Optimize for
Speed Size
Turns on optimization.
INLINE(*OFF) No Yes
INLINE(*ON) Yes No
DBGVIEW(*NONE) No Yes
Run-Time Limits
v The maximum amount of storage of any single variable (such as a string or
array) is 16 773 104 bytes
v The maximum length of a command passed to the system function is 32 702
bytes
v The maximum size of dynamic heap storage is 4 gigabytes
A very large memory allocation may cause a system crash if there is insufficient
auxiliary storage on your system. A 4 gigabtye memory allocation requires more
than 4 gigabytes of available DASD. The iSeries Work System Status
(WRKSYSSTS) command shows auxiliary storage usage.
GENCSRC provides the means to retrieve externally- described file information for
use in a C/C++ program. The command creates a C/C++ header file which
contains the typedef structure for the include file. The GENCSRC command lets
you create C/C++ include files. #pragma mapinc exploits GENCSRC internally,
and provides the opportunity to make DDS => include_file conversion directly
with the help of the GENCSRC command. GENCSRC can produce include files in
IFS file system, while #pragma mapinc produces headers only in database file
system; GENCSRC include files can be placed permanently anywhere, while
#pragma mapinc writes generated include files into QTEMP library.
The following table shows the comparison of #pragma mapinc options and the new
keywords for GENCSRC. For more information on any particular option, refer to the
description of #pragma mapinc in the VisualAge for C++ for AS/400 C++ Language
Reference manual.
Note: If you are developing new code, then follow the ANSI guidelines as much
as possible and avoid platform-specific extensions. In general, your code
should be portable. Platform specific extensions, for example, _Far16,
_Pascal16 are platform-specific pointers that are not portable.
Inter-language Calls
ILE C source code containing inter-language calls must be modified to run under
ILE C++. The extern linkage specification with the function definition or
declaration must be used instead of the #pragma linkage or #pragma argument
directives. The #pragma map directive has some semantic differences.
There is a difference in the way the #pragma argument directive and the extern
linkage specification handle function definitions. Both generate the same code
when processing a function call but the #pragma argument directive does not
affect parameters within the function definition. The extern linkage specification
does affect parameters within the function definition.
int main()
{
char *s;
foo (1, s);
}
int main()
This code shows how extern "OS" with a function definition is used to replace the
#pragma linkage directive. See Chapter 16, “Calling Conventions” on page 325 for
additional information. Instead of using
#pragma datamodel (p128)
typedef void (FUNC)(int);
#pragma linkage (FUNC, OS)
#pragma datamodel(pop)
not
typedef void (FUNC)(int)
extern "OS" FUNC; //error
This program shows the source to code a packed decimal data type in ILE C:
// ILE C program
#include <decimal.h>
void main()
{
int dig, prec;
decimal(9,3) d93;
dig = digitsof(d93);
prec = precisionof(d93);
}
#include <bcd.h>
void main()
{
int dig, prec;
This program shows you that by using the macros defined in <bcd.h>, you can use
the same ILE C shown in the first program:
// C++ program using the macro
#include <decimal.h>
void main()
{
int dig, prec;
decimal(9,3) d93;
dig = digitsof(d93);
prec = precisionof(d93);
}
Note: The <decimal.h> header file is specified since <decimal.h> includes the
<bcd.h> header file.
Member of a Union
Since an object of a class with a constructor cannot be a member of a union, the
_DecimalT class template in ILE C++ cannot be used as a member of a union.
Member of a Structure
In ILE C++ if a _DecimalT class template is a member of a struct, that struct
cannot be initialized with an initializer list. The structure in ILE C is:
typedef struct {
char s1;
decimal(5,3) s2;
}s_type;
Decimal Constant
The decimal constant defined using the suffix D or d is not supported by the C++
_DecimalT class template. Instead, a string literal embraced by __D is used to
represent a packed decimal constant. The decimal constant 123.456D defined in ILE
C equivalent to __D("123.456") in ILE C++.
switch int(op) {
The compiler flags the case statement indicating that the case expression is not an
integral constant expression.
to map the template class instantiation to the desired ILE C syntax. ILE C code
using the decimal(n,p) specifier can be ported to C++ without any modification.
The second type specifier supported by ILE C is not supported by the C++
compiler:
\\ decimal (constant-expression) \]
In this case, you need to insert a zero explicitly at the type specifier:
change decimal(10) to decimal(10,0)
Error Checking
In ILE C packed decimal is implemented as a native data type. This allows an
error such as invalid decimal format to be detected at compile time. In C++
detection of a similar error is deferred until run time:
#define _DEBUG 1
#include <bcd.h>
void main()
{
_DecimalT<10,20> b= __D("ABC"); // Run-time exception is raised
}
and
#define _DEBUG 1
#include <bcd.h>
void main()
{
_DecimalT<33,2> a; // Max. dig. allow is 31. Again,
// run-time exception is raised
}
Some errors can occur at compile time. When n<1, (_Decimal<-33,2>) then the error
is detected at compile time.
void main()
Extra Precision
The _DecimalT class template allows a maximum of 62 and 93 digits as the internal
results for the multiplication and division operations respectively. This is different
from the ILE C packed decimal data type in which a maximum of 31 digits is used
for both operations.
This internal result is different from the intermediate result. The internal result is
designated to store the temporary result during the operation. After the operation
is completed, the internal result is converted to the intermediate result and
returned to the caller.
Header File
In ILE C, the header file <decimal.h> must be included in the source prior any
usage of the packed decimal data type. In ILE C++ <bcd.h> must be included
instead.
Digits of an Object
In ILE C, when you use the __digitsof operator with a packed decimal data type
the result is an integer constant. The __digitsof operand can be applied to a
packed decimal data type or a packed decimal constant expression. The __digitsof
operator returns the total number of digits n in a packed decimal data type.
int n,n1;
decimal (5, 2) x;
In ILE C++ when you use the member function DigitsOf() with a _DecimalT class
template the result is an integer constant. The member function DigitsOf() can be
applied to a _DecimalT template class object. The member function DigitsOf()
returns the total number of digits n in a _DecimalT template class object.
int n,n1;
_DecimalT <5, 2> x;
Precision of an Object
When you use the __precisionof operator with a packed decimal data type the
result is an integer constant. The __precisionof operand can be applied to a
packed decimal data type or a packed decimal constant expression. The
__precisionof operator tells you the number of decimal digits p of the packed
decimal data type.
int p,p1;
decimal (5, 2) x;
In ILE C++ when you use the member function PrecisionOf() with a _DecimalT
class template the result is an integer constant. The member function
PrecisionOf() can be applied to a _DecimalT template class object. The member
function PrecisionOf() tells you the number of decimal digits p of the _DecimalT
template class object.
To determine the number of decimal digits p of the _DecimalT class template object:
#include <bcd.h>
int p,p1;
_DecimalT <5, 2> x;
To control the format of the printout use the flags, width and precision fields of the
printf() function.
To control the format of the scanf() function use the * and width fields of the
scanf() function. See the VisualAge for C++ for AS/400 C Library Reference for
information on these fields.
Print Function Flags: Table 33 describes the flag characters and their meanings for
D(n,p) conversions.
Table 33. Flag Meanings for Printing the Value of a _DecimalT Template Class Object
Flag Meaning
# The result always contains a decimal-point character, even if no digits follow it.
0 Leading zeros (following any indication of sign or base) are used to pad to the
field width; no space padding is performed.
- The result is always left-justified within the field.
+ The result always begins with a plus or minus sign.
space The result is always prefixed with a space where the result of a signed
conversion is no sign or the signed conversion results in no characters.
Print Function Field Width: The optional minimum field width for the printf()
function indicates the minimum number of digits to appear in the integral part,
fractional part or both parts of a _DecimalT template class object. If there are fewer
characters than the field width, then the field is padded with spaces. The field
width can be an *. If n is an *, the value of n is derived from the corresponding
position in the parameter list.
Print Function Field Precision: The optional precision for the printf() function
indicates the number of digits to appear in the fractional part of a _DecimalT
Conversion Specifiers: The conversion specifier for the printf() function is:
D(n,p) The _DecimalT template class object is converted in the style [ - ] ddd.ddd
where the number of digits after the decimal-point character is equal to the
precision of the specification. If the precision is missing, it is taken as 0; if
the precision is zero and the #flag is not specified, no decimal-point
character appears. If a decimal-point character appears, at least one digit
appears before it. The value is truncated to the appropriate number of
digits. The (n,p) descriptor is used to describe the characteristic of the
_DecimalT template class object. Both n and p have to be in the form of
decimal integers. If p is missing, a default value of zero is assumed. If the
specifier is in another form not stated above, the behavior is undefined.
If n and p of the variable to be printed do not match with the n and p in the
conversion specifier %D(n,p), the behavior is undefined. Use the unary operators
__digitsof (expression) and __precisionof (expression) in the argument list to
replace the * in D(*,*) whenever the size of the resulting class of a _DecimalT
template class object expression is not known.
Conditional Operator
In C++ both the second and third expressions must be of the same class. In ILE C
if both the second and third operands have an arithmetic type, the usual arithmetic
conversions are performed to bring them to a common type. In C++ if either
expression has a different class, then you must cast the second or third expression
so that it has the same class. The following illustrates an example where the
conditional expression fails because the second and third expressions are not of the
same class.
#include <bcd.h>
main()
{
int var_1;
decimal(4,2) op_1_1 = __D("12.34");
decimal(10,2) op_1_2 = __D("123.45");
var_1 = (op_1_1 < op_1_2) ? (op_1_1 + 3) : op_1_2;
}
To use the conditional operator with the _DecimalT template class you can do one
of the following:
v Use an explicit cast on the second expression so that it has the same type as the
third expression
v Use the same type of variables
main()
{
int var_1;
decimal(4,2) op_1_1 = __D("12.34");
decimal(10,2) op_1_2 = __D("123.45");
var_1 = (op_1_1 < op_1_2) ? (_DecimalT<10,2>)__D(op_1_1 + 3) : op_1_2;
}
main()
{
int var_1;
decimal(10,2) op_1_1 = __D("12.34");
decimal(10,2) op_1_2 = __D("123.45");
var_1 = (op_1_1 < op_1_2) ? op_1_1 : op_1_2;
}
Note: The + 3 was removed from the second expression since (op_1_1 + 3) results
in _Decimal<13,2>.
void main ()
{
decimal(4,0) d40 = 123D;
decimal(6,0) d60 = d40;
d60 = d40;
decimal(8,0) d80 = (decimal(7,0))1;
decimal(9,0) d90;
d60 = (decimal(7,0))12D;
d60 = (decimal(4,0))d80;
d60 = (decimal(4,0))(d80 + 1);
d60 = (decimal(4,0))(d80 + (float)4.500);
}
void main ()
{
_DecimalT<4,0> d40 = __D("123"); // OK
_DecimalT<6,0> d60 = __D(d40); // Because no constructor
// exists that can convert d40 to d60.
// macro __D is needed to convert d40
// into an intermediate type first.
d60 = d40; // OK. This is different from the
// previous statement in which
// the constructor was called.
_DecimalT<(9,0> d90; // OK
d60 = (_DecimalT<7,0>)__D("12"); // OK
d60 = (_DecimalT<4,0>)__D(d80);
d60 = (_DecimalT<4,0>)__D(d80 + 1);
C/C++ Enablement
Wrap your header files in the following construct:
.
.
.
#ifdef __cplusplus
extern "C" {
#pragma info(none)
#else
//only if you have #pragma
#pragma nomargins nosequence //nomargin and #pragma checkout in the
#pragma checkout(suspend) //header file
#endif
.
.
.
#ifdef __cplusplus
#pragma info(restored)
{
#else
#pragma checkout(resume)
#endif
}
The linkage specification extern "C" informs the compiler that all functions
prototyped will have C linkage. C++ linkage functions cannot be called from C
using the C++ internal name. See Chapter 16, “Calling Conventions” on page 325
for information on calling functions from other languages.
Packed Structures
The _Packed keyword tells the compiler to ignore the padding and pack the
structure as much as possible. In ILE C this keyword can be used in a structure
Therefore, you must make sure the _Packed keyword is only used in typedefs in
the header file.
In the ILE C/C++ compiler, the #pragma pack directive applies only to C
programs. The ILE C #pragma pack directive is not compatible with the Windows®
#pragma pack directive.
Function Prototype
To allow your header files to be used by ILE C and ILE C++ compilers, all
functions with "OS" linkage type must be dual prototyped:
#ifdef __cplusplus
extern "linkage-type" //linkage type "OS"
#else
#pragma linkage(function_name,linkage_type)
#endif
void function_name(...);
If you have a list of functions that need dual prototypes, you can:
#ifdef __cplusplus
extern "linkage-type" { //linkage type "OS"
#else
#pragma linkage(function_name1, linkage_type)
.
.
#pragma linkage(function_nameN, linkage_type)
#endif
void function_name1(...);
.
.
void function_nameN(...);
#ifdef __cplusplus
}
#endif
Type Checking
Type checking is stricter in C++ than it is in C. C++ allows void pointers to be
assigned only to other void pointers. In ANSI C, a pointer to void can be assigned
to a pointer of any other type without an explicit cast. See the VisualAge for C++ for
AS/400 C++ Language Reference for information on compatibility.
Assume some source uses memcmp() to compare a constant char array to a volatile
char array. The C compiler compiles this code without errors. Compiling the same
code with the C++ compiler will result in an error message, for example, Volatile
unsigned char cannot be converted to a const void pointer. You cannot use a
constant pointer where a volatile pointer is expected unless you cast it. You can
cast a void pointer to the appropriate pointer type before compiling the code.
Name Mangling
All C++ function names are mangled to enable function overloading. You receive
an undefined names error when you bind ILE C/C++ functions with mangled
names, for example, LocateSpaces__FPc. In ILE C, the service program relationship
isLocateSpaces__FPc == LocateSpaces or LocateSpace__FPc == LocateSpace
If you are porting ILE C code and you want to stop function name mangling then
use extern "C" around the function name.
File Inclusion
The include file name must be a valid workstation file name, for example
"file_name" or <file_name>. Include files cannot reference *LIBL or *CURLIB values.
You can use these values in ILE C include names. For example, ("*LIBL/ABC",
*LIBL/ABC/*All)"...).
If you use a back slash (\) character in your include file name, you must use a
double back slash (\\). For example, "c:\\abc\\aaa.h" for a fully qualified path
name or "proj1\\aaa.h" for a relative path include name. See the VisualAge for
C++ for AS/400 C++ Language Reference for information on file inclusion.
The type defined in a pointer declaration must match the type defined in the
function prototype in C++. You can assign a pointer to a different type from what
is defined in the function prototype by using an explicit cast expression:
CTT3055: "extern "C" void(*) (int) cannot be converted to "void (*) ()"
results from the type mismatch between the type defined in void pointer
("void(*) ()") and the type defined in the function prototype (void (*) (int)).
The int parameter does not exist in the function oldsig or the void pointer
declaration.
In another example, QXX functions return unsigned char pointers. ILE C allows
you to assign a signed char to an unsigned char pointer. This is not valid in C++.
Unsigned char pointers must be declared as unsigned char variables in the source
code as shown in the example source:
#include <xxcvt.h> //void(QXXITOP(unsigned char *pptr, int digits, int
//fraction, int value);
#include <stdio.h>
int main(void)
{
unsigned char pptr[10];
int digits = 3, fraction = 0;
int value = 116;
QXXITOP (pptr, digits, fraction, value);
}
You can assign the pointer to a variable of a different type by using a cast
expression. You can also use the DFTCHAR compiler option when you compile the
C++ source to set the default char type. See the ILE C/C++ Compiler Reference for
more information on these options.
In ANSI C, space for the trailing ’\0’ can be omitted in this type of information.
The following initialization, for instance, is not valid in C++:
char v[3] = "asd"; //not valid in C++, valid in ANSI C
//because four elements are required
This initialization produces an error because there is no space for the implied
trailing ’\0’ (zero of type char). The following initialization, for instance, is valid in
C++:
char v[4] = "asd"; //valid in C++
See the VisualAge for C++ for AS/400 C++ Language Reference for information on
compatibility.
String Literals
In ILE C strings are placed into read/write memory by default. In ILE C++ you
must use the #pragma strings directive to place strings into read/write memory.
The syntax of this pragma is:
C strings are read/write by default. C++ strings are read only by default. This
pragma must appear before any C or C++ code in a file.
The C and C++ stream I/O default is integrated file system enabled using the ILE
C++ for iSeries compiler. The ILE C compiler defaults to C data management
stream I/O. If you have programs that use database or DDM files, your best choice
is to use the SYSIFCOPT(*NOIFSIO) compiler option. This ensures that you
compile your existing programs using the iSeries data management file system and
not the integrated file system. Compiling programs that use restricted database or
DDM files under the integrated file system results in a run-time error.
#include <stdio.h>
#include <stdlib.h>
#include <iostream.h>
#include <terminat.h>
void a();
void my_terminate();
int main() {
set_terminate(my_terminate);
try {
a();
}
catch(...) cout << "failed" << endl;
}
// File term.c
#include <stdio.h>
Templates may be used in C++ to declare and define classes, functions, and static
data members of template classes. The C++ language describes the syntax and
meaning of each kind of template. Each compiler determines the mechanism that
controls when and how often a template is expanded.
ILE C++ offers several alternative organizations with a range of convenience and
compile performance to meet the needs of any program. This information describes
those alternatives and the criteria you should use to select which one is right for
you.
See the VisualAge for C++ for AS/400 C++ Language Reference for a general
description of templates.
Note: The *CURRENT and *PREV compilers handle templates differently. The
*CURRENT compiler is compliant with ANSI 98, whereas the *PREV
compiler follows the documentation for the previous release.
When you use templates in your program, the ILE C++ compiler automatically
instantiates each defining template that is:
v Referenced in the source code
v Visible to the compiler (included as the result of an #include statement)
v Not explicitly defined by the programmer
If an program consists of several separate compilation units that are compiled
separately, a given template may expand in two or more of the compilation units.
For templates that define classes, inline functions, or static nonmember functions,
this is the desired behavior. These templates need to be defined in each
compilation unit where they are used.
For other functions and for static data members, which have external linkage,
defining them in more than one compilation unit would normally cause an error
when the program is bound. ILE C++ avoids this problem by giving special
treatment to template-generated versions of these objects. At bind time, ILE C++
gathers all template-generated functions and static-member definitions, plus any
explicit definitions, and resolves references to them in these ways:
v If an explicit definition of the function or static member exists, it is used for all
references. All template-generated definitions of that function or static member
are discarded.
You may have only one explicit definition of any external linkage template
instance.
In the template, the constructor function is defined inline. Assume the other
functions are defined using separate function templates in the file stack.c:
template <class Item, int size>
int Stack<Item,size>::operator << (Item item) {
if (top >= size) return 0;
stack[top++] = item;
return 1;
}
template <class Item, int size>
int Stack<Item,size>::operator >> (Item& item)
{
if (top <= 0) return 0;
item = stack[--top];
return 1;
}
The constructor has internal linkage because it is defined inline in the class
template declaration. In each compilation unit that uses an instance of the Stack
class, the compiler generates the constructor function body. Each unit uses its own
copy of the constructor.
In each compilation unit that includes the file stack.c, for any instance of the
Stack class in that unit, the compiler generates definitions for the following
functions (assuming there is no explicit definition):
Stack<item,size>::operator<<(item)
Stack<item,size>::operator>>(item&)
To use this method with the Stack template, include both stack.h and stack.c in
all compilation units that use an instance of the Stack class. The compiler
generates definitions for each template function. Each template function may be
defined multiple times, increasing the size of the module.
Note: Use the same compiler options to bind your modules that you use to
compile them.
CRTPGM PGM (MYLIB/MYPROG) MODULE(MYLIB/MYFILE)
This is especially important for options that control libraries, linkage, and code
compatibility.
For each header file with template functions that need to be defined, the compiler
generates a template-include file. The template-include file generates #include
statements in that file for:
v The header file with the template declaration
v The corresponding template-implementation file
v Any other header files that declare types used in template parameters
Note: If you have other declarations that are used inside templates but are not
template parameters, you must place or #include them in either the
template-implementation file or one of the header files included as a result
of the above three steps. Define any classes that are used in template
arguments and that are required to generate the template function in the
header file. If the class definitions require other header files, include them
with the #include directive. The class definitions are then available in the
template-implementation file when the function definition is compiled. Do
not put the definitions of any classes used in template arguments in your
source code.
foo.h
template<class T> void foo(T*);
hoo.h
void hoo(A*);
foo.c
template<class T> void foo(T* t)
{t -> goo(); hoo(t);}
other.h
class A {public: void goo() {} };
main.cpp
#include "foo.h"
#include "other.h"
#include "hoo.h"
int main() { A a; foo(&a); }
This requires the expansion of the foo(T*) template with class A as the template
type parameter. The compiler creates a template-include file TEMPINC\foo.cpp. The
file contents (simplified below) are:
#include "foo.h" //the template declaration header
#include "other.h" //file defining template type parameter
#include "foo.c" //corresponding template implementation
This does not compile properly because the header "hoo.h" did not satisfy the
conditions for inclusion but the header file is required to compile the body of
foo(A*). One solution is to move the declaration of hoo(A*) into the "other.h"
header file.
Before it invokes the binder, the compiler compiles the template-include files and
generates the necessary template function definitions. Only one definition is
generated for each template function.
The TEMPLATE parameter defaults to *NONE. The other options are *SRCDIR or
pathname, where pathname is an IFS directory. The compiler creates the specified
directory if it does not already exist. The applicable xlc qshell command option is
-qtempinc=dir, where dir is a directory name. You can specify a fully qualified
path name or a path name relative to the current directory.
If you specify a different directory for your template-include files, make sure that
you specify it consistently for all compilations of your program, including the bind
step.
Note: After the compiler creates a template-include file, it may add information to
the file as each compilation unit is compiled. The compiler never removes
information from the file. If you remove function instantiations or reorganize
your program so that the template-include files become obsolete, you may
want to delete one or more of these files and recompile your program. If
error messages are generated for a file in the TEMPINC directory, you must
either correct the errors manually or delete the file and recompile. To
regenerate all of the template-include files, delete the TEMPINC directory, the
modules, and recompile your program.
If you do not delete the modules, MAKEFILE rules prevent the modules from
being recompiled, and the template-include files cannot be updated with all the
lines needed for all the compilation units used in the program. The end result is
that the bind fails.
A Template-Implementation File
In the Stack source, the file stack.c is a template-implementation file. To create a
program using the Stack class template, stack.h and stack.c must reside in the
same directory. You include stack.h in any source files that use an instance of the
class. The stack.c file does not need to be included in any source files. Given the
source file:
#include "stack.h"
void Swap(int i&, Stack<int,20>& s)
{
int j;
s >> j;
s << i;
i = j;
}
The path is used to specify the path name for the template-implementation file. If it
is only a partial path name, it must be relative to the directory containing the
header file.
Note: This path is a quoted string following the normal conventions for writing
string literals. Backslashes must be doubled.
In the Stack class, to use the file stack.def as the template-implementation file
instead of stack.c, add the line: #pragma implementation("stack.def") anywhere
in the stack.h file. The compiler then looks for the template-implementation file
stack.def in the same directory as stack.h.
A Template-Include File
A typical template-include file generated by the compiler shows this information:
/*0000000000*/ #pragma sourcedir("c:\swearsee\src") 0
/*0698421265*/ #include "c:\swearsee\src\list.h" 1
/*0000000000*/ #include "c:\swearsee\src\list.c" 2
/*0698414046*/ #include "c:\swearsee\src\mytype.h" 3
/*0698414046*/ #include "c:\IBMCPP\INCLUDE\iostream.h" 4
#pragma define(List<MyType>) 5
ostream& operator<<(ostream&,List<MyType>); 6
#pragma undeclared 7
int count(List<MyType>); 8
0 This pragma ensures that the compiler looks for nested include files in the
directory containing the original source file, as required by the ILE C++ file
inclusion rules.
1 The header file that corresponds to the template-include file. The number
in comments at the start of each #include line (for this line
/*0698421265*/) is a time stamp for the included file. The compiler uses
this number to determine if the template-include file is current or should
be recompiled. A time stamp containing only zeroes (0) as in line 2
means the compiler is to ignore the time stamp.
2 The template-implementation file that corresponds to the header file in line
1.
3 Another header file that the compiler requires to compile the
template-include file. All other header files that the compiler needs to
compile the template-include file are inserted at this point.
4 Another header file required by the compiler. It is referenced in the
function declaration in line 6.
5 The compiler inserts #pragma define directives to force the definition of
template classes. In this case, the class List<MyType> is to be defined and
its member functions are to be generated.
6 The operator<< function is a nonmember function that matched a template
declaration in the list.h header file. The compiler inserts this declaration
to force the generation of the function definition.
7 The #pragma undeclared directive is used only by the compiler and only
in template-include files. All template functions that are explicitly declared
in at least one compilation unit appear before this line. All template
functions that are called, but never declared, appear after this line. This
Note: Although you can edit the template-include files, it is not normally
necessary or advisable to do so.
Use #pragma define directives to force the compiler to generate the necessary
definitions for all template classes used in other compilation units. Use explicit
declarations of non-member template functions to force the compiler to generate
them.
To use the second method, include stack.h in all compilation units that use an
instance of the Stack class, but include stack.c in only one of the files. If you
know what instances of the Stack class are being used in your program, you can
define all of the instances in a single compilation unit:
#include "stack.h"
#include "stack.c"
#include "myclass.h" // Definition of "myClass" class
#pragma define(Stack<int,20>)
#pragma define(Stack<myClass,100>)
The #pragma define directive forces the definition of two instances of the Stack
class without creating any object of the class. Because these instances reference the
member functions of that class, template function definitions are generated for
those functions. See the VisualAge for C++ for AS/400 C++ Language Reference for
information about the pragma directive.
When you use these methods, you may also need to specify the
TEMPLATE(*NONE) compiler option to suppress automatic creation of TEMPINC
files. See ILE C/C++ Compiler Reference for more information about ILE C/C++
compiler options.
Introducing RTTI
The RTTI language extension was designed to address the difficulty encountered
by builders and users of major C++ libraries. The builders and users needed a
mechanism for extending base classes provided in libraries. The RTTI mechanisms
implemented by builders of the major C++ libraries were incompatible with each
other. This meant it could be difficult to use more than one library for a given
program, or to use the same program with different libraries without major
changes to the program. Since the C++ language supports the reuse of code and
the building of programs from parts, the incompatibilities of the RTTI mechanisms
used internally by the various C++ libraries presented a challenge to the purpose
of C++. A language-supported mechanism was needed.
The dynamic_cast operator makes downcasting much safer than conventional static
casting. It obtains a pointer to an object of a derived class that is given a pointer to
a base class of that object. The operator dynamic_cast returns the pointer only if
the specific derived class actually exists. If not, it returns zero.
The operator converts the expression to the desired type type_name. The type_name
can be a pointer or a reference to a class type. If the cast to type_name fails, the
value of the expression is zero.
In this class hierarchy, dynamic casts can be used to include the manager::bonus()
function in the manager’s salary calculation but not in the calculation for a regular
employee. The dynamic_cast operator uses a pointer to the base class employee,
and gets a pointer to the derived class manager in order to use the bonus() member
function.
void payroll::calc (employee *pe) {
// employee salary calculation
if (manager *pm = dynamic_cast<manager*>(pe)) {
// use manager::bonus()
}
else {
// use employee’s member functions
}
}
Note: In the above program, dynamic casts are needed only if the base class
employee and its derived classes are not available to users (as in part of a
library where it is undesirable to modify the source code). Otherwise,
adding new virtual functions and providing derived classes with specialized
definitions for those functions is a better way to solve this problem.
Since there is no such thing as a zero reference, it is not possible to verify the
success of a dynamic cast using reference types by comparing the result (the
reference that results from the dynamic cast) with zero. A failing dynamic cast to a
reference type throws a bad_cast exception.
A dynamic cast with a reference is a good way to test for a coding assumption.
The employee example above using reference casts is:
void payroll::calc (employee &re) {
// employee salary calculation
try {
manager &rm = dynamic_cast<manager&>(re);
// use manager::bonus()
}
catch (bad_cast) {
// use employee’s member functions
}
}
Note: This program is only intended to show the dynamic_cast operator used as a
test. This example does not demonstrate good programming style, since it
uses exceptions to control execution flow. Using dynamic_cast with pointers,
as shown above is a better way.
These examples use the typeid operator in expressions that compare the run-time
type of objects in the employee class hierarchy:
// ...
employee *pe = new manager;
employee& re = *pe;
// ...
typeid(pe) == typeid(employee*) // 1. True - not a polymorphic type
typeid(&re) == typeid(employee*) // 2. True - not a polymorphic type
typeid(*pe) == typeid(manager) // 3. True - *pe represents a polymorphic type
typeid(re) == typeid(manager) // 4. True - re represents the object manager
Similarly, re in comparison 2 represents the addess of the object referred to (that is,
not a polymorphic type); therefore, the expression typeid(re) returns the static
type of re, which is a pointer to employee.
The type returned by typeid represents the dynamic type of the expression only
when the expression represents a polymorphic type, as in comparisons 3 and 4.
Comparisons 5, 6, and 7 are false because it is the type of the expression (pe) that is
examined, not the type of the object pointed to by pe.
These examples do not directly manipulate type_info objects. Using the typeid
operator with built-in types requires interaction with type_info objects:
int i;
// ...
typeid(i) == typeid(int) // True
typeid(8) == typeid(int) // True
// ...
The result of the typeid and dynamic_cast operations is undefined if the operand
refers to an object under construction or destruction, and if the static type of the
operand is not an object of the constructor’s or destructor’s class or one of its
bases.
Additional operations for destroy and deallocation of memory are also provided to
detect an exception that occurs during construction. These operations are:
v Destruction of an object
v Destruction of an array of objects
v Descruction of memory for an object
virtual void* copy (void* to, const void* from) const=0; //object
virtual void* copy (void* to, const void* from, size_t count) const=0;
//array
Explanation of terms:
size() Size of the type represented by the extended_type_info object.
create(void*)
This function is called to create an object of the type represented by the
extended_type_info object at the storage location pointed to by at.
create(void*, size_t)
This function is called to create an array of objects of the type represented
by the extended_type_info object at the storage location pointed to by at.
If any exceptions are thrown during construction, create() destroys the
array elements that were already constructed before rethrowing the
exception.
copy(void* to, const void* from)
This function is called to copy an object of the type represented by the
extended_type_info object into the storage location pointed to by to, using
the value of the object referred to by from.
copy(void* to, const void* from, size_t)
This function is called to copy an array of objects of the type represented
by the extended_type_info object into the storage location pointed to by
to, using the value of the object referred to by from. If any exceptions are
thrown during construction, copy() destroys the array elements that were
already constructed before rethrowing the exception.
destroy(void*)
This function is called to destroy an object of the type represented by the
extended_type_info object at the storage location pointed to by at.
destroy(void*, size_t)
This function is called to destroy an array of objects of the type
represented by the extended_type_info object at the storage location
pointed to by at. If any exceptions are thrown during destruction,
Bibliography 475
Utility (BGU), advanced function printing (AFP), and examples of working with
the AS/400 system printing elements such as how to move spooled output files
from one output queue to a different output queue. Also includes an appendix
of control language (CL) commands used to manage printing workload. Fonts
available for use with the AS/400 system are also provided. Font substitution
tables provide a cross-reference of substituted fonts if attached printers do not
support application-specified fonts.
v REXX/400 Programmer’s Guide, SC41-5728-00, provides a wide-ranging discussion
of programming with REXX on the iSeries system. Its primary purpose is to
provide useful programming information and examples to those who are new to
Procedures Language 400/REXX and to provide those who have used REXX in
other computing environments with information about the Procedures Language
400/REXX implementation.
v ILE RPG Programmer’s Guide, SC09-2507-03, provides information needed to
design, code, compile, and test RPG programs on the iSeries system. The manual
provides information on data structures, data formats, file processing, multiple
file processing, the automatic report function, RPG command statements, testing
and debugging functions, application design techniques, problem analysis, and
compiler service information. The differences between the RPG for AS/400
compiler, the System/38® environment RPG III compiler, and the
System/36®-compatible RPG II compiler are also discussed.
v iSeries Security Reference, SC41-5302-04, tells how system security support can be
used to protect the system and the data from being used by people who do not
have the proper authorization, protect the data from intentional or unintentional
damage or destruction, keep security information up-to-date, and set up security
on the system.
v Local Device Configuration, SC41-5121-00, provides step-by-step procedures for
initial installation, installing licensed programs, program temporary fixes (PTFs),
and secondary languages from IBM. This manual is also for users who already
have an AS/400 system with an installed release and want to install a new
release.
v System API Programming, SC41-5800-00, provides information for the experienced
application and system programmers who want to use the OS/400 application
programming interfaces (APIs). Provides getting started and examples to help
the programmer use APIs.
v System Operation, SC41-4203-00, provides information about handling messages,
working with jobs and printer output, devices communications, working with
support functions, cleaning up your system, and so on.
IBM may have patents or pending patent applications covering subject matter in
this document. The furnishing of this document does not give you any license to
these patents. You can send license inquiries, in writing, to:
Director of Licensing,
Intellectual Property & Licensing
International Business Machines Corporation,
North Castle Drive, MD - NC119
Armonk, New York 10504-1785,
U.S.A.
The following paragraph does not apply to the United Kingdom or any other
country where such provisions are inconsistent with local law:
INTERNATIONAL BUSINESS MACHINES CORPORATION PROVIDES THIS
PUBLICATION “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS
FOR A PARTICULAR PURPOSE. Some states do not allow disclaimer of express or
implied warranties in certain transactions, therefore, this statement may not apply
to you.
Any references in this information to non-IBM Web sites are provided for
convenience only and do not in any manner serve as an endorsement of those Web
sites. The materials at those Web sites are not part of the materials for this IBM
product and use of those Web sites is at your own risk.
Licensees of this program who wish to have information about it for the purpose
of enabling: (i) the exchange of information between independent created programs
and other programs (including this one) and (ii) the mutual use of the information
which has been exchanged, should contact:
IBM Canada Ltd.
Department 071
1150 Eglinton Avenue East
The licensed program described in this information and all licensed material
available for it are provided by IBM under terms of the IBM Customer Agreement,
IBM International Program License Agreement, or any equivalent agreement
between us.
This publication contains examples of data and reports used in daily business
operations. To illustrate them as completely as possible, the examples include the
names of individuals, companies, brands, and products. All of these names are
fictitious and any similarity to the names and addresses used by an actual business
enterprise is entirely coincidental.
This book is intended to help you create Integrated Language Environment C/C++
programs. It contains information necessary to use the Integrated Language
Environment C/C++ compiler. This book documents general-use programming
interfaces and associated guidance information provided by the Integrated
Language Environment C/C++ compiler.
400 IBM
AFP IBMLink
AS/400 Integrated Language Environment
AS/400e iSeries
Application System/400 OS/2
C/400 OS/400
CICS/400 RPG/400
COBOL/400 SAA
DB2 SQL/400
Eserver WebSphere
GDDM
UNIX is a registered trademark of The Open Group in the United States and other
countries.
Other company, product, and service names may be trademarks or service marks
of others.
The C++ language is consistent with the International Standard for Information
Systems - Programming Language C++ - ISO/IEC 14882:1998 standard.
Notices 479
480 ILE C/C++ Programmer’s Guide
Index
A CALL command
changes to parameters 76
Coded Character Set Identifier (CCSID)
(continued)
activation 30, 70 passing parameters to a program 75, specifying 404
activation groups 76 coded character sets 411
grouping 71 calling coding procedures and data items 65
leaving 56 C++ procedures 351 command
process 70 C++ programs 351 ACQPGMDEV parameter 237
reasons to use 30 CL 328 Add ICF Device (ADDICFDEVE) 237
adding conventions for dynamic program break 109
conditional breakpoints 97 calls 326 Change ICF File (CHGICFF) 237
programs to a debug session 93 ILE C 343 Change Module (CHGMOD) 125
service programs to a debug message queue 257 Create a Display File (CRTDSPF) 221
session 93 OPM COBOL 328 Create Binding Directory
additional types of exception OPM RPG 328 (CRTBNDDIR) 26
handlers 257 procedures 345, 346 Create Bound C Program 22
argument passing cancel handlers, using 278 Create Bound C Program
match data type requirements 346 CCSIDs recognized single-byte EBCDIC (CRTBNDC) 22, 90
operational descriptors 346, 349 CCSID 403 Create Bound C++ Program
attribute of an ILE C program 23 CEEMRCR 285 (CRTBNDCPP) 22
changing Create C Module (CRTCMOD) 24, 90
CCSID 405 Create C++ Module
B module 95 (CRTCPPMOD) 24
before starting debug 87 module optimization level 124 Create DDM file (CRTDDMF) 207
binary stream database files observability 123 Create ICF File (CRTICFF) 237
I/O considerations 207 optimization 123 Create Program (CRTPGM) 22, 67
binary stream display files optimization levels 123 ACTGRP(*NEW) 67
program devices 221 value of scalar variables, while BNDDIR parameter 66
binary stream ICF files debugging 113 default parameters 22
I/O considerations 236 value of variables 111 ENTMOD parameter 67
program devices 236 view of a module 96 OPTION (*DUPPROC) 67
binary stream save files character arrays, displaying 117 OPTION (*DUPVAR) 67
I/O considerations 253 Character Data Representation OPTION(*RSLVREF) 67
binary stream subfiles Architecture (CDRA) 403 Create Service Program
I/O considerations 221 character sets 411 (CRTSRVPGM) 65, 66
binary stream tape files characters ACTGRP(*CALLER) 66
I/O considerations 246 case conversion 411 EXPORT(*ALL) 65
binary streams 143 classification 411 EXPORT(*SRCFILE) 65
bindable api collating 411 OPTION(*DUPPROC) 66
CEEHDLR 281 ordering 411 OPTION(*RSLVREF) 66
CEEHDLU 281 checking debug 88
binder 26 errno value 260 End Debug (ENDDBG) 90
binder language global variable _EXCP_MSGID 261 End Program Export
EXPORT symbol 65 Machine Instruction template 124 (ENDPGMEXP) 65
reason to use 33 major/minor return code 261 Override Diskette File
STRPGMEXP optimization level 124 (OVRDKTF) 249
LVLCHK parameter 65 return value of a function 259 Override ICF Device
binding directory system exceptions for record (OVRICFDEVE) 237
reasons for creating 26 files 261 Override ICF File (OVRICFF) 237
block records 429 system exceptions for stream Start Debug (STRDBG) 90, 94
breakpoints files 261 Start Program Export
conditional 96 CL, calling 328 (STRPGMEXP) 65
remove at a statement number 98 classes, exception 269 Update Production files
setting 96 Coded Character Set Identifier (CCSID) (UPDPROD) 94
unconditional 96 changing 405 Work with Module (WRKMOD) 124
unconditional, setting and Character Data Representation command line, debug 88
removing 99 Architecture (CDRA) 403 common mechanism to return function
character set 403 results 327
code page 403 compile-time errors 320
C code points 403
definition 403
condition handler
move the resume cursor 285
C locale migration table 412 graphic characters 403 percolate an exception 283
Index 483
locales (continued) opening binary stream files (continued) pragma (continued)
environment variables 418 record at a time 156 exception_handler 268, 269, 273
ILE C for AS/400 support 412 opening text stream files externally described files 185
international locale support 411 fopen 147 pragma mapinc
library function 417 modes 147 database files 190
overview of ILE C support 411 OPM COBOL, calling 328 header description 186
run-time functions 419 OPM default activation groups 56 lvlchk option 187
setting an active locale 417 OPM RPG, calling 328 typedefs 185
LOCALETYPE option 416 optimization level pragma mapinc directive 63
locate mode 429 *FULL 124 preparing a program for debugging 88
locate run-time errors 87 *NONE 124 printer files
logical files changing 123 binary stream functions 243
multi-format 202 optimization, changing 123 FCFC 242
optimizing 123 I/O considerations 219, 243
OPTION indicators 219
M *EXPMAC 91
*LSTDBG 91
major/minor return codes 220
open as binary stream files 243
major/minor return code, checking 261
*SHOWINC 91 open as record files 243
major/minor return codes 220
*SHOWSKP 91 record functions 243
match data type requirements
*SHOWSYS 91 separate indicator areas 219
by reference 345
*SHOWUSR 91 procedure pointer calls 346
by value directly 345
*SRCDBG 91 procedures
by value indirectly 345
*XREF 64 calling 345, 346
module
overflow behavior 313 stepping into 110
changing 95
stepping over 109
changing the view 96
program
different views 96
effect of debug data on size 88 P debugging 87
devices
observability 125 packed decimal data
I/O feedback area 231
removing 125 conversion functions 200
effect of debug data on size 88
preparing for debugging 88 packed decimal data type
preparing for debugging 88
module object representation 309
stepping into 106
debug data 24 packed decimal types
stepping over 106
program entry procedure 24 pragma nosigntrunc directive 322
stepping through 106
user entry procedure 24 passing
program described files 201
module’s optimization level, arguments 345
program entry procedure (PEP) 24
changing 124 different methods 345
program source, viewing 95
multiple record formats, using 196 packed decimal arguments 315
public interface 33, 65
packed decimal value to a
function 313
N pointer to a packed decimal to a
function 314 Q
native language 411
pointers as arguments 304 QCAPCMD 76
nested exceptions 278
styles 345 QINLINE 145
newline, displaying 117
percolation 258 QUSRTOOL
no debug data 91
physical file 431 MAKE 20
null capable fields 205
physical files 201
null ended character arrays,
pointers
displaying 116
casting 303
casting constraints 303
R
reading binary stream files
constraints 300
O declaring pointer variables 300, 301
character at a time 152
record at a time 157
observability 125 function 299, 301
reading text stream files 149
observability, changing 123 invocation 299
record diskette files
open data path 204 label 299
blocking 250
open files 430 open 299, 300
I/O considerations 250
open modes for dynamically created passing pointers 304
read and write 250
stream files 143 pointers other than open
record display files
opening pointers 300
I/O considerations 222
database files as binary stream space 299
record field names 189
files 206 suspend 299
record files 141
DDM files as binary stream files 206 system 299
record format
display files as binary stream teraspace 395
_Rformat() function 188
files 221 types 299
definition 188
display files as record files 221 pragma
record ICF files
subfiles as binary stream files 221 argument 345
I/O considerations 237
opening binary stream files convert 403, 405
program devices 237
character at a time 150 disable_handler 268
Index 485
view (continued)
listing 91
program source 95
root source 90
W
writing binary stream files
character at a time 152
record at a time 156
writing text stream files 148
Z
zoned decimal data
using 199
SC09-2712-03