Thursday, December 20, 2007

AS/400 Interview questions

How do I trim leading zeroes?

Is there a BIF to trim leading 0's off of a numeric field AND reduce the field size accordingly?

 i.e. a field with a value of 000012345
I want to print:
"value: 12345" instead of
"value: 000012345" or
"value: 12345"

Use a combination of %edit and %trim. The %edit BIF will edit your number any way you wish: suppress leading zeroes, add commas, etc. The %trim BIF will strip the blanks off the front. Typical use for this is to insert a numeric amount into a message, like "Your balance is $25.00" Here is some code to do that: EDITBIF RPG IV 9 Mar 2001


%size gives me a compile time error on my D specs

%size doesn't accept an expression; try %len instead.


%trim is very slow

I did a quickie test of this on my heavily loaded 620. 5000 loops of %trim(String). "String" contains the literal "A":

If "String" is declared 20 bytes long, it takes :01.
If "String" is declared 32000 bytes long, it takes 1:45
If "String" is declared 20 bytes varying, :01.
If "String" is declared 32767 bytes varying, :01.

The moral of the story: try variable length fields for your string manipulation chores.


How can I right justify a field?

For V4R4 and above:
c evalr output = %trimr(input)
For earlier releases:
c Eval output =
%subst(BLANKS:1:%len(input) -
%len(%trimr(input))) +
input

where "input" and "BLANKS" are equal length character fields.


How can I centre a field?

Basically, take half the length of the output field, subtract half the length of the trimmed input field, put that many blanks at the front of the output field and add the input field. Watch out for output size less than input size and blank input!

d input           s             24    inz('1234567890              ')
d output s 40 inz
d BLANKS s like(output) inz
d len s 10i 0

c eval len = (%len(output)/2) -
c %len(%trimr(input))/2

c if len + %len(%trimr(input)) <=
c %len(output)
c eval output = %subst(BLANKS: 1: len) +
c input
c else
c eval output = input
c endif

Does %len count the null in a null terminated string?

The quick answer is no. Hans Boldt supplied this amplification:

Well, I don't mean to nitpick, but there are no null characters in the field after the %trim. "C" convention uses the null character to indicate the end of a character string. But in RPG IV, we maintain a separate length attribute. For char varying fields, the length is a 16-bit unsigned number at the beginning of the fields storage. (Within expressions, though, the length is held in some temporary variable.) Within a char varying field, the characters following the logical end of the data can be anything, but they're ignored by RPG.

In order to support the character string BIF's, we actually had support for the character varying type within expressions since V3R1. However, we didn't get around to actually adding the varying data types until V4R2. Priorities, you know. If you need to pass a null terminated string to a "C" function, use keyword OPTIONS(*STRING). Or to use a null terminated string as a normal RPG IV char field, use built-in function %STR.

Answer courtesy Hans Boldt via MIDRANGE-L


Date/time datatypes


I get RNX0112 when I use L (date) data types in my display file.

If you use CAnn instead of CFnn, this will happen because of the way the I/O buffer is validity checked by the RPG runtime. Internal to work station data management (WSDM), there is a working buffer (WB) which contains the current values for all fields on the display station (actually there is one WB per active record format on the display). When the user uses a CF key/ENTER/etc. the current display values are moved to this WB and verified.

If one or more errors are encountered (VALUES, RANGE, Date validation, etc.) then WSDM responds with an error message. When no errors are encountered, WSDM moves the WB contents to the input buffer associated with the *DSPF and RPG application, and returns control to the RPG program (or RPG runtime anyway).

