Can I pass a variable to an RPG program and have it open the referenced file? use another file to hold a list of fields I want to read?
3.47 DSPATR (Display Attribute) Keyword
Use this field-level keyword to specify one or more display attributes for the field you are defining. You can specify the DSPATR keyword more than once for the same field, and you can specify more than one attribute for the same keyword. However, each attribute (for example, UL), can be specified only once per field.
Note: The effects of attributes may not appear on the display, depending on the hardware or software emulator you are using. The format for the keyword is one of the following:
DSPATR(attribute -1 [attribute -2 [attribute -3 [...]]]) or
DSPATR(&program-to-system-field);
If you specify more than one attribute for the same field, whether in one keyword or in separate keywords, each attribute that is specified (and in effect when the field is displayed) affects the field. For example, if you want a field to be displayed with its image reversed and with high intensity, specify either DSPATR (RI HI), or DSPATR(RI), and DSPATR(HI).
The program-to-system-field parameter is required and specifies that the named field must be defined in the record format, alphanumeric (A in position 35), length of one, and usage P (P in position 38). The program uses this P-field to set the display attribute for the field this DSPATR keyword applies to.
The name P-field is used for multiple fields with the record being defined. One DSPATR P-field is allowed per field. The P-field contains the display attribute and identifies whether the field should be protected. See "Valid P-field Values" in topic 3.47.3. The following are valid attributes for the first format of the DSPATR keyword:
For All Fields
Display Attribute Meaning BL Blinking field CS Column separator HI High intensity ND Nondisplay PC Position cursor
RI
Reverse image UL
Underline
For Input-Capable Fields Only Display Attribute Meaning MDT
Set changed data tag when displayed OID
Operator identification PR
Protect contents of field from input keying SP
Select by light pen Notes:
1. If you specify the UL, HI, and RI attributes on the 5250 display station for the same field, the result is the same as if you had specified ND.
2. If OID is specified, then SP should not be specified. Neither OID nor SP can be optioned unless specified with another display attribute.
3. Display attributes BL, CS, HI, RI, and UL can also be specified at the file, record, or field level as parameter values on the CHGINPDFT keyword.
4. Display attributes CS, HI, and BL can cause fields on the 5292, 3477 Model FC, 3487 Model HC, 3179, 3197 Model C1 and C2, and 3488 (5) color display stations to appear as color fields. See "COLOR (Color) Keyword" in topic 3.36 for more information.
5. If you are using an IBM Personal System/2* (PS/2)* computer that is emulating a 5250 display station and you are directly changing the EBCDIC screen buffer, you need to set the MDT attribute. See the IBM Personal Computer Enhanced 5250 Emulation Program Technical Reference manual for additional information.
6. If you are using a PS/2 computer and VGA monitor, the UL attribute does not work due to hardware specific limitations in the way buffers are used.
Option indicators are valid for this keyword, except when the attributes OID or SP are the only display attributes specified. Detailed descriptions of each of the attributes follow the coding example and sample display provided in Figure 152.
How do I use activation groups?
This answer is intended to be a starting point for ILE beginners. More than most FAQ answers, it is NOT intended to be the absolute, correct and only way to approach activation groups.
This FAQ is a work in progress, based mostly on the RPG400-L archive thread at http://archive.midrange.com/rpg400-l/200203/threads.html#00130
Everyone reading this should have already read the ILE Concepts manual (V5R2):
http://publib.boulder.ibm.com/iseries/v5r2/ic2924/books/c4156066.pdf
and the RPG IV Redbook:
http://www.redbooks.ibm.com/abstracts/sg245402.html
The activation group concept is intended to be a way to subdivide a job into smaller portions, especially in the areas of overrides and static memory. An activation group does NOT span jobs! The corollary is that programs which run in the same AG are intended to be developed as a single cooperative application.
Activation group strategies revolve around the various parameters
on the CRTRPGMOD, CRTBNDRPG, CRTPGM and CRTSRVPGM commands: NAG - Named activation group. Includes
*NEW - create a system-named group when the program is activated *CALLER - inherit the AG from the program that called this one DAG - Default activation group. Usually OPM, but can include *CALLER - inherit the AG from the program that called this one Remembering always that an AG is a subdivision of a job, why would we want to do such a thing? To answer that, we need to think about application design for a bit. In most OPM designs, a typical job might consist of a single CL program driver that calls several other CL
programs. They, in turn might issue an override and call an RPG program, like this:
CLMAIN CL01
OVRPRTF FORMTYPE()
RPG01 /* Print selected records */ RPG02 /* Mark records for deletion */ CL02
OVRPRTF FORMTYPE()
RPG03 /* Print totals */
RPG04 /* Clear totals for next month */
Let's modify RPG03 so that it uses a subprocedure. Because of that
subprocedure, we can no longer use DFTACTGRP(*YES) [As Jon Paris often
comments, we should really think of this parameter as OPMCOMPATIBILITYMODE.] What do we do?
If we use *NEW, then RPG03 will create a new AG, run there and then the system will delete the AG when RPG03 completes. That's a fair amount of overhead considering that we don't _need_ the job subdivided.
If give the activation group a custom name like BUCK, then we have two problems:
it up for us.
2) We need to worry about name collisions. What other programs in this job might use AG(BUCK) and accidentally share overrides? What other programs NEED to share overrides? Do they have the same AG name?
So while choosing a single named AG like your company name might seem like a good idea at first, you should think about using *CALLER. *CALLER allows you to compile and use subprocedures, but you don't have to worry about whay activation group to run in. Everything runs in the default AG, just as it always did! Programs running in the same AG are a designed to share resources. This strategy implies that you will never try to end the activation group (for instance with RCLACTGRP) and that you will never need to re-activate a program.
Service programs.
Service programs are like "procedure libraries." They provide the ability to semi-dynamically load code at runtime. "Semi" because once a service program is activated, it stays activated until the AG or job ends.
The implication is that you can't re-compile a program/ser vice program that runs in the DAG unless you get everybody out of it. That is, they won't see the change until their job (and DAG) ends.
Once you've set up a few service programs, you'll eventually run into the
scenario where you want the service program to be shared between G/L and A/R, but you want different overrides (or static memory) for each app. Now, you want to subdivide your job into different activation groups. You're an ILE programmer!
Enter *NEW/*CALLER.
By compiling the first A/R program as AG(*NEW), a new AG will be created every time ARMENU runs in the job. All the subsequent programs will inherit that AG. YOUR overhead is reduced because you don't need to keep track of who is using what private AG name, and the system will clean up the AG once all the programs are done with it.
MAINMENU DAG
ARMENU *NEW 5F4A716B (system generated) ARINQ *CALLER 5F4A716B (inherited)
CUSTSRVPGM *CALLER 5F4A716B (inherited) GLMENU *NEW 2A9F7E14 (system generated) GLINQ *CALLER 2A9F7E14 (inherited) CUSTSRVPGM 2A9F7E14 (inherited)
You can see that the system created TWO separate AGs and thatthe service program CUSTSRVPGM keeps the A/R and G/L apps separated from each other. So, you can keep a counter rolling of the number of accounts viewed today, and the counter for A/R will be different from the one for G/L.
This is impossible to do in OPM.
a technique describing how to declare arrays in RPG IV that had a variable number of array elements. Arrays of this nature are referred to as dynamic arrays. This
week, I am presenting a new technique for declaring dynamic arrays. This one does not have the comp lexities of the previous technique.
A major shortcoming in the previous technique that I illustrated was the need to allocate and reallocate memory dynamically based on a mathematical equation (the number of desired elements multiplied by the
length of a single element). In addition, the requirement that the number of array elements currently allocated had to be tracked by the program is undesirable. The technique was useable but not fun. This time, none of those shortcomings occur. About the only oddity is the use of a pointer, and the use of that pointer isn't complex at all. Here's the outline of this new technique:
? Declare the array with the BASED keyword.
? Get a pointer to a user space.
? Assign that pointer to the pointer in the BASED keyword.
Other than that, you can use the array as if it were dynamic, because it is now automatically growing as you access elements in the array. So, if one time you access five elements and another time you access 5,000 elements, your program will work, and none of the allocate/deallocate issues exist.
First things first. To create a dynamic array, you need to declare the array with the BASED keyword. Within the BASED keyword, specify the name of a field. The field name does not need to exist, and probably should not exit. If it does not exist, the RPG IV compiler automatically generates the correct declaration for it. If it does exist, it must be declared as a data type of pointer (*).
The following Definition statement declares an array named DYNARR and specifies the BASED keyword. The BASED(pArr) keyword identifies the based-on pointer field. Since there is no explicit declaration for that field, RPG IV automatically declares one for you.
...DName+++++++++++EUDS...Length+TDc.Functions+++++++++++++++
D dynArr S 200A Dim(32766) BASED(pArr)
The following two Definition statements have the same effect as the previous one; however, the pArr variable is explicitly declared on the first line. Therefore, the compiler does not need to declare one for you. This style is useful for more advanced programming in which, perhaps, you would leverage the pArr variable for more than one use, or you might use this style simply for completeness.
...DName+++++++++++EUDS...Length+TDc.Functions+++++++++++++++ D pArr S *
D dynArr S 200A Dim(32766) BASED(pArr)
Whenever a variable is declared and that declaration contains the BASED keyword (as in the examples above), the compiler does not allocate storage for the variable. That means that if you try to move something into DYNARR, you'll get a runtime error, because no storage has been allocated for the variable. When the BASED keyword is involved, you are telling the compiler that you will allocate the storage for the variable yourself. This could mean using the ALLOC/REALLOC opcodes or simply assigning the address of another variable to the pointer. See the example below.
...DName+++++++++++EUDS...Length+TDc.Functions+++++++++++++++ 0001 D pData S *
0002 D Data S 32A BASED(pData) 0003 D Real S 128A
...C..n01...OpCode(ex)Extended-factor2++++++++++++++++ 0004 C eval pData = %addr(Real)
In this example, the field DATA is declared as a 32-byte character field with the BASED keyword. The pointer field pDATA is explicitly declared on the prior statement. Initially, no storage is assigned to the pDATA pointer; therefore, the DATA has no storage associated with it.
To assign a value to the pDATA pointer, and consequently to provide storage for the DATA variable, an assignment statement is used (line 4). The %ADDR built-in function returns the memory location (i.e., the address) of the field identified by its first parameter. An address is the only type of data that may be stored in pointers.
Once this assignment is made, the data that has been allocated (automatically by the compiler) for the REAL variable is now also being used for the DATA variable. Overlapping fields? Yes.
Notice the variance in the field lengths. The DATA field is 32 bytes long, whereas the REAL field is 128 bytes long. This is perfectly fine as long as the REAL field is at least as long as the DATA field. If the situation were reversed, however, you'd run into a problem if you attempt to access byte 33 of the 32- byte field.
User Spaces as Dynamic Arrays
The safest way that I've found to dynamically allocate storage for a dynamic array is to not do it at all. That is, come up with a way to make the system safely and automatically allocate the storage for you. After all, isn't that the way a true dynamic array scheme would work if IBM did it for us? The big question is, however, what is there that would do such a thing? It occurred to me that a user space object (*USRSPC) could be just the right solution to this question.
User space objects are what data areas are based on. Space objects have been on this system for over 25 years, and user spaces have been around for as long as the AS/400's been around and then some. So they are a pretty reliable object to use.
By default, user space objects are fixed-size objects, just like a data area. However there are two interesting aspects of user space objects that help solve the dynamic memory problem.
1) User space objects have an attribute that controls whether the user space is fixed-length or variable- length. Changing that attribute to 1 causes the user space to become auto-extending. This means that if you create the user space with a length of 12 bytes and you attempt to read or write to byte 750, the underlying interface automatically extends the user space to at least 750 bytes. You do nothing special; it just happens!
2) Using the QUSPTRUS API, you can retrieve a pointer to a user space object that works and acts just like a pointer from the %ADDR built-in function.
Given these two facts, it occurred to me that I could just do something like this: ...DName+++++++++++EUDS...Length+TDc.Functions+++++++++++++++
0001 D dynArr S 200A Dim(32766) BASED(pArray) ...C..n01...OpCode(ex)Extended-factor2++++++++++++++++ 0002 C CallP QusPtrUs(szUS:pArray:apiError)
The field named szUS contains the name of the user space. The field name pArray is the return value that receives the pointer to the user space object's data, and apiError is the standard IBM-supplied API error data structure.
With just two lines of code, you can declare and assign the storage for a dynamically sized array. The best part is that you don't have to worry about deallocating or freeing up the storage for the dynamic array when you finish. Create the user space in QTEMP and forget about it!
The bad news is that if you now use something like the SORTA opcode, the entire array will be sorted and hence extend the user space up to the full size of the array. That may be OK if you're expecting that to happen, but you may get unwanted results if you expected it to only sort the elements with data in
them. Obviously, a full IBM-provided solution is needed, such as the rumored %SUBARR built-in function that may allow you to segment an array and work with the dynamically specified from and to elements.
Odds and Ends
The QUSPTRUS API is used to retrieve a pointer to the user space object's data. The APi can be easily called with the traditional CALL/PARM opcodes. But after all this is 2003, not 1983, so why not call it using a prototype? The source for the prototype follows:
...DName+++++++++++EUDS...Length+TDc.Functions+++++++++++++++ D QusPTRUS PR ExtPgm('QUSPTRUS')
D szUserspace 20A Const D pRtnPtr *
D apierror 16A OPTIONS(*VARSIZE)
Remember, the parameter names on a prototype are just placeholders or comments. They are not field declarations. So it doesn't matter what you call them, but you should take advantage of the fact that they are not declarations and use them in lieu of comments. For example, "szUserSpace" helps to signify that the field is character and is supposed to contain the name of a user space.
The apiError parameter is the standard API exception/error data structure. Unfortunately, the APIs lack consistency with respect to this data structure. Some of them require it to be passed as a parameter; on some, it is optional; and on others, there is an alternate format. For our purposes, the apiError data structure's format is declared as follows:
...DName+++++++++++EUDS...Length+TDc.Functions+++++++++++++++ D apiError DS Inz
D apiLen 10I 0 Inz(%size(apiError)) D apiRLen 10I 0
D apiMsgID 7A
D apiResv1 1A Inz(X'00') D apiErrData 10A
A Full Example
The example that follows can be compiled on your system and should provide you with an example of how this technique works. But first, you must create a user space with a relatively small size (larger size is OK, but we're only testing at this point). To create a user space, call the CrtUsrSpace procedure (included in the RPG ToolKit) from within your RPG IV progra m and specify a size of something like 32 bytes or so, as follows:
...C..n01...OpCode(ex)Extended-factor2++++++++++++++++ C Callp CrtUsrSpace(szUS : 32)
If you don't have the RPG ToolKit, you can key in and run the CL command and the RPG IV CPP listed in Figures 2 and 3 at the end of this article. That command performs the same function as the CrtUsrSpace procedure, but does it from within CL. Alternatively, you can call the QUSCRTUS API followed by the QUSCUSAT API to accomplish the same thing.
Once the user space has been created, and the source member listed in Figure 1 is compiled, use the STRDBG command to set a break point on the last RETURN opcode (last line of code). Once the
breakpoint is set, exit the debugger using F12 and then call the program. When the line containing the breakpoint is about to be run, the debugger will stop and display the source on the screen. At this point, place the cursor on the "dynArr(1700)" variable displayed in the source window and press F11. You should see "Hello World" in that element of the array.
If you do the math, you'll see that the size of an array element (200 in the example in Figure 1)
multiplied by the element number (1,700) comes to 340,000. This means that 340,000 bytes of storage would have been required in order for the array to successfully provide 1,700 elements. But since we are using a user space, we avoided any hand-coded allocation schemes and are indifferent about the
When I created a 32-byte user space and ran this program, the size of the user space automatically grew to 344,064 on my machine.
DftActGrp(*NO)
The following three lines are only used if
the RPG Toolkit (www.rpgiv.com/toolkit) is installed. They are not needed to make this example work. DEFINED(RTK_TOOLKIT)
TOOLKIT/QCPYSRC,space /ENDIF
...DName+++++++++++EUDS...Length+TDc.Functions+++++++++++++++ QusPTRUS PR ExtPgm('QUSPTRUS')