If you ever used the old PC DOS (also called MS DOS), you may be familiar with the PATH command. This was the DOS equivalent of OS/400 library lists. You entered a PATH command followed by the directories on your PC disk, and the system used that path to search for programs. But under PC DOS, where was this data stored? It was stored in something called the "environment."
IBM added the environment to OS/400 several versions ago, but few programmers take advantage of it. The environment is somewhat similar to the local data area in that it is unique to each job and it can be copied to submitted jobs from an interactive job.
Unlike a regular data area, however, the environment is not an object, and you don't create, rename, or delete it. It doesn't have a predefined size, and you don't refer to data in the environment with from and to positions.
The environment allows you to store data and retrieve it later in another program. There are two environments available, called "environment levels." The job level environment is created and destroyed along with each job on the system, and the system level environment is obviously systemwide and available to any job running on the system.
The job level environment is great for passing data between different types of programs. For example, it is extremely easy to pass data to the environment from within RPG IV and have that data read from the environment in a Java program.
Do I advocate using the environment? Yes, particularly for CGI RPG programming. You use the environment to communicate between the Web server (such as Apache) and the CGI program that is running. Therefore, you have to use the environment with CGI applications (probably the fastest-growing type of new RPG IV application is CGI RPG).
But the environment can also play a new role in day-to-day applications. For example, I recently wrote a program that extracted two values from a database record and returned them as parameter values. Initially, this application's design called for just those two values to be returned to the caller. While writing the program, I realized that the users will soon be asking me for additional information from the database records.
This database record is one you might see in your own shop; it contains standard information such as the full company name, screen titles, and a few other odds and ends. Some people store this information in a database file, others use a message file, and still others use a data area. Regardless, this program's job is to retrieve the company title and Internet domain. So I created an RPG IV program that read the infamous record 1 from the file and extracted the two pieces of information. Those two values were then copied to return parameters, and the program ended.
But in reviewing the information in the file, I realized that also it included the company address, company phone number, and other pieces of information. I thought for certain that at some point in the future, the users would add at least the phone number to the requirements list.
In fact, another program I was writing (also a CL program) would need the same information plus the company's phone number.
So I decided to provide the users with their required information via return parameters, but also store the other information in that database record in the environment. This way, rather than write another program or provide the users with too much function, I could write one application that provided them with their information via return parameters, while also solving the other application's requirements.
Environment CL Commands
To access the environment from CL, IBM created four CL commands:
- ADDENVVAR--Add an Environment Variable
- CHGENVVAR--Change an Environment Variable's Value
- RMVENVVAR--Remove (delete) an Environment Variable
- WRKENVVAR--Work with All Environment Variables
The ADDENVVAR command allows you to, from CL or Command Entry, create an environment variable. You specify the environment variable name and its associated value, along with the optional LEVEL parameter. LEVEL(*JOB) adds it to the job-level environment, and LEVEL(*SYS) adds it to the system level environment. ADDENVVAR also supports a REPLACE(*YES | *NO) option, making it effectively the same as the CHGENVVAR command.
The CHGENVVAR command allows you to modify an existing environment variable's value. If the variable doesn't exist, message CPFA981 is issued.
The RMVENVVAR command allows you to delete an environment variable. Normally, you delete an environment variable by setting its value to an empty string (''), but the CL command works fine, too.
The WRKENVVAR command is pretty nice. It allows you to display a traditional Work With panel of all the environment variables for the job or system. From that panel, you can run all the other environment variable commands (ADD, CHG, and RMV) as well as display and print.
As you can see (assuming you've entered one of them into the Command Entry display and pressed F4 to prompt), you set an environment variable's value by name, not position or record number. The variable and its value are stored, logically speaking, as follows:
COMPNAME=The Lunar Spacecraft Company
The environment variable is COMPNAME, and its value is 'The Lunar Spacecraft Company'. On other platforms, the internal implementation looks up an environment variable name and subsequently its value, searching the environment for the variable name followed by the equals sign (=). I don't know if the iSeries implementation works similarly, but it works.
Give Me the Value
Interestingly, IBM did not provide a RTVENVVAR (Retrieve Environment Variable) CL command. Apparently you can add, change, or delete environment variables from within CL programs, but you're not allowed to retrieve them. Why this oversight?
I have three theories on why it was omitted:
- Theory 1 is that IBM never expected anyone who uses CL to actually use the environment from within a CL program. So the RTVENVVAR never made it into the design.
- Theory 2 is that IBM planned on implementing a built-in function similar to %SST in CL, perhaps %ENVVAR('MYVARY'). Alas, a quick scan of the InfoCenter shows no such function as of V5R3.
- Theory 3 is more Cozzi-like in nature. I don't believe the developers of the environment commands knew how to write a CL command that allowed you to return a value of various lengths into a CL variable.
Assuming that any one of these theories could be correct, I decided to write a CL command called RTVENVVAR.
The RTVENVVAR command allows you to retrieve the value for a given environment variable. You specify an environment variable name and a CL return variable to receive the environment variable's value.
To get this to work, we need a command definition object. The command has two parameters.
- The ENVVAR parameter is the name of the environment variable that you want to retrieve. The command supports environment variable names of up to 256 characters. Environment variable names are case-sensitive, so CompName is different from COMPNAME.
- The RTNVAL parameter is used to specify a CL variable that will receive the value currently assigned to the environment variable. This CL variable must be TYPE(*CHAR) and can be up to 4096 bytes in length.
The source code for the RTVENVVAR command follows:
/* Command processing program is RTVENVVAR */
PARM KWD(ENVVAR) TYPE(*CHAR) LEN(256) MIN(1) +
EXPR(*YES) INLPMTLEN(17) +
PROMPT('Environment variable')
PARM KWD(RTNVAL) TYPE(*CHAR) LEN(1) RTNVAL(*YES) +
VARY(*YES) CHOICE('Environment var return +
value') PROMPT('CL Var. for return value')
The secret parameter that makes this thing work is the RTNVAL parameter. It contains both the RTNVAL(*YES) keyword and one of my favorites, VARY(*YES). In addition, the length for the RTNVAL parameter is LEN(1). This was done because CL commands allow you to place a CL variable that is as long as or longer than the parameter. So if I specified LEN(20), for example, you would only be allowed to specified a return variable that was declared with a length of at least 20 bytes. But by using a length of 1, any CL variable length is accepted.
RTNVAL(*YES) says that this parameter must contain a CL variable name. So the command analyzer does not allow you to specify non-CL variable names for the parameter.
VARY(*YES) says to pass an additional value to the command processing program (CPP) that indicates the length of this parameter's value. That is, it tells the RPG program how long the CL variable is. This allows the RPG program to return just enough information so that the return variable is filled but no extra memory is stepped on. I've used this parameter attribute for two decades now.
To compile this command, be sure to specify the ALLOW(*IPGM *BPGM *IMOD *BMOD) parameter. After 25 years, IBM still doesn't assume these values when the RTNVAL(*YES) keyword issued. Instead, you'll get this idiotic error message: "RTNVAL(*YES) requires ALLOW(*IPGM *BPGM) or ALLOW(*IMOD *BMOD) keyword." Give me a break!
The CPP (the term used to describe the program that actually does the work behind any user-written CL command) requires OS/400 V5R1 to compile. Starting with this issue, I will no longer go out of my way to write code that works on pre-V5R1 unless excessively bribed to do so.
The source code for the RTVENVVAR RPG IV program follows:
*************************************************************
** This program returns the environment variable's value.
** It is stuffed into the CL variable passed on the 2nd parm.
** (c) Copyright 2005 – Robert Cozzi, Jr. All rights reserved.
**************************************************************
** Template definition used for 2nd parameter
D CL_RTNVAR_T DS based(pNothing_T) QUALIFIED
D nLen 5I 0
D Data 32766A
** Program *ENTRY PList prototype
D RtvEnvVar PR
D envvar 256A
D rtnVar LIKEDS(CL_RTNVAR_T)
** Program *ENTRY PList
D RtvEnvVar PI
D envvar 256A
D rtnVar LIKEDS(CL_RTNVAR_T)
** Prototype for the "Get Env Var" API
D Qp0zGetEnv PR * ExtProc('Qp0zGetEnv')
D envvar * VALUE OPTIONS(*STRING)
D nCCSID 10I 0
D pEnv S * Inz
D nCCSID S 10I 0 Inz(0)
C eval *INLR = *ON
** Retrieve a pointer to the environment variable's value
C eval pEnv = Qp0zGetEnv(%TRIMR(ENVVAR):nCCSID)
** If nothing comes back, then
** the ENVVAR is bad, so return nothing.
C if pEnv = *NULL
C return
C endif
** Copy the environment variable to the return variable,
** being careful not to overstep the variable's length.
C eval %subst(rtnVar.Data:1:rtnVar.nLen)
C = %str(pEnv)
C return
The RPG program used as the CPP calls the Qp0zGetEnv API. This API retrieves an environment variable and the variable's CCSID. I don't much care about the CCSID, so I just throw that value away.
On the final line before the program returns, the %SUBST built-in function (which should have been named %SST, in my opinion) is used to copy the environment variable value to the returned CL variable. The length passed into the CPP is used to control how many bytes are impacted by the assignment statement. I also use the %STR built-in to convert the C-language-style, null-terminated string into a regular character field value.
The environment is very important if you're using CGI or Java to do work on the iSeries. But it can also be leveraged as a much simpler tool than data areas because no knowledge of the location of the data is required. Give it a try.
As always, the source code for this week's issue can be downloaded from my RPG Lab. Click on the "RPG Developer Source Code Examples" link.
Bob Cozzi is a programmer/consultant, writer/author, and software developer. His popular RPG xTools add-on subprocedure library for RPG IV is fast becoming a standard with RPG developers. His book The Modern RPG Language has been the most widely used RPG programming book for more than a decade. He, along with others, speaks at and produces the highly popular RPG World conference for RPG programmers.
LATEST COMMENTS
MC Press Online