What is happening is that the ENTER key is causing the invalid date to be loaded into the WB, the error is being reported, the CA key is being used to bypass entry, and the WB (in it's last used invalid state) is being returned to the RPG program. RPG runtime appears to be validating the Date field contents and is signalling the RNX0112.

Note that the very same thing (except for the RNX0112) occurs with VALUE DDS checking. If a VALUES('A' 'B' 'C') is defined and the user enters "E", ENTER, CA then the RPG program does indeed get 'E' returned (but as there is no datatype error you don't get an explicit error message).

This situation is documented in the DDS manual under CAnn as part of Validity Checking Considerations with suggested workarounds of:

  1. Don't use CA keys or
  2. Don't use functions such as VALUES, RANGE, CHECK(VN), etc.
As Date validity checking is done in the same manner as these other DDS keywords (that is, in WSDM and not the work station controller) it falls into the same classification.

Error handling


I'm using the *PSSR and want to return to the line of code following the one in error. How do I do that?

You can't directly GOTO the line after the error occurred. Basically, you'll need to set a flag to indicate where you are, then your *PSSR does an ENDSR *DETC. Now that you're at the top of the detail calcs, you check your flag and GOTO the spot after the error, something like this, perhaps: Demonstrate *PSSR 16 Mar 2001


APIs


Can I use RPG to read and write to the IFS?

Scott Klement donated these code samples in a post on MIDRANGE-L:


What does BINARY 4 mean?

Several IBM API documents refer to "binary 4." What exactly does that mean?

BINARY(4) means a 4-byte binary number.

  • In RPG III, this means a subfield of a data structure that is defined with 4 bytes, and has the 'B' type.
  • In RPG IV, there are two kinds of 4-byte binary number: the 10-digit integer or the 9-digit binary. The 10-digit integer is better when dealing with APIs. If you define an integer or binary number using length notation (no from-position), you give the number of digits. 10I-0 or 9B-0. A very common error is to define a BINARY(4) field or parameter using length notation as 4B-0. This always causes problems calling the API.

Answer courtesy Barbara Morris 15 Mar 2001


How can I get a list of jobs for a user?

Mark Walter provides an RPG program here that uses the following APIs:

  • QUSCRTUS - Create user space
  • QUSLJOB -- List user jobs into user space
  • QUSRTVUS - Retrieve data from user space
  • QCMDEXEC - Execute OS/400 command
  • QUSDLTUS - Delete user space

The program gets a list of jobs for the current user at status *JOBQ and puts the list in a user space. It then builds an array of jobs that can be readily manipulated. This example performs an ENDJOB command on each one.

Answer courtesy Mark Walter via MIDRANGE-L 5 Feb 2001


Indicators


How can I position the cursor in a display file without using up an indicator?

I got this from MIDRANGE-L long ago and foolishly neglected to keep track of the kind soul who posted it. Use the CSRLOC DDS keyword, and you can position exactly by row and column. Here are DDS and RPG IVexamples.

Answer courtesy MIDRANGE-L 16 Apr 2001


How can I highlight a field in a display file without using up an indicator?

Use DSPATR with a program to system field. Here are DDS and RPG IVexamples. You can't set Position Cursor, unfortunately.

Answer courtesy Dave Mahadevan via MIDRANGE-L 16 Apr 2001


Debugging


How can I debug ILE programs?

Brad Stone has a FAQ entry that addresses this.

If that site is unavailable, here is a list of steps culled from posts to RPG400-L:

    Using the green screen debugger:
  1. Submit your program to batch. The job MUST be held. You can either hold the job queue (HLDJOBQ) or hold the individual job (HLDJOB) or specify HOLD(*YES) on the SBMJOB command.
  2. WRKSBMJOB/WRKUSRJOB/WRKACTJOB and find your submitted job. Note that the SBMJOB command gives you an informational message with the job name/number. What you need is the job name, user ID and job number - the fully qualified job name. Example: 123456/BUCK/MONTHEND
  3. STRSRVJOB on the held batch job.
  4. STRDBG on your program. Specify UPDPROD(*YES) if needed. You'll see the source listing if you compiled with DBGVIEW(*LIST) or *SOURCE.
  5. Press F12 to exit - you cannot set a breakpoint yet.
  6. Release the job so that it becomes STATUS(*ACTIVE).
  7. You'll see a display asking if you want to debug or continue. Press F10 to debug.
  8. DSPMODSRC to see the source listing again. Alternately, press F10 to step into the first instruction.
  9. Now you can add your breakpoints.
  10. Press F3 until you're back to the "debug or continue" display. Press Enter to run the program with your breakpoints set.
  11. When you're done, do an ENDDBG and ENDSRVJOB.

Thanks to Bob Slaney, Phil, Patrick Conner and Kelly Fucile.

    Using the IBM Distributed Debugger:
  1. SBMJOB CMD(CALL PGM(yourlib/yourpgm)) JOBQ(yourlib/yourjobq) HOLD(*YES)
  2. Start your Code debugger from Start->Programs->WebSphere Development...->IBM Distributed Debugger->IBM Distributed Debugger
  3. Select the debugger Start up window and key into the job name entry field */##########/* where ########## is your user id.
  4. You may have to log in and specify the AS/400 system name.
  5. Select the job that is being held in yourjobq.
  6. Click the ok push button.
  7. Enter the library and program name into the Program entry field
  8. Click the Load push button on the debugger Startup information window. A debugger message will appear telling you to start the program.
  9. Click Ok on the message push button, even though it tells you to start your program first.
  10. Switch to a 5250 emulation window.
  11. WRKJOBQ JOBQ(yourlib/yourjobq)
  12. Release your job.

Answer courtesy Rob Berendt via RPG400-L 2 Aug 2001


How can I debug OPM programs?

If you're willing to use the old OPM debugger, you can use the ILE steps outlined above.

Mike Barton suggests compiling the program with OPTION(*SRCDEBUG) and then using STRDBG OPMSRC(*YES), which should work with the steps given above.

STRISDB won't work unless the job is running, so you can't put it on hold and enter your break points.

Martin Rowe contributed the following idea: Insert a simple CL program into your RPG that waits for you to answer a message. This way, the job is running, but not processing yet. (RPG400-L 24 May 2001)

Here is an adaptation of his idea:

Here's the RPG program you're trying to debug:
H 1
C CALL 'DBGWAIT'
C Z-ADD1 X 50
C SETON LR

I've inserted "CALL 'DBGWAIT'" and re-compiled.

Here's the source for DBGWAIT:
pgm

dcl &reply *char 1

sndusrmsg msgid(CPF9898) +
msgf(QCPFMSG) +
msgdta('Paused for debug') +
msgrpy(&reply)

endpgm

And here are the actual debugging steps:

  • STRISDB PGM(BATCHOPM) UPDPROD(*NO) INVPGM(*NO) SRVJOB(*SELECT) You'll see a list of all active jobs on your system.
  • Select the one you're trying to debug. You'll get a message saying that the program is in debug mode.
  • Answer the "paused for debug" message, and the source will pop up after the call to DBGWAIT.

The Rest (not easily catalogued)


Why is garbage in my *ENTRY parameters?

This is undoubtedly a result of a mis-match between the definition of the parameters between the caller and the called program. Very often, this mis-match is unwittingly caused by calling a program from a command line (or SBMJOB).

The following text uses CL as an example, but the same ideas work for RPG as well. CL is used, because it is usually a CL command (SBMJOB or CALL) that reveals the mis-match. Text by John Taylor

CL Parameter Basics

When a variable is declared within a CL program, the system assigns storage for that variable within the program automatic storage area (PASA). If you subsequently use the variable as a parameter within a CALL command, the system does not pass the value of that variable to the called program, but rather a pointer to the PASA of the calling program. This is known as parameter passing by reference.

For this reason, it is very important that both programs declare the parameter to be of the same type and size. To illustrate, let's look at the following example:

PgmA: Pgm

DCL &Var1 *CHAR 2 Inz( 'AB' )
DCL &Var2 *CHAR 2 Inz( 'YZ' )

Call PgmB Parm( &Var1 &Var2)

EndPgm
PgmB: Pgm Parm( &i_Var1 &i_Var2 )

DCL &i_Var1 *CHAR 4
DCL &i_Var2 *CHAR 2

EndPgm

Hopefully, you've noticed that the first parameter is declared to be larger in PgmB than it was in PgmA. Although you might expect &i_Var1 to contain 'AB ' after the call, the following is what the input parameters in PgmB actually contain:

&i_Var1 = 'ABYZ'
&i_Var2 = 'YZ'

&i_Var1 shows the contents of the first parameter, and the second, because the second parameter is immediately adjacent to the first within the storage area. If the second parameter was not contiguous to the first, then the last two bytes of &i_Var1 would show whatever happened to be in the storage area at that time.

You can think of &i_Var1 as a 4-byte "window" into the storage area of the calling program. It's passed a pointer that tells it where the view begins, and it accesses anything in storage from that point up to the parameter's declared length.

Looking at Literals

There are several ways that a program can be called, other than from another program. Examples include the command line, SBMJOB, job scheduler etc. In the case of an interactive call from the command line, you specify the parameters as literals, ie:

Call PgmB Parm('AB' 'YZ')

Consider that when we do this, there is no PASA. We'll look at the implications of that in a minute, but for now, just make a note of it.

Submitting a job from the command line isn't any different. If you're submitting a CALL, then you'll be specifying any associated parameters as literals. However, things can get a bit deceiving when you submit a job from within a program, as the following example illustrates:

PgmC: Pgm

DCL &Var1 *CHAR 2 Inz( 'AB' )
DCL &Var2 *CHAR 2 Inz( 'YZ' )

SbmJob Cmd(Call PgmB Parm( &Var1&Var2))

EndPgm

Clearly, we're not passing literals here. Or are we?

Let's think about how things would work if we passed variables:

  • PgmC submits a call to PgmB, passing two variables as parameters.
  • PgmC immediately ends as a result of the EndPgm statement.
  • PgmB begins running in batch and receives pointers to PgmC's PASA.
  • PgmB crashes when it attempts to use the pointers.

We have invalid pointers because PgmC is no longer running. If you've ever tried this personally, you know that it doesn't happen in practice. The reason for that is that the system is converting those variables to literals before issuing the CALL command. Very sneaky, but effective.

Now that we've seen some examples of where literals are used, and why, it's time to talk about the PASA again. When we discussed the basics of CL parameter passing, we learned that the called program expects to receive a pointer to a storage area within the PASA for each input parameter. This requirement hasn't changed. So now we have a situation where the CALL command is passing literals, but the called program is still expecting pointers.

Obviously, it's time for the system to perform some more magic behind the scenes. In order to accomodate the requirements of the called program, the system creates a space in temporary storage for each literal being passed, and moves the value of the literal into that storage space. Now it can pass pointers to the called program, and everyone is happy.

Except you that is, because none of this changes the fact that you're getting "garbage" in the input variables of your called program! Fair enough. I'm getting to that now, but you needed the background in order to understand the next part.

Sizing It All Up

Now that you know the system is creating variables behind the scene, you might wonder how it knows what size those variables need to be. The answer is that it doesn't. Instead, the designers have imposed some specific rules about how literals are transformed to variables, and thereby passed as parameters.

CL supports only three basic data types: character, decimal, and logical. For the purposes of this discussion, you can consider the logical data type equivalent to the character type, because it's treated in the same manner.

The simplest rule is the one that handles decimal literals. All decimal literals will be converted to packed decimal format with a length of (15 5), where the value is 15 digits long, of which 5 digits are decimal places. Therefore, any program that you expect to call from the command line, or SBMJOB etc., needs to declare it's numeric input parameters as *DEC(15 5).

Character literals are a little bit more complicated, but still fairly straightforward. There are two rules to remember. The first is that any character literal up to 32 characters in length will be converted to a 32 byte variable. The value is left justified, and padded on the right with blanks.

So if you were to pass the following literal:

Call PgmB 'AB'

the associated storage space for that literal would contain:

'ABxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' (where "x" represents a blank space)

The second rule is that character literals longer than 32 bytes are converted to a variable of the same length as the literal value itself, as in the following example:

Call PgmB 'This is a long character literal that will exceed 32 bytes.'

the associated storage space for that literal would contain:

'This is a long character literal that will exceed 32 bytes.'

Finally, since the logical data type follows the same rules as the character type, and the only possible values for a logical data type are '0' or '1', we know that a logical literal will always be created as a 32 byte, left justified, padded character variable.

Parameter Problems

In the beginning of this explanation, you learned that it was important for the parameter declarations to match between a called program and it's caller. Then you discovered that the system sometimes has to take it upon itself to declare the parameters of the caller on your behalf. If the two declarations don't match, we have the potential for trouble.

In the case of a decimal value, the result is immediate and obvious; you get a data decimal error. Character variables are more difficult to debug because they don't generate any immediate errors. What actually happens depends upon the length of the parameter in the called program.

If the length of the parameter in the called program is less than the length of the parameter being passed, the extra characters are effectively truncated, as follows:


Call SomePgm ('ABCDEFG') /* system creates 32 byte *CHAR*/

SomePgm: Pgm Parm( &i_Var1 )

DCL &i_Var1 *CHAR 4

EndPgm

What happens is that the system passes 'ABCDEFGxxxxxxxxxxxxxxxxxxxxxxxxx' ('x' is a blank), but because of the declared length of &i_Var1, SomePgm only see's 'ABCD'. For most of us, this is the behaviour that we would expect.

Things get nasty when the declared length of the variable is longer than what is being passed in. Using the same example as we've just seen above:

SomePgm: Pgm Parm( &i_Var1 )

DCL &i_Var1 *CHAR 34

EndPgm

In this case, the system will still allocate 32 bytes of storage and assign 'ABCDEFGxxxxxxxxxxxxxxxxxxxxxxxxx' to it, but because &i_Var1 is now declared to be 34 bytes long, SomePgm will see more storage than it was intended to. It will see the 32 bytes that were allocated for it, plus two additional bytes. It's those two additional bytes that can cause the infamous "unpredictable results" which IBM's documentation often refers to.

If the extra bytes contain blanks, chances are that you won't notice a problem, but if they contain something else, your input parameter will contain "garbage".

As you can see, when dealing with literals, the magic number for character parameters is 32. If the called program declares the parameter to be less than or equal to 32, you'll never see "garbage" in the parameter. Once you cross that 32 byte threshhold, you need to take extra care to ensure that the size of the literal being passed is equal to the declared size of the input parameter.

Things to Remember

  • always match the type/size of parameters on your pgm to pgm calls.
  • remember that the system converts literals to variables in the background.
  • remember that decimal literals are always converted to *DEC(15 5).
  • and that char literals less than or equal to 32 bytes are converted to *CHAR(32).
  • and that char literals greater than 32 bytes are converted to variables of equivalent size.

and last, but not least:

  • the called program "sees" as much storage as it declares for an input parameter, regardless of whether or not the caller actually allocated that much storage for it.

Solutions

Text by Buck.

>Wow! So what you are basically saying is
>that I shouldn't have a problem if I use
>variable lengths less than or equal to
>32, but I must use longer, to make sure
>they are the same size?
>(Which by the way was the case)

This is the assumption that is in error. Imagine being the CL program doing the SBMJOB.

CL STEP2CL

dcl &filename *char 50
chgvar &filename 'test.txt'
sbmjob cmd(call STEP3CL &filename)

STEP2CL has set aside 50 bytes of storage for &FILENAME. When STEP2CL calls another program, he passes a pointer to his internal storage (parameters are passed by reference.) Well, on the AS/400 one job doesn't get to manipulate storage in another job (SBMJOB creates another job) so this can't work like a CALL.

Instead, SBMJOB resolves the value of &FILENAME and passes it to the newly created job (which is QCMD - look at your routing entries) as a constant. So instead of "call step3cl

" you get "CALL QCMD PARM(call step3cl 'test.txt')" (not really but why add request message processing to the muddle?)

QCMD parses out the string that it received and realises that it has to create some storage for the parameter 'test.txt' Because it is a character constant, QCMD creates a 32 byte storage area, initialises it to blanks and loads the constant into it, left-justified. QCMD then does a "call STEP3CL

"

CL STEP3CL

pgm &filename
dcl &filename *char 50

When STEP3CL tries to use &FILENAME, it reads 50 characters starting at

Unfortunately, QCMD only initialised 32 bytes of that area (PARAMETER SIZE MISMATCH ALERT) so the remaining bytes of &FILENAME contain Who Knows What, also known as Garbage.

A work-around:

Have STEP2CL pass 51 bytes of data to SBMJOB. Here's how that would work:

CL STEP2CL

dcl &filename *char 50
dcl &filetemp *char 51
chgvar &filename 'test.txt'
chgvar &filetemp (&filename *cat 'x')
sbmjob cmd(call STEP3CL &filename)

STEP2CL has set aside 51 bytes of storage for &FILETEMP. When the SBMJOB resolves the value of &FILETEMP, you get "CALL QCMD PARM(call step3cl 'test.txt x')" QCMD does his thing and sees 51 bytes of data, so he allocates an internal work area 51 bytes long. When STEP3CL tries to use &FILENAME, it reads 50 characters starting at

He doesn't care that QCMD has initialised 51 bytes of storage, or that byte 51 contains something: he stops reading at byte 50.

The Cool Way:

Write your own command. We've been able to extend OS/400 since the dinosaurs roamed Pangaea. Here's how it works:

CMD CVTTOPDF
CMD 'Convert IFS file to PDF'

PARM KWD(FILENAME) TYPE(*PNAME) LEN(50) MIN(1) +
PROMPT('Input file name')

crtcmd cvttopdf pgm(STEP3CL)

CL STEP2CL

dcl &filename *char 50
chgvar &filename 'test.txt'
sbmjob cmd(cvttopdf filename(&filename))

The SBMJOB results in "cvttopdf filename('test.txt')" When QCMD does his thing, he sees that he needs to run a command (not CALL) so the command processor checks the command definition for each parameter, initialises the defined amount of storage (here it's 50 bytes) loads the internal storage areas up and away we go. When STEP3CL gets called as a result of processing CVTTOPDF, his definition of 50 bytes exactly matches the caller's definition of 50 bytes and All Is Well.

No comments: