Allocate and free up program storage at run time.
Many i5/OS APIs receive variable-length output parameters. Many i5/OS machine interface (MI) instructions expect variable-length output operands. Many algorithms implemented by user programs need to allocate program storage with unpredictable length until run time. All these facts lead to the need for allocating and freeing program storage at run time.
Here, I will discuss four dynamic program-storage management methods that you can use in ILE RPG.
- Dynamically allocating automatic storage by MI instruction MODASA
- Managing heap storage with ILE CEE APIs or heap management MI instructions
- Using teraspace storage in ILE RPG
- Using thread-level global dynamic storage via Pthreads thread-specific storage APIs
Dynamically Allocating Automatic Storage by MI Instruction MODASA
On i5/OS, automatic storage supplied to a program is called the "automatic storage stack." Before threads were introduced to i5/OS, the automatic storage stack of a program was allocated based on the program's activation group. After threads were introduced to i5/OS, the automatic storage stack of a program became allocated based on the thread in which the programming is running. There are two automatic storage segments within a thread—one for system-state programs and the other for user-state programs—and the two segments reside, respectively, in the system domain and the user domain. A user program has space addressability to the user-domain automatic storage stack. An interesting example of addressing a program's automatic storage stack is provided in Appendix A, Changing a Caller's Automatic Variables.
Unlike common operating systems, where automatic storage allocation in a procedure is decided at compile time by reserving space on the stack, an i5/OS program can modify (extend or truncate) automatic storage allocation in a certain procedure invocation at run time.
So what's the benefit of using dynamically allocated automatic storage over other run-time storage allocation methods? First, automatic storage is local to a procedure: you can forget it once the control flow leaves the procedure; there's no need to release or re-initialize the allocated automatic storage. Second, automatic storage is a thread-local resource, which means that it needs no synchronization in multi-threaded environments.
The MI instruction Modify Automatic Storage Allocation (MODASA) is responsible for extending and truncating automatic storage in a specific invocation of a procedure. This instruction's bound program access interface (system built-in for HLLs), _MODASA, does not provide the functionality to truncate allocated automatic storage. Here is the prototype of system built-in _MODASA.
/* MODASA, modify automatic storage allocation */ /* Note that unlike MI instruction MODASA, built-in _MODASA */ /* cannot be used to truncate ASF */ d modasa pr * extproc('_MODASA') d mod_size 10u 0 value |
_MODASA returns a space pointer addressing the allocated automatic storage. Operand mod_size should be greater than 0 and less than or equal to 16,773,119. When the automatic storage is extended, the extension is aligned on a 16-byte boundary. The extension is not initialized.
Look at this example ILE RPG program, T032, that uses dynamically allocated automatic storage as the receiver operand to MI instruction Materialize Authorized Users (MATAUU). With option hex 32, instruction MATAUU returns privately authorized user profiles to the MI object identified by the object operand. Here's the prototype of system built-in _MATAUU for ILE RPG.
/* MATAUU, materialize authorized users */ d matauu pr extproc('_MATAUU') d receiver * value d object * d option |
ILE RPG program T032 lists privately authorized user profiles to a given program object.
/** * @file t032.rpgle * * test of MATAUU. * retrieve privately authorized USRPRF to a PGM object */
/copy mih52
d mat_opt s d tmpl ds likeds(matauu_tmpl_t) d based(tmpl_ptr) d tmpl_ptr s * d authd ds likeds(auth_desc_long_t) d based(authd_ptr) d authd_ptr s *
d dec10 s * d len s 10i 0 d
d i_main pr extpgm('T032') d pgm_name
d i_main pi d pgm_name
/free
rslvsp_tmpl.obj_type = x'0201'; rslvsp_tmpl.obj_name = pgm_name; rslvsp2(dec10 : rslvsp_tmpl);
// materialize privately authorized profiles // using long description entry format mat_opt = x'32';
// how many bytes do we need to allocate? (1) tmpl_ptr = modasa(8); tmpl.bytes_in = 8; matauu(tmpl_ptr : dec10 : mat_opt); len = tmpl.bytes_out;
// really allocate receiver buffer (2) tmpl_ptr = modasa(len); tmpl.bytes_in = len; matauu(tmpl_ptr : dec10 : mat_opt);
// report returned privately authorized users authd_ptr = tmpl_ptr + 16; for dsply 'Privately Authed User' '' authd.usrprf_name; authd_ptr += AUTH_DESC_LONG_LENGTH; endfor;
*inlr = *on; /end-free |
Notes in code:
(1) Allocate 8 bytes for MATAUU's operand 1, and then call MATAUU to retrieve the necessary amount of bytes to allocate.
(2) Allocate necessary amount of bytes for MATAUU's operand 1.
To test ILE RPG program T032, you might grant specific authority for a program object to a given user profile and then call T032 and pass the name of the program object. Here's an example:
3 > CALL QCMD 4 > GRTOBJAUT OBJ(T031) OBJTYPE(*PGM) USER(DEMO) AUT(*USE) Authority given to user DEMO for object T Object authority granted. 4 > GRTOBJAUT OBJ(T031) OBJTYPE(*PGM) USER(LJL2) AUT(*EXCLUDE) Authority given to user LJL2 for object T Object authority granted. 4 > CALL PGM(T032) PARM('T031') DSPLY Privately Authed User DEMO ? *N DSPLY Privately Authed User DEMO ? *N DSPLY Privately Authed User LJL2 ? *N DSPLY Privately Authed User LJL2 ? *N |
Managing Heap Storage with ILE CEE APIs or Heap Management MI Instructions
Since heap storage is an activation group-level resource, it is also referred to as "activation group-based heap space storage" in MI documentation provided by IBM. The heap storage for an activation group is accessible by any thread with a program or procedure running in the activation group. A default heap space with heap identifier 0 is automatically available in each activation group. The default heap space is created when the first Allocate Activation Group-Based Heap Space (ALCHSS) instruction is issued against the default heap space. A program can create additional heap spaces in its activation group by issuing MI instruction Create Activation Group-Based Heap Space (CRTHS). The amount of heap space storage that can be allocated for a single heap space is
To illustrate heap space, the following table shows the attributes of an activation group's default heap space and the acceptable values of each heap space attribute.
Heap Space Attributes and Values |
||
Heap Attribute |
Value of an Activation Group's Default Heap Space |
Acceptable Values |
Maximum single allocation size |
( |
( |
Minimum boundary requirement |
16-byte boundary |
Min: 16 bytes |
Creation size advisory |
1 page bytes |
Min: 1 page bytes; Max: ( |
Extension size advisory |
1 page bytes |
Min: 1 page bytes; Max: ( |
Domain |
Domain is determined from the state of the program issuing the instruction |
System-domain; User-domain |
Allocation strategy |
Normal allocation strategy |
Normal allocation strategy; Force process space creation on each allocate |
Allow heap space mark ** |
A heap space mark is not allowed |
Allow heap space mark; Prevent heap space mark |
Transfer size |
1 page bytes |
Transfer the minimum storage transfer size for this object; Transfer the machine default storage transfer size for this object |
Create the heap space in the PAG |
The process access group (PAG) membership advisory value is taken from the activation group |
Do not create the heap space in the PAG; Create the heap space in the PAG |
Allocation initialization |
Heap space storage allocations are not initialized to the allocation value. |
Do not initialize allocations; Initialize allocations |
Overwrite freed allocations |
Heap space storage allocations are not overwritten to the freed value after being freed. |
Do not overwrite freed allocations; Overwrite freed allocations |
* The term "page" is defined to mean one or more basic storage units used by the machine to manage memory and DASD. The number of bytes in a page can be determined with option hex 12 of the Materialize Resource Machine Data (MATRMD) instruction. The typical value of 1 page is 4KB (4096).
** If a heap space mark is allowed, marking a heap space allows a subsequent Free Activation Group-Based Heap Space Storage from Mark (FREHSSMK) instruction or ILE CEE API Release Heap (CEERLHP), using the mark identifier returned in operand 1, to free all outstanding allocations that were performed against the heap space since the heap space was marked with that mark identifier. This relieves the user from performing a Free Activation Group-Based Heap Space Storage (FREHSS) instruction or ILE CEE API Free Storage (CEEFRST) for every individual heap space allocation.
The ILE RPG language provides operation codes and built-in functions to operate the default heap space of an activation group.
Opcodes and BIFs |
||
Operation |
Operation Code |
Language Built-in Function |
Allocate Storage |
ALLOC |
%ALLOC |
Free Storage |
DEALLOC |
(No corresponding BIF) |
Reallocate Storage |
REALLOC |
% REALLOC |
To gain full control over heap spaces, use ILE CEE heap APIs or heap management MI instructions. The following table lists ILE CEE heap APIs or heap management MI instructions that achieve same functionalities.
Heap Space Equivalents |
||
Operation |
ILE CEE APIs |
Heap Management MI Instructions |
Create a new heap |
||
Destroy an existing heap |
||
Allocate storage within a heap |
||
Free one previously allocated heap storage |
||
Change the size of previously allocated storage |
||
Mark a heap |
||
Free heap storage since the mark was specified |
For ILE RPG prototypes of heap management MI instructions, please refer to mih52.rpgleinc, ILE RPG header for MI instructions (system built-ins).
There are no real differences in functionality between ILE CEE heap APIs and heap management MI instructions. One can even mix them when working with heap storage.
Please note the following when creating a heap space that is allowed to mark:
- No heap identifier is required for CEERLHP or FREHSSMK. The heap identifier of the heap to be released is inferred from the mark address. The release operation may be issued from an activation group other than the activation group that owns the heap.
- Multiple marks can be maintained for each heap. Mark and release operations treat the heap in a fashion similar to a stack. A release operation frees all storage allocated after the specified mark operation, but not the storage allocated before the specified mark operation. For example, in the following ILE RPG program, T041, heap storage allocated before the target heap was marked with space pointer mark2 could not be released when issuing instruction FREHSSMK on the target heap with mark_identifier operand set to mark2.
/** * @file t041.rpgle * * test of heap management instructions */
/copy mih52
d CEEGTST pr d heap_id 10i 0 d size 10i 0 d ptr * d fc
d crt_tmpl ds likeds(crths_tmpl_t) d based(crt_tmpl_ptr) d crt_tmpl_ptr s * d heap_id s 10i 0 d mark s * d mark2 s * d len s 10i 0 d ptr s * d buf ds 32 based(ptr) d ptr2 s * d buf2 ds 32 based(ptr2)
/free
// create a new heap space by instruction CRTHS crt_tmpl_ptr = modasa(96); propb(crt_tmpl_ptr : x'00' : 96); crt_tmpl.max_alloc = x'FFF000'; // crt_tmpl.min_bdry = 16; crt_tmpl.crt_size = 0; // use system default value crt_tmpl.ext_size = 0; // use system default value crt_tmpl.domain = x'0000'; // system should chose the domain crt_tmpl.crt_option = x'2CF // normal allocation strategy // allow heap space mark // transfer the machine default storage transfer size // do not create the heap space in the PAG // initialize allocations // overwrite freed allocations // allocation value, '1' // freed value, '0' crths(heap_id : crt_tmpl);
// mark heap space sethssmk(mark : heap_id);
// allocate heap storage len = x'A00000'; // 10MB CEEGTST(heap_id : len : ptr : *omit);
sethssmk(mark2: heap_id);
CEEGTST(heap_id : len : ptr2: *omit);
// free heap storage by mark frehssmk(mark2); // heap storage pointed to by ptr2 is freed buf = 'ptr is NOT freed!'; // heap storage pointed to by ptr is NOT freed!
frehssmk(mark); // heap storage pointed to by ptr is freed
// destroy heap space deshs(heap_id);
*inlr = *on; /end-free |
Using Teraspace Storage in ILE RPG
A teraspace is a large, contiguous, process-local address space of 1TB size. A teraspace is not a space object, which means it cannot be referred to by using a system pointer, but it can be addressed with a space pointer. High-level languages (HLLs)—including ILE RPG, ILE COBOL, and ILE CL—are teraspace-enabled by default.
The following teraspace APIs exported by service program QSYS/QC2UTIL3 can be used to manage teraspace storage in an ILE RPG program.
- _C_TS_malloc—This function reserves a block of teraspace storage of size bytes. Unlike the _C_TS_calloc() function, _C_TS_malloc() does not initialize all elements to 0.
- _C_TS_calloc—This function reserves teraspace storage space for an array of num elements, each of length size bytes. The _C_TS_calloc()function then gives all the bits of each element an initial value of 0.
- _C_TS_free()—This function frees a block of teraspace storage.
- _C_TS_realloc—This function changes the size of a previously reserved teraspace storage block.
Here are the prototypes of teraspace APIs extracted from ts.rpgleinc.
/* * The _C_TS_calloc() function reserves teraspace storage space for an * array of num elements, each of length size bytes. The _C_TS_calloc() * function then gives all the bits of each element an initial value of * 0. */ d ts_calloc pr * extproc('_C_TS_calloc') d num 10i 0 value d size 10i 0 value
/* * The _C_TS_free() function frees a block of teraspace storage. */ d ts_free pr * extproc('_C_TS_free') d ptr * value
/* * The _C_TS_malloc() function reserves a block of teraspace storage of * size bytes. Unlike the _C_TS_calloc() function, _C_TS_malloc() does * not initialize all elements to 0. */ d ts_malloc pr * extproc('_C_TS_malloc') d size 10i 0 value
/* * The realloc() function changes the size of a previously * reserved teraspace storage block. */ d ts_realloc pr * extproc('_C_TS_realloc') d ptr * value d size 10i 0 value |
The most notable benefit of using teraspace storage might be the large, contiguous address space. Limited by the single-level storage model, you cannot allocate a single contiguous piece of automatic storage or heap storage larger than 16MB. When you really need a contiguous space larger than
h bnddir('QC2LE')
/copy mih52 /copy ts
d ptr s * d val ds qualified d based(ptr) d n 9p 0 d str
d pos s * d buf ds 3 based(pos)
/free
// allocate 32MB teraspace storage ptr = ts_calloc(1 : 33554432); val.n = 95; val.str = 'ABC';
// offset ptr += 16777216; val.n = 96; val.str = 'DEF'; ptr -= 16777216;
// search for character 'D' pos = memchr(ptr : 'D' : 33554432); if pos <> *NULL; dsply 'Get it!' '' buf; endif;
// free allocated teraspace storage ts_free(ptr);
*inlr = *on; /end-free |
Using Thread-Level Global Dynamic Storage via Pthreads Thread-Specific Storage APIs
Some i5/OS programmers believe that ILE RPG is not a multi-thread-capable language. It's not accurate to say a programming language is multi-thread-capable or not. Most general-purpose programming languages don't have thread-synchronization-intrinsic primitives. For example, there are no synchronization primitives in C language's or C++ language's keywords. It is the operating system, not a specific programming language, that provides thread capabilities. Microsoft Windows provides thread capabilities to HLLs with thread management APIs such as CreateThread(), and POSIX-confirmed UNIX-like operating systems provide thread capabilities to HLLs with the Pthread library. It is not the duty of a programming language to implement thread support for different target operating systems. A programming language with large and often cross-platform function library or class library support might be an exception. For example, Java provides thread support with a unified interface under different platforms and leaves the complexity of working with platform-specific thread functions to vendors who implement the Java Virtual Machine (JVM) on a specific platform.
In my opinion, it would be more accurate to say that ILE RPG is unaware of multi-threading than to say that ILE RPG is not a multi-thread-capable language. Historically, the RPG compiler leaves a large number of static variables in compiled RPG modules or RPG programs. This makes it more complex to design a procedure or program to be invoked in a multi-threaded environment in ILE RPG than in other programming languages, such as C or C++. But, as an i5/OS HLL, there's no essential difference between ILE RPG and other i5/OS HLLs in terms of managing threads or implementing procedures or programs to run in multi-threaded environments.
In V6R1, a new value, THREAD(*CONCURRENT) was introduced to the THREAD parameter of ILE RPG's control specification. If THREAD(*CONCURRENT) is specified, then multiple threads can run the procedures within the module at the same time and be completely independent of each other. By default, all the static storage in the module will be in thread-local storage, meaning that each thread will have its own copy of the static variables in the module, including compiler-internal variables. But what if we need thread-level dynamically allocated storage that is global to all procedures within an i5/OS thread? The answer is Pthreads thread-specific storage APIs. This set of APIs provides us a mechanism to safely allocate, consume, and free dynamic thread-private storage that is global to all procedures within a thread.
Pthreads Thread-Specific Storage APIs
The i5/OS exposes thread management functions by implementing the POSIX standard for threads (Pthreads). Thread-specific storage APIs are one group of APIs of Pthread APIs.
Thread-Specific Storage APIs |
|
API |
Description |
pthread_getspecific |
Retrieves the thread local storage (TLS) value associated with the key; pthread_getspecific() may be called from a data destructor. |
pthread_key_create |
Creates a TLS key for the process and associates the destructor function with that key. |
pthread_key_delete |
Deletes a process-wide TLS key. |
pthread_setspecific |
Sets the TLS value associated with a key. |
For ILE RPG prototypes of Pthread thread-specific storage APIs, please refer to pthread.rpgleinc.
Example ILE RPG Program Using Dynamically Allocated Thread-Specific Storage
ILE RPG program T038 starts two child threads in its main procedure that use different types of storage in different threads.
/** * @file t038.rpgle * * test of thread-specific APIs * * CL command to run t038: * SBMJOB CMD(CALL T038) ALWMLTTHD(*YES) * */
h bnddir('QC2LE')
/copy mih52 /copy pthread.rpgleinc /copy ts.rpgleinc
/* thread function */ d thread_main pr * d param * value
/* TLS destructor */ d tls_destructor pr d tls * value
/* procedure that consumes allocated TLS storage */ d func pr d tls_key 10i 0 value
d thd ds likeds(pthread_t) d dim(2)
d thd_parm_ds_t ds qualified d tls_size_ind d tls_key 10i 0
d thd_parm ds likeds(thd_parm_ds_t) d dim(2) d tls_key s 10i 0 d rtn s 10i 0 d status s * d tls_rls_proc s * procptr
/free
// create TLS key (1) tls_rls_proc = %paddr(tls_destructor); rtn = pthread_key_create(tls_key : tls_rls_proc); if rtn <> 0; // error handling endif;
// launch child threads (2) thd_parm(1).tls_key = tls_key; thd_parm(1).tls_size_ind = 'S'; // small TLS allocation thd_parm(2).tls_key = tls_key; thd_parm(2).tls_size_ind = 'L'; // large TLS allocation
rtn = pthread_create(%addr(thd(1)) : *null : %paddr(thread_main) : %addr(thd_parm(1)) ); if rtn <> 0; // error handling endif; rtn = pthread_create(%addr(thd(2)) : *null : %paddr(thread_main) : %addr(thd_parm(2)) ); if rtn <> 0; // error handling endif;
// wait for child threads pthread_join(thd(1) : status); pthread_join(thd(2) : status);
rtn = pthread_key_delete(tls_key);
*inlr = *on; /end-free
p thread_main b d thread_main pi * d param * value
d thd_parm ds likeds(thd_parm_ds_t) based(param) d tls s * d SMALL_TLS_ALC c 32 d LARGE_TLS_ALC c 33554432 32MB
/free
// allocate TLS (3) pthread_lock_global_np(); if thd_parm.tls_size_ind = 'S'; tls = %alloc(SMALL_TLS_ALC); elseif thd_parm.tls_size_ind = 'L'; tls = ts_malloc(LARGE_TLS_ALC); else; // error handling endif;
pthread_unlock_global_np();
// set TLS (4) pthread_setspecific(thd_parm.tls_key : tls);
// call func() which consumes allocated TLS (5) func(thd_parm.tls_key);
return *null; /end-free p thread_main e
p tls_destructor b d tls_destructor pi d tls * value
d rtn s 10i 0
/free // is TLS storage a teraspace allocation (6) rtn = testptr(tls : x'01');
if rtn = 0; // TLS storage is allocated from the default heap (7) dealloc tls; else; // TLS storage is allocated from Teraspace (7) ts_free(tls); endif;
/end-free p tls_destructor e
/* procedure that consumes allocated TLS storage */ p func b d func pi d tls_key 10i 0 value
d cvthc pr extproc('cvthc') d receiver * value d source * value d length 10i 0 value
d tid s d chtid s d tls s * d greeting s
/free
// get TLS storage tls = pthread_getspecific(tls_key);
// consume TLS storage tid = retthid(); cvthc( %addr(chtid) : %addr(tid) : 16); greeting = 'From ' + chtid + ': Ni hao!'; dsply 'Greetings' '' greeting;
/end-free p func e
/* EOF */ |
Code notes:
(1) Call pthread_key_create () to create a thread local storage (TLS) key, passing the procedure pointer to the TLS destructor procedure tls_destructor().
(2) Launch child threads, passing the returned TLS key.
(3) Allocate TLS in a specific thread. During the storage allocation process, Pthread APIs pthread_lock_global_np() and pthread_unlock_global_np() are used to synchronize possible race conditions on job-level storage resources, such as activation group-based heap storage or a job's teraspace storage.
(4) Call pthread_setspecific() to make the allocated TLS storage visible to all procedures within the current thread.
(5) Consume allocated TLS storage in other procedures of the current thread.
(6) Once a thread exits, the registered TLS storage destructor procedure will be called to release corresponding TLS storage used by the thread. This example uses MI instruction TESTPTR to test whether the allocated TLS is allocated from a job's teraspace storage or not.
(7) Release allocated TLS storage in the TLS storage destructor.
Methods of Dynamic Program Storage Management
I have discussed different methods of dynamic program storage management. Which one you should choose depends on your particular case. For example, if a small piece of procedure-local dynamic storage is needed when implementing a utility service program that would be invoked in multi-threaded environments, dynamically allocated automatic storage would be preferred over other types of dynamic storage.
There are even more methods to make use of dynamic allocated storage on i5/OS—for example, using temporary or permanent space objects or using shared memory APIs declared in <sys/shm.h>. The reason I decided to introduce the above-mentioned methods is that these methods are more convenient and suitable for ILE HLLs such as ILE RPG.
Appendices
Appendix A: Changing a Caller's Automatic Variables
As mentioned, a user program can address the user-domain automatic storage stack of the current thread by a space pointer. So it is not surprising that one procedure can address its caller procedure's or caller program's automatic storage after obtaining a space pointer to the automatic storage stack of the current thread.
Here are two ILE RPG programs that have been written just for fun. Program T040A calls program T040 from one of its subprocedures, func(), which has automatic data structure tom_sawyer. T040 obtains a space pointer to the current thread's automatic storage by MI instruction Materialize Invocation Attributes (MATINVAT) and then searches for caller program T040A's automatic data structure tom_sawyer and changes its content.
Caller Program T040A
/** * @file t * * call program T040 */
h dftactgrp(*no)
d func pr
/free func(); *inlr = *on; /end-free
p func b d func pi
d tom_sawyer ds d fb d fc
c call 'O45' c 'tom_sawyer' dsply tom_sawyer p func e |
Program T040 Called by T040A
/** * @file t040.rpgle * * @attention Program T040 makes changes to its caller program's * automatic storage. It should be called by T */
/copy mih52
d asf_tmpl ds likeds(matinvat_asf_selection_t) d based(asf_tmpl_ptr) d asf_tmpl_ptr s * d asf_rcv ds likeds(matinvat_asf_receiver_t) d asf_rcv_ptr s * inz(%addr(asf_rcv))
d spp_info_ptr s * d spcptr_info ds likeds(matptr_spcptr_info_t) d based(spp_info_ptr) d ptr s * d buf ds 32767 based(ptr) d str12 ds 12 based(ptr) d pos s 10i 0
/free
// init asf_tmpl asf_tmpl_ptr = modasa(matinvat_asf_selection_length); propb(asf_tmpl_ptr : x'00' : matinvat_asf_selection_length);
asf_tmpl.num_attr = 1; asf_tmpl.flag1 = x'00'; asf_tmpl.ind_offset = 0; asf_tmpl.ind_length = 0; asf_tmpl.attr_id = 2; asf_tmpl.flag2 = x'00'; asf_tmpl.rcv_offset = 0; asf_tmpl.rcv_length = 16;
// locate space pointer to ASF matinvat(asf_rcv_ptr : asf_tmpl_ptr);
// retrieve current offset into ASF SPCPTR spp_info_ptr = modasa(matptr_spcptr_info_length); spcptr_info.bytes_in = matptr_spcptr_info_length; matptr(spp_info_ptr : asf_rcv.asf_ptr);
// offset to the start of ASF SPCPTR ptr = asf_rcv.asf_ptr; ptr -= spcptr_info.offset;
// find 'Tom Sawyer' and change it pos = %scan('Tom Sawyer' : buf); if pos <> 0; // modify caller program's automatic storage ptr += pos - 1; str12 = 'Mark Twain'; endif;
*inlr = *on; /end-free |
Appendix B: Links and References
Fortress Rochester: The Inside Story of the IBM iSeries by Frank G. Soltis, ISBN-13: 978-1583040836
i5/OS V6R1 Programming Languages and programming toolkits
ILE RPG headers provided by the open-source project i5/OS Programmer's Toolkit
- mih52.rpgleinc, ILE RPG header for MI instructions (system built-ins)
- pthread.rpgleinc, ILE RPG header for Pthread APIs
- ts.rpgleinc, ILE RPG header for Teraspace APIs
For the latest versions of these ILE RPG header files, refer to http://i5toolkit.svn.sourceforge.net/viewvc/i5toolkit/rpg/ or directly check them out by svn from https://i5toolkit.svn.sourceforge.net/svnroot/i5toolkit/rpg.
LATEST COMMENTS
MC Press Online