Learn the native dynamic external data mechanism we have had since System/38.
As an IBM i developer, you might be proud of the numerous dynamic features in either the OS level or the machine interface (MI) level that allow our developers to produce more reliable and maintainable software products with less effort. For example, the Override commands allow programmers to override (replace) a file that is used by a program or override certain parameters of the file being processed so that changes to either the actual file or manner of processing the file are transparent to the program that uses it.
The journal mechanism allows all changes to a physical file to be recorded as journal entries with no need of change to applications that use the physical file. Recorded journal entries can be used in bi-directional data recovery (applying journaled changes to or removing journaled changes from a physical file) or data replication. The Call/Return model, aka the external program-call model, provides a distinguished dynamic, late-binding program-invocation model. Similar invocation models appeared in some virtual machine architectures, such as the Java Virtual Machine (JVM), which was released in 1995, seventeen years after the announcement of System/38.
Just like the above-mentioned dynamic features, there's yet another dynamic feature that has been available since System/38—the external scalar data exported by OPM MI programs that can be resolved by name at runtime with or without dependency on the program that exports it. Since I haven't found any official naming of the data items exported by OPM MI programs, I'll refer to them as program-exported data in the remaining portion of this article.
In my opinion, program-exported data is a complement to the Call/Return program-call model. In this article, I hope to show you the power of combining the two in the application designs.
Program-Exported Data
What is program-exported data? And what is the difference between program-exported data and exported data items in ILE?
The Integrated Language Environment (ILE) introduced to AS/400 at V2R3 was an important effort made by IBM to implement a procedure-oriented, early binding program model (which is widely adopted by common platforms) in AS/400. If you are new to ILE, please refer to the ILE Concepts book for detailed information.
An ILE service program (*SRVPGM, a kind of MI program object with MI object type code hex 0203) can export either procedures or data items. A service program is similar to a dynamic-link library (DLL) in Windows or a shared library in UNIX-like systems. A consumer program of the procedures or data items exported by a service program is linked to the service program at compile time so that, at run time, the consumer program is "invoked," the service program is "loaded" (activated into a proper activation group) implicitly, and the exports become usable.
Just like a DLL or a shared library, a service program can also be "loaded" explicitly at run time; a consumer program can then resolve an individual export of the service program. The following table shows the steps to resolve a procedure or data item exported by a service program, a shared library, or a DLL in IBM i, Linux, and Microsoft Windows, respectively.
DLL Solutions for Resolving an Exported Data Item |
||
|
Load a DLL / Activate a *SRVPGM |
Resolve Exports |
IBM i Service Program |
The Activate Bound Program APIs (QleActBndPgm, QleActBndPgmLong) or the Activate Bound Program MI instructions (ACTBPGM, ACTBPGM2) activate a service program (identified by the input system pointer addressing the service program) in a specified activation group.
|
The Get Export (QleGetExp) and Get Export Long (QleGetExpLong) APIs resolve a pointer to an export (either data or procedure) either by name or export number. |
Linux Shared Library |
The function dlopen() loads the dynamic library file named by the null-terminated string filename and returns an opaque "handle" for the dynamic library. |
The function dlsym() takes a "handle" of a dynamic library returned by dlopen() and the null-terminated symbol (either a function or a variable) name, returning the address where that symbol is loaded into memory. |
Windows DLL |
The LoadLibrary or LoadLibraryEx Windows APIs load either a DLL or an executable file into the address space of the calling process. |
The GetProcAddress API returns the address of the exported function or variable from a specified DLL by name or ordinal value. |
In comparison with the above-shown DLL solutions, the distinguishing feature of program-exported data is that an exported data item can be resolved by searching the export name within all activations within the current MI process instead of searching an export within an activated/loaded service program/DLL. This feature decouples an exported data item from the program that actually exports it, and hence provides additional flexibility. As you will see in the remaining portion of this article, with the complement of program-exported data, the already powerful Call/Return program-invocation model of IBM i can be more powerful.
How Does an OPM MI Program Export a Data Item?
A scalar data item defined in an OPM MI program with external scope (aka externally accessible) is a program-exported data item. To make a scalar data item externally accessible, the EXT scope keyword is used in the Declare (DCL) statement that defines the scalar. A scalar data item with external scope must have the static addressability attribute; therefore, a program-exported data item exported by an OPM MI program is always allocated in the static storage frame of the program at runtime. The following example OPM MI code defines a program-export data item named YOUNG-LION:
DCL DD YOUNG-LION CHAR(64) STAT EXT INIT ( "A lion cub" ) ; |
Note that the EXT scope keyword is necessary, since the default scope attribute of a scalar is internal (INT). The STAT addressability keyword is not necessary, since in OPM MI the addressability of scalars defaults to STAT.
Also note that the INIT keyword in a DCL statement defines the initial value of a data item. When an OPM MI program containing program-exported data is activated, as static scalars of the program, the program-exported data items will be initialized as specified by the INIT keywords.
How Do You Access a Program-Exported Data Item?
The Resolve Data Pointer (RSLVDP) MI instruction resolves a program-exported data item by name and an optional system pointer to an activated program object. The RSLVDP instruction has three operands:
- Operand 1: A data pointer with addressability to and the attributes of the resolved program-exported data item
- Operand 2: A 32-byte character scalar indicating the name of the program-exported data item to resolve
- Operand 3: A system pointer to an activated program that exports the target data or null
If you are not familiar with the program activation process and related terms (such as activation entry), please refer to the Common Program Call Processing section in the documentation of either the Call External (CALLX) MI instruction or the Transfer Control (XCTL) MI instruction.
RSLVDP's document in the
Operand 3 specifies a system pointer that identifies the program whose activation is to be searched for the external scalar definition. If operand 3 is null, the instruction searches all activations in the activation group from which the instruction is executed, starting with the most recent activation and continuing to the oldest. The activation under which the instruction is issued also participates in the search. If operand 3 is not null, the instruction searches the activation of the program addressed by the system pointer.
Note the highlighted words "in the activation group from which the instruction is executed," which in my opinion, is incorrect. The resolution scope should always be one of the two default activation groups of the current MI process (i.e., job) rather than the activation group from which the instruction is executed. Which default activation group is chosen depends on the state in which the program that issues the RSLVDP instruction is running. If the program is running in user state, activation entries in the user state default activation group will be searched. If the program is running in system state, activation entries in the system state default activation group will be searched.
If a program-exported data item cannot be found, an External Data Object Not Found (hex 0604) exception is signaled.
After a program-exported data item is resolved and returned in the data pointer operand (operand 1), the returned data pointer can be used directly as operands of MI instructions that accept data pointer operands—for example, the Copy Bytes Left-Adjusted (CPYBLA) instruction and the Copy Extended Characters Left-Adjusted With Pad (CPYECLAP) instruction. You can also retrieve the addressability of the program-exported data by issuing a Set Space Pointer from Pointer (SETSPPFP) on the returned data pointer.
System built-ins that can be used in ILE high-level languages to resolve a program-exported data item include _RSLVDP2 and _RSLVDP3, the ILE RPG prototypes of which are the following (see mih-ptr.rpgleinc):
/** * @BIF _RSLVDP2 (Resolve Data Pointer (RSLVDP)) */ d rslvdp2 pr extproc('_RSLVDP2') d dta_ptr * d obj_name
/** * @BIF _RSLVDP3 (Resolve Data Pointer (RSLVDP)) */ d rslvdp3 pr extproc('_RSLVDP3') d dta_ptr * d obj_name d pgm_ptr * |
An invocation to _RSLVDP2 is equivalent to an invocation to _RSLVDP3 with pgm_ptr set to null.
The following is a pair of simple example programs. OPM MI program T083_B (t083_b.emi) exports two external data objects, named NOVEL and PERSONAL-INFO. ILE RPG program T083 (t083.rpgle) tries to resolve the exports of T083_B via _RSLVDP3 and _RSLVDP2, respectively.
T083_B
/** * @file t083_b.emi * * Exports: * - Character scalar named NOVEL * - Data structure named PERSONAL-INFO */
DCL DD NOVEL CHAR(32) EXT INIT("Family, Spring, and Fall") ;
DCL DD PERSONAL-INFO CHAR(64) EXT ; DCL DD NAME CHAR(32) DEF(PERSONAL-INFO) POS(1) INIT( "Sun Wukong" ) ; DCL DD AGE PKD(5,0) DEF(PERSONAL-INFO) POS(33) INIT( P"1500" ) ;
PEND ; |
To compile an OPM MI program, you may choose a wrapper program of the Create Program (QPRCRTPG) API (e.g., mic).
T083
/** * @file t083.rpgle * * Test of _RSLVDP3, _RSLVDP2. */ h dftactgrp(*no)
/copy mih-ptr /copy mih-pgmexec
d spc_ptr s * d ext_data_obj s d ext_ds ds qualified d based(spc_ptr) d name d age 5p 0 d ds d proc_ptr * procptr d dta_ptr * overlay(proc_ptr) d ssf s * d pgm s * d dta_name s
/free // [1] Test of _RSLVDP3 // [1.1] Resolve program object T083_B rslvsp_tmpl.obj_type = x'0201'; rslvsp_tmpl.obj_name = 'T083_B'; rslvsp2(pgm : rslvsp_tmpl);
// [1.2] Activate program T083_B actpg(ssf : pgm);
// [1.3] Resolve external data object NOVEL dta_name = 'NOVEL'; rslvdp3(dta_ptr : dta_name : pgm);
// [1.4] Obtain space pointer from dta_ptr spc_ptr = setsppfp(proc_ptr); dsply 'Novel' '' ext_data_obj;
// [2] Test of _RSLVDP2 deactpg1(pgm); // Deactivate PGM actpg(ssf : pgm); // Activate PGM again dta_name = 'PERSONAL-INFO'; // [2.1] Resolve external data object PERSEONAL-INFO rslvdp2(dta_ptr : dta_name);
// Obtain space pointer from dta_ptr spc_ptr = setsppfp(proc_ptr); dsply ext_ds.name '' ext_ds.age;
*inlr = *on; /end-free
/** Example Output * * 4 > call t083 * DSPLY Novel Family, Spring, and Fall * DSPLY Sun Wukong 1500 */ |
Notes
[1] Test of _RSLVDP3
[1.1] Resolve program object T083_B
[1.2] Activate program T083_B
[1.3] Resolve external data object NOVEL
[1.4] Obtain space pointer from dta_ptr
[2] Test of _RSLVDP2
[2.1] Resolve external data object PERSEONAL-INFO
A Polymorphism Example Based on Program-Exported Data
It is an important feature of program-exported data that the target program-exported data item can be resolved by export name independently of the program that actually exports the data item. It would be extremely flexible to be able to combine this feature with the dynamic features provided by the external program-call mechanism. In this section, I'll show you a polymorphism example implemented via program-exported data.
In this example, OPM MI program KITTEN (kitten.emi) exports a collection of methods that are expected to be implemented by an animal via a program-exported data item named ANIMAL-METHODS. Each method is identified by an instruction pointer addressing the method's starting MI instruction in KITTEN:
- Method chase() accepts no parameter
- Method taste() accepts an input character parameter, food
Another OPM MI program, PUPPY (puppy.emi), exports the same collection of methods as KITTEN does, while PUPPY's implementation of these methods is different.
ILE RPG program TST_ANIMAL (tst_animal.rpgle) consumes the methods of an animal object, despite having no knowledge of what exactly the animal is. TST_ANIMAL resolves a data pointer addressing the program-exported data ANIMAL-METHODS via the RSLVDP instruction, retrieves a space pointer addressing the exported data via the SETSPPFP instruction, retrieves a system pointer to the actual "animal" (the program object that actually exports ANIMAL-METHODS) by issuing the Set System Pointer from Pointer (SETSPFP) instruction on one of the "methods" (instruction pointers) in ANIMAL-METHODS, and then invokes methods of the animal object via the instructions stored in the export. See the embedded comments in the following source of TST_ANIMAL.
Source of the OPM MI programs KITTEN, PUPPY, and the ILE RPG program TST_ANIMAL are the following:
KITTEN
/** * @file kitten.emi */
dcl insptr @method parm ; dcl spcptr @p parm ; dcl ol pl-main(@method, @p) parm ext min(0);
entry *(pl-main) ext ; stpllen argc ; cmpnv(b) argc, 0 / eq(see-you) ;
b @method ; see-you: rtx * ;
dcl dd argc bin(4) auto ; /* Exports: methods of an animal */ dcl dd animal-methods char(64) STAT EXT bdry(16) ; dcl insptr @chase def(animal-methods) pos(1) init(chase) ; dcl insptr @taste def(animal-methods) pos(17) init(taste) ; dcl ptr * def(animal-methods) pos(33) ; dcl dd * char(16) def(animal-methods) pos(49) init( "KITTEN-MOTHODS" ) ;
/* Method Chase() */ chase: cpyblap msg, "A kitten chases mice.", " " ; %sendmsg(msg, 32) ; brk "CHASE" ; b see-you ;
/* Method Taste() */ taste: dcl dd food char(16) bas(*) ; cpybla msg(1:16), @p->food ; cpyblap msg(17:16), "taste bad :(", " " ; cmpbla(b) @p->food, "Fish" / neq(=+2) ; cpyblap msg(17:16), "TASTY!", " " ; : %sendmsg(msg, 32) ; brk "TASTE" ; b see-you ;
dcl dd msg char(32) auto ;
pend ; |
PUPPY
/** * @file puppy.emi */
dcl insptr @method parm ; dcl spcptr @p parm ; dcl ol pl-main(@method, @p) parm ext min(0);
entry *(pl-main) ext ; stpllen argc ; cmpnv(b) argc, 0 / eq(see-you) ;
b @method ; see-you: rtx * ;
dcl dd argc bin(4) auto ; /* Exports: methods of an animal */ dcl dd animal-methods char(64) STAT EXT bdry(16) ; dcl insptr @chase def(animal-methods) pos(1) init(chase) ; dcl insptr @taste def(animal-methods) pos(17) init(taste) ; dcl ptr * def(animal-methods) pos(33) ; dcl dd * char(16) def(animal-methods) pos(49) init( "PUPPY-MOTHODS" ) ;
/* Method Chase() */ chase: cpyblap msg, "A puppy chases cats.", " " ; %sendmsg(msg, 32) ; brk "CHASE" ; b see-you ;
/* Method Taste() */ taste: dcl dd food char(16) bas(*) ; cpybla msg(1:16), @p->food ; cpyblap msg(17:16), "taste bad :(", " " ; cmpbla(b) @p->food, "Meat" / neq(=+2) ; cpyblap msg(17:16), "TASTY!", " " ; : %sendmsg(msg, 32) ; brk "TASTE" ; b see-you ;
dcl dd msg char(32) auto ;
pend ; |
TST_ANIMAL
/** * @file tst_animal.rpgle * */ h dftactgrp(*no)
/copy mih-ptr /copy mih-pgmexec
d spc_ptr s * d methods ds based(spc_ptr) d chase * procptr d taste * procptr d no_more * d eye_catcher d ds d proc_ptr * procptr d dta_ptr * overlay(proc_ptr) d ssf s * d argds ds d insptr * procptr d argv * dim(2) d args * dim(3) overlay(argds) d pgm s * d dta_name s d food s
/free // [1] Resolve external data object ANIMAL-METHODS dta_name = 'ANIMAL-METHODS'; monitor; rslvdp2(dta_ptr : dta_name); on-error; // Error handling endmon;
// [2] Obtain a space pointer from dta_ptr so that // instruction pointers in data struture @var methods // become valid. spc_ptr = setsppfp(proc_ptr);
// [3] Retrieve a system pointer to the program object // which exports instruction pointer @var chase pgm = rpg_setspfp(chase); // Debug: ev %var(pgm)
// [4] Invoke method Chase() insptr = chase; callpgmv(pgm : args : 1);
// [5] Invoke method Taste() insptr = taste; argv(1) = %addr(food); callpgmv(pgm : args : 2);
*inlr = *on; /end-free |
Compile the example programs, run TST_ANIMAL like the following, and examine the output of TST_ANIMAL.
4 > call kitten 4 > call tst_animal A kitten chases mice. Meat bones taste bad :( 4 > rclrsc 4 > call puppy 4 > call tst_animal A puppy chases cats. Meat bones TASTY! |
Yeah, we did it!
LATEST COMMENTS
MC Press Online