Fill In Command Prompts with Last-used Values
Over the years in MC, there have been so many excellent utilities that it is hard to imagine being without them. A classic example is the List File Field Description (LSTFFD) utility, which was highlighted in “Programmer’s Toolbox: The List File Field Description (LSTFFD) Utility” in November 1998. With LSTFFD, commonly used tools can be usefully organized on a menu, with the slight drawback that the values last keyed are not retrieved. This can be frustrating on days when fingers and keyboard refuse to coordinate!
Part of the need for retrieving parameter values can be removed by validating whatever is keyed before the command-processing program (CPP) gets control. A sample validity-checking program (tailored to LSTFFD) is shown in Figure 1. Attaching this to LSTFFD, by using the Change Command (CHGCMD) command, means that you stay on the prompt screen until valid file details are entered.
Once validation is passed, the program also does some extra work to squirrel away valid values for future prompting. A message file is used for this, with a message identifier based on the last four digits of the user ID number from the user profile. (The program in Figure 2 creates this message file.) This scheme may need modification if user IDs exceed 9999 on your machine. IBM-supplied user profiles, such as QPGMR, could also yield unpredictable results.
With the parameter values stored away safely, a prompt override program (shown in Figure 3) can retrieve what was last keyed. Prompt override is a two-step process if the command definition contains parameters defined with keyparm(*yes). No key parameters are needed to accomplish this, so the initial display of the prompt screen is bypassed. The program simply retrieves the command string (and length) stored in the second-level text of the appropriate message description and returns it to the command for display. If there is an error (e.g., no message identifier yet for the current user), the string is blanked and its length is set to zero, displaying the default values.
The first parameter sent to a prompt override program is always the qualified name of the command. Therefore, it is possible to use a common prompt override program for many commands.
— Phil Hope
/*==================================================================*/
/* To compile/implement: */
/* */
/* CRTCLPGM PGM(XXX/FFD001VC) SRCFILE(XXX/QCLSRC) */
/* CHGCMD CMD(XXX/LSTFFD) VLDCKR(*LIBL/FFD001VC) */
/* */
/*==================================================================*/
FFD001VC: PGM PARM(&NAME1 &FORMAT)
DCL VAR(&NAME1) TYPE(*CHAR) LEN(20)
DCL VAR(&FORMAT) TYPE(*CHAR) LEN(10)
DCL VAR(&FNAME) TYPE(*CHAR) LEN(10)
DCL VAR(&LNAME) TYPE(*CHAR) LEN(10)
DCL VAR(&ERROR) TYPE(*CHAR) LEN(1)
DCL VAR(&UID) TYPE(*DEC) LEN(10 0)
DCL VAR(&USRNO) TYPE(*CHAR) LEN(10)
DCL VAR(&USRID) TYPE(*CHAR) LEN(10)
DCL VAR(&MSGID) TYPE(*CHAR) LEN(7)
DCL VAR(&FSTLVL) TYPE(*CHAR) LEN(80)
DCL VAR(&SECLVL) TYPE(*CHAR) LEN(1024)
CHGVAR VAR(&FNAME) VALUE(%SUBSTRING(&NAME1 1 10))
CHGVAR VAR(&LNAME) VALUE(%SUBSTRING(&NAME1 11 10))
/* Check for valid library name */
IF COND(&LNAME *NE ‘*LIBL’) THEN(DO)
CHKOBJ OBJ(&LNAME) OBJTYPE(*LIB)
MONMSG MSGID(CPF9800) EXEC(DO)
SNDPGMMSG MSGID(ERR0001) MSGF(FFDMSGF) +
MSGDTA(‘0000’ *CAT &LNAME) MSGTYPE(*DIAG)
CHGVAR VAR(&ERROR) VALUE(‘Y’)
ENDDO
ENDDO
/* Check for valid file name */
IF COND(&ERROR *NE ‘Y’) THEN(DO)
CHKOBJ OBJ(&LNAME/&FNAME) OBJTYPE(*FILE)
MONMSG MSGID(CPF9800) EXEC(DO)
SNDPGMMSG MSGID(ERR0002) MSGF(FFDMSGF) +
MSGDTA(‘0000’ *CAT &FNAME) MSGTYPE(*DIAG)
CHGVAR VAR(&ERROR) VALUE(‘Y’)
ENDDO
ENDDO
/* Send escape message if there was an error */
IF COND(&ERROR = ‘Y’) THEN(DO)
SNDPGMMSG MSGID(CPF0002) MSGF(QCPFMSG) MSGTYPE(*ESCAPE)
ENDDO
/* Else save the valid values for next time */
ELSE CMD(DO)
RTVUSRPRF RTNUSRPRF(&USRID) UID(&UID)
CHGVAR VAR(&USRNO) VALUE(&UID)
CHGVAR VAR(&MSGID) VALUE(‘USR’ *CAT %SST(&USRNO 7 4))
CHGVAR VAR(&SECLVL) VALUE(‘??FILE(‘ *TCAT &LNAME +
*TCAT ‘/’ *CAT &FNAME *TCAT ‘)’)
CHGMSGD MSGID(&MSGID) MSGF(FFDMSGF) SECLVL(&SECLVL)
MONMSG MSGID(CPF0000) EXEC(DO)
CHGVAR VAR(&FSTLVL) VALUE(‘LSTFFD Last Values for +
‘ *CAT &USRID)
ADDMSGD MSGID(&MSGID) MSGF(FFDMSGF) MSG(&FSTLVL) +
SECLVL(&SECLVL)
ENDDO
ENDDO
ENDPGM
Figure 1: This validity-checking program can do more than verify parameter values.
/*===================================================================*/
/* To compile: */
/* */
/* CRTCLPGM PGM(XXX/FFDCRTMSGF) SRCFILE(XXX/QCLSRC) + */
/* TEXT(‘Create LSTFFD Message File’) */
/* */
/* */
/*===================================================================*/
PGM
CRTMSGF MSGF(QGPL/FFDMSGF)
MONMSG MSGID(CPF0000)
ADDMSGD MSGID(ERR0001) MSGF(FFDMSGF) MSG(‘Library &2 +
is not valid.’) FMT((*CHAR 4) (*CHAR 10 0))
MONMSG MSGID(CPF0000)
ADDMSGD MSGID(ERR0002) MSGF(FFDMSGF) MSG(‘File &2 +
not valid.’) FMT((*CHAR 4) (*CHAR 10 0))
MONMSG MSGID(CPF0000)
ENDPGM
Figure 2: Use a message file to store command defaults.
/*==================================================================*/
/* To compile/implement: */
/* */
/* CRTCLPGM PGM(XXX/PMTOVRC) SRCFILE(XXX/QCLSRC) */
/* CHGCMD CMD(XXX/LSTFFD) PMTOVRPGM(*LIBL/PMTOVRC) */
/* */
/*==================================================================*/
PMTOVRC: PGM PARM(&QCMD &STRING)
DCL &QCMD *CHAR 20
DCL &CMD *CHAR 10
DCL &UID *DEC (10 0)
DCL &USRNO *CHAR 10
DCL &MSGID *CHAR 7
DCL &MSGF *CHAR 10
DCL &FILE *CHAR 20
DCL &SECLVL *CHAR 1024
DCL &STRING *CHAR 5700
DCL &DECLEN *DEC (5 0)
DCL &BINLEN *CHAR 2
MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR))
/* Construct the message identifier for the current user */
RTVUSRPRF UID(&UID)
CHGVAR VAR(&USRNO) VALUE(&UID)
CHGVAR VAR(&MSGID) VALUE('USR' *CAT %SST(&USRNO 7 4))
CHGVAR VAR(&CMD) VALUE(%SST(&QCMD 1 10))
/* Initialise the message file name */
IF COND(&CMD *EQ 'LSTFFD') THEN(DO)
CHGVAR VAR(&MSGF) VALUE('FFDMSGF')
ENDDO
/* Retrieve the last keyed values for the current user */
RTVMSG MSGID(&MSGID) MSGF(&MSGF) SECLVL(&SECLVL) +
SECLVLLEN(&DECLEN)
MONMSG MSGID(CPF0000) EXEC(DO)
CHGVAR VAR(&DECLEN) VALUE(0)
CHGVAR VAR(&SECLVL) VALUE(' ')
ENDDO
/* Return the comand string prefixed with the string length */
CHGVAR (%BIN(&BINLEN)) VALUE(&DECLEN)
CHGVAR VAR(&STRING) VALUE(&BINLEN)
CHGVAR VAR(&STRING) VALUE(&STRING *TCAT &SECLVL)
RETURN
ERROR:
SNDPGMMSG MSGID(CPF0011) MSGF(QCPFMSG) MSGTYPE(*ESCAPE)
ENDPGM
Figure 3: This prompt override program retrieves the values used most recently by the user.
Debug Active Programs
If you are running an interactive program and want to get into debug, you don’t have to end the program, start debug, and restart the program. Press Attn from within the program, get a command line, start debug for the program, add breakpoints, and return to the program via F12. The program stops whenever it hits any breakpoint you add. You can also end debug in the same manner without ending the program.
— John Chadwick DMC Consulting, Inc.
Editor’s Note: Pressing Attn may or may not give a command line. If your Attn program is QCMD or QUSCMDLN, you’ll get a command line. Another way to access a command line is to take System Request, option 3, to display the job; take option 16 to display commitment control status; and press F9.
New /INCLUDE Compiler Directive for RPG
In the release following V4R5, IBM will add the /INCLUDE compiler directive to RPG IV to solve a problem with the Create SQL ILE RPG Object (CRTSQLRPGI) command. To use /INCLUDE with releases before the next release is available, install the appropriate PTFs, as shown in Figure 4.
The problem is that SQL does not allow nested /COPY directives and does not obey conditional directives, such as /IF and /EOF. /INCLUDE is like /COPY, with one exception: The SQL preprocessor expands /COPY directives but ignores /INCLUDE. Source lines containing /INCLUDE are passed on to RPG without expanding the included file. The RPG compiler then expands the /INCLUDE directives.
If you use SQLRPGLE and want the SQL preprocessor to see the contents of the included file (because it has embedded SQL or definitions for host variables), use /COPY but be sure that the copied file does not contain conditional directives or /COPY directives. Use /INCLUDE if you want SQL to ignore the included file. In RPG programs that do not contain embedded SQL (i.e., source member type RPGLE), /INCLUDE behaves exactly like /COPY.
— Barbara Morris RPG Compiler Development
IBM Toronto Figure 4: The /INCLUDE directive, available by PTF, addresses limitations of the SQL preprocessor.
Release PTF
V4R2M0 SF62167 V4R4M0 SF61918 for TGTRLS(*CURRENT) V4R4M0 SF63132 for TGTRLS(*PRV)
Concatenating Null-terminated Strings by Using Pointers
Q: I am writing a Sockets program and need to concatenate multiple recv( ) buffers together to get one string. However, I cannot simply use the CAT op code, because it cuts off trailing blanks. I must find a way to concatenate null-terminated strings by using pointers.
I have two pointers declared in my D-specs: ptrBuffer and ptrString. I use ALLOC to allocate some storage for them. On the recv( ) function, I receive data into ptrBuffer and want to concatenate this data to the end of the data pointed to in ptrString. Do you know how to do this?
— Michael Skvarenina
A: Use the Get or Store Null-terminated String (%STR) built-in function, shown in Figure
5. Using %STR on the right-hand side of an expression returns all the data up to the null terminator that the pointer indicates. Using %STR on the left-hand side puts a null- terminated string in the storage that the pointer indicates, truncating the data on the right- hand side, if necessary, to fit it into the variable in the second parameter.
— Barbara Morris RPG Compiler Development
IBM Toronto
D ptrBuffer s *
D ptrString s *
D lenBuffer s 10i 0 inz(10)
D lenString s 10i 0 inz(15)
D
C alloc lenBuffer ptrBuffer
C alloc lenString ptrString
C
C eval %str(ptrBuffer: lenBuffer) = 'Jack'
C eval %str(ptrString: lenString) = 'Hi '
C eval %str(ptrString: lenString) =
C %str(ptrString) + %str(ptrBuffer)
C*** ptrString now points to the value 'Hi Jack.', where '.'
C*** represents null.
C
C dealloc ptrBuffer
C dealloc ptrString
C
C eval *inlr = *on
Figure 5: Use %STR when concatenating null-terminated strings.
Static Storage and Named Activation Groups
Q: We have started to use named activation groups in our shop, and I was wondering what happens when a program ends abnormally. Our CL programs run in a named activation group, and RPG programs run in the caller’s activation group. Under the Original Program Model (OPM), whenever a halt occurred on an RPG running from a CL program, we could often fix the RPG error and answer the CL message with RETRY, which would start the RPG program over again with variables initialized. Now that we use named activation groups, if the RPG program bombs before the LR indicator can be set on, won’t the variables already contain modified values when I retry?
— Randy J. Bock
A: In ILE terms, you’re right. If the activation group is still active, the static storage is not reinitialized, but the static storage in RPG modules is sometimes reinitialized, depending on the RPG cycle. If the main procedure (the executable code before any P-specs) of a module ends abnormally, the global static storage for that module is reinitialized the next time you call the main procedure.
If your RPG program is a single-module program with no subprocedures, everything works as it does under OPM. If you have a program with several modules, the
global static storage of any modules whose main procedures are on the call stack at the time of the crash is reinitialized. Modules whose main procedures are not on the call stack at the time of the crash do not have their global static storage reinitialized. Private static storage in subprocedures is never reinitialized.
— Barbara Morris RPG Compiler Development
IBM Toronto
Running Java Classes with Class
Q: I use the CL JAVA command in interactive CL programs to run a Java class. When the JAVA command is finished, the terminal session stops at the Java Shell display. The operator has to press Enter to execute the next command in the CL program. Is there a solution to this problem?
A: There definitely is. Qshell uses the stdin, stdout, and stderr files. Override the stdout file to a database file of your own choosing. Create the database file with a length of at least 80 characters and one member. Now when you execute a Java application, the Java errors and standard output will go to your file instead of to the display. Also, use the Java command within QSHELL, instead of the CL JAVA or Run Java (RUNJVA) command. This process is illustrated in Figure 6.
— Don Denoncourt Senior Technical Editor
Midrange Computing
CHKOBJ OBJ(QTEMP/STDOUT) OBJTYPE(*FILE)
MONMSG MSGID(CPF9801) EXEC(DO)
CRTPF FILE(QTEMP/STDOUT) RCDLEN(96)
ENDDO
CLRPFM FILE(QTEMP/STDOUT)
OVRDBF FILE(STDOUT) TOFILE(QTEMP/STDOUT) MBR(STDOUT)
CHGVAR VAR(&JAVA) VALUE(‘java SomeJavaClass arg1 arg2’)
QSH CMD(&JAVA)
DLTOVR FILE(STDOUT)
Figure 6: Don’t get stuck in Qshell when running Java classes.
Lateral Thinking
In SEU, changing the session default for Amount to roll to C (cursor) causes the screen to scroll up and down relative to the cursor position. It may not be immediately obvious that this setting also allows you to window left and right relative to the cursor position by using F19 and F20. This can be useful for reading comment lines in 24 x 80 mode when those few words just disappear off to the left or when you have windowed left in an RPG IV source member and want to reposition the left margin to column 6.
— Phil Hope
Right-Adjust and Zero-Fill
We had a 10-character field with a left-adjusted number but needed it to be right-adjusted and filled with leading zeros. We found that the bit of code in Figure 7 does the trick.
Just remember: To use the EVALR op code, you must be at V4R4M0 or higher.
— Jan Jørgensen
C evalr Char10 = %trim(Char10)
C ‘ ‘:’0’ xlate Char10 Char10
Figure 7: Here’s a handy, simple way to right-adjust and zero-fill a number.
Editor’s Note: This is a good way to handle a not uncommon problem, but be aware of a few things. First, this code right-adjusts any character field with an embedded number, whether the number is left-adjusted, right-adjusted, or anywhere in the middle. In other words, it could have both leading and trailing blanks. Second, this code translates embedded blanks into zeros. Third, this routine does not verify that data is numeric. Finally, this routine does not de-edit numbers. Any numbers with editing characters—such as a minus sign, decimal point, currency symbol, or thousands separator—retain them.
DBGVIEW Performance Question
Q: We use the value DBGVIEW(*NONE) on the Create Bound RPG (CRTBNDRPG) command. Some of the staff members here have been told (by IBM and other sources) that using any of the other available views has a negative impact on performance. Can you confirm that?
— Darlene E. Kelley
A: Having a debug view doesn’t affect the performance of your program; debug information is not brought into memory unless you are debugging. The debug view only affects the amount of disk required to store your program, and the source view does not add very much storage.
If you want to remove debug information, you can use this command rather than recompile the program:
CHGPGM RMVOBS(*DBGDTA)
— Barbara Morris RPG Compiler Development
IBM Toronto
— Scott Mildenberger
Routing Interactive Jobs by User
Q: I am trying to force a certain group of users to operate in a different subsystem, but I can’t do it with subsystem workstation entries, because they need to be able to work from various workstations. I have created a job description and a new subsystem and added routing entries. I get the right job description when I log on, but I am running in the QINTER subsystem instead of the new one. My understanding of routing entries and classes is very limited, but I thought that what I did should have worked. Any ideas?
— Russell J. Neilsen
A: If you want to assign subsystems by user ID, you can include code like this in the users’ initial program(s):
CHKOBJ OBJ(QTEMP/ONETIME) OBJTYPE(*DTAARA)
MONMSG MSGID(CPF0000) EXEC(DO) CRTDTAARA DTAARA(QTEMP/ONETIME) +
TYPE(*CHAR) LEN(1)
TFRJOB JOBQ(QSYS/QCTL)
ENDDO
The TFRJOB command must point to a job queue attached to the desired interactive subsystem. In this example, a user transfers to the QCTL subsystem. When the user signs on, the data area will not exist in QTEMP, so the job will be transferred to QCTL. At that point, the initial program will run again, but since the data area will exist, the TFRJOB will not execute again.
— Ricardo P. Ang
A: Here is a way to route specific users into specific subsystems, regardless of their workstation location. Suppose that you’ve created a subsystem named BASEMENT and a job queue named SPEEDY and that you’ve attached SPEEDY to BASEMENT:
ADDJOBQE SBSD(BASEMENT) +
JOBQ(SPEEDY)
Make up some string for routing data, such as “HELLO,” and enter this string into the routing data in the job descriptions of your users. Also enter a job queue name in the request data in the job descriptions: Next, attach the job descriptions to the user profiles: Now when a user signs on, TOP001CL runs first. It retrieves the job queue name from the job description’s request data and transfers the job to the appropriate subsystem. In my example, when user GENE signs on, his job runs briefly in QINTER and then transfers to BASEMENT. You can maintain the target subsystems of users later through either CHGUSRPRF JOBD(xxx) or CHGJOBD RQSDTA(xxx).
— Gene Gaunt
Access Files Without an F-spec
Q: In the October 2000 issue of MC, Vincent B. Goldsby’s article, “Subprocedures: From Simple Function to Truly Functional,” states, “...the F-specification for file A can only exist at the main program level, not at the subprocedure level.” On page 317 of the IBM Redbook Who Knew You Could Do That with RPG IV? A Sorcerer’s Guide to System Access and More (SG24-5402-00) is an example of a subprocedure called GETCUST2,
CHGJOBD JOBD(ROOKIES) +
RTGDTA(‘HELLO’) +
RQSDTA(‘SPEEDY’)
CHGUSRPRF USRPRF(GENE) +
JOBD(ROOKIES)
Then, create CL program TOP001CL:
PGM
DCL VAR(&JOBQUEUE) TYPE(*CHAR) LEN(10)
RCVMSG PGMQ(*EXT) MSGTYPE(*RQS) MSG(&JOBQUEUE)
TFRJOB JOBQ(&JOBQUEUE)
ENDPGM
Finally, add a new routing entry to the interactive subsystem of the sign-on screens:
ADDRTGE SBSD(QINTER) +
SEQNBR(100) +
CMPVAL(‘HELLO’) +
PGM(TOP001CL)
which has an F-spec for a file called Customer. Could you clarify whether or not an F-spec can be used in a subprocedure?
— Dexter Okada
A: Vincent is correct. Subprocedures cannot contain F-specs, only D- and C-specs. However, the example he points to is a good way to get around his dilemma. GETCUST2 is not only a subprocedure but also an entire RPG module: It inhabits one source member, which is compiled via the Create RPG Module (CRTRPGMOD) command. You bind this module into programs, which would use the GetCust subprocedure to access the Customer file without having to declare the F-spec.
Here is code from the Redbook example, from which I’ve omitted the D- and C- Notice that the F-spec is not inside the GetCust subprocedure. — Ted Holt Senior Technical Editor
Midrange Computing
Time to Share Subfiles
Recently, I had an application in which I wanted to build a subfile, allow the user to select subfile records for further processing, and, if records were selected, require the user to confirm that processing should continue. I wanted the confirmation step to be a pop-up window that displayed selected subfile records, so I chose to override the initial display file and use it as the driving file in the confirmation subfile build. In other words, I wanted to share with the second RPG program the subfile from the first RPG program, to be able to read in the second program the subfile records built by the first.
I’ve created a very basic example to demonstrate my method of sharing subfiles between programs. (You can get the complete code at www.midrangecomputing.com/mc.) In this example, I allow the user to select records to be deleted from a physical file. Here, CL program SHSF01C performs an Override Display File (OVRDSPF) command on display file SHSF01D: SHSF01C then calls RPG program SHSF01R to build the subfile in SHSF01D (see Figure 8).
From this subfile, the user selects one or more records for deletion. If SHSF01R detects that at least one record has been selected, it calls RPG program SHSF02R, which uses the Read Changed Record (READC) op code to read the subfile in display file SHSF01D to build the confirmation display (see Figure 9).
At this point, I allow the user to exit the application with no update (via F3), cancel back to SHSF01R with no update (via F12), or confirm that the deletion process should continue (via F14). If you need to share subfiles between programs, use this example as a guideline.
— Gary Mikos Akron, Pennsylvania
specs:
H NoMain
FCustomer IF E K Disk ... more stuff
P GetCust B Export
... contents of subprocedure
P GetCust E
OVRDSPF FILE(SHSF01D) SHARE(*YES)
CALL PGM(SHSF01R)
FSHSF01D CF E WORKSTN SFILE(SUBF01:RRN1)
...
C ADD 1 RRN1
C WRITE SUBF01 ...
C CALL ‘SHSF02R’
Figure 8: SHSF01R loads the subfile and calls SHSF02R.
FSHSF01D CF E WORKSTN SFILE(SUBF01:RRN1)
FSHSF02D CF E WORKSTN SFILE(SUBF02:RRN2)
...
C READC SUBF01 30
Figure 9: SHSF02R reads the modified records of the shared subfile.
LATEST COMMENTS
MC Press Online