rpg tips & tricks

of 88 /88
1 Question: The main differences between RPGIV, RPG400 and the newer ILE version ? Answer(s): There are really only two current RPGs for the AS/400, RPG/400 and ILE RPG. RPG IV is a popularized name for ILE RPG. RPG/400: This is the older RPG that has been around for several years. It used the "Old Programming Model" popularly known as OPM, while ILE RPG uses the ILE model. The following information applies to ILE RPG. ILE RPG: It became available at Version 3. If you are at V3R1 on a CISC machine, I would recommend going to V3R2 to take advantage of the best features of ILE RPG. Some ILE RPG Features: Syntax: All non-external data definitions can now be specified in a D-specifications that are new to ILE RPG. In addition you can define "named constants" that greatly simplify coding in the C-spec's. Also C-spec formats have changed slightly to provide for variable names of up to 10 characters (up from 6 in RPG/400) and longer operation codes. New Operations: Several have been added. One that I like is EVAL which allows you to evaluate a mathematical expression similar to Cobol and other mathematical programming languages such as Basic, FORTRAN, PL/1, etc. Modularity: This is a big plus. You can now write modules (non-executable) in several languages a nd bind them together into a single ILE program. Thus you can use the best language (ILE C, ILE Cobol, ILE RPG, ILE CLP) for a process or use existing modules to write a program. You can also write callable procedures or procedures that function like built-in functions. Question: I am trying to create a command which will accept five or six parameters . One parameter is called CONO. I would like this parameter CONO to only accept two-character text strings (Z1, AB, JW etc) *OR* the special value of *ALL. The problem I have is with the length of the parameter. If I define it of type *CHAR with a length of two then it will not compile, saying that *ALL is not valid. If I compile it with a size of four then it will accept any string upto four characters (ABCD etc) and does not enforce the two- character rule. I know it can be done, as I have seen a non-IBM command do ikt (EXCAMTASK, in the JBA software) but I cannot figure it out for myself. All help very much appreciated! Answer(s): One way to do this is to define the parameter with a length of 2 and specifying in the special value list something like (*ALL xx), where xx is the value the CPP will receive when the command is executed using the special value *ALL. The value you assign to xx can be a value you cannot type using the keyboard (e.g. x'0000') so you don't have to reserve a two-character combination for this special value. HTH Try this PARM KWD(CONO) TYPE(*CHAR) LEN(2) RSTD(*YES) VALUES(Z1 AB JW) SPCVAL(*ALL *A) ... When the value *ALL is entered, you receive the translated value *A in your command processing program. If you allow multiple values eg MAX(n) you should use SNGVAL(*ALL *A) Good luck! You can specify a RANGE (e.g. from "AA" to "ZZ") in the PARM-Keyword. Just prompt with F4, then you will see it. First thought (speaking from memory, I'm away from my AS/400), is to make sure that your PARM statement says something like SPCVAL((*ALL ' ')). Of course, you hereby lose the ability to process a CONO of all blanks (or whatever other value you are willing to give up). If this does not work, let me know and I shall be glad to look further Try this: PARM KEYWORD(CONO) TYPE(*CHAR) LEN(2) SPCVAL((*ALL ' ')) The special values allow you to override type and length checks on a parameter with special values (imagine that?). Each special value has two values value 1 is what the user types in the command. Value 2 is the replacement value that is sent to your program. Value 2 must be of the same base type and length declared for the PARM. Special values are exempt from most of the validity checking rules and so are the replacement values. In the above example, when the user types CONO(*ALL) your program will actually see " " (blanks) See the CL programmer's guide for details Here's one way: PARM KWD(CONO) TYPE(*CHAR) LEN(2) RSTD(*YES) + VALUES(Z1 AB JW) SPCVAL((*ALL '**')) Here's another: PARM KWD(CONO) TYPE(*CHAR) LEN(2) RST(*YES) + SPCVAL((*ALL '**') (Z1) (AB) (JW)) Note that if the user enters *ALL, your CPP will see **.

Author: paul-rathnam-marri-menon

Post on 02-Mar-2015

1.083 views

Category:

Documents


3 download

Embed Size (px)

TRANSCRIPT

Question: The main differences between RPGIV, RPG400 and the newer ILE version? Answer(s): There are really only two current RPGs for the AS/400, RPG/400 and ILE RPG. RPG IV is a popularized name for ILE RPG. RPG/400: This is the older RPG that has been around for several years. It used the "Old Programming Model" popularly known as OPM, while ILE RPG uses the ILE model. The following information applies to ILE RPG. ILE RPG: It became available at Version 3. If you are at V3R1 on a CISC machine, I would recommend going to V3R2 to take advantage of the best features of ILE RPG. Some ILE RPG Features: Syntax: All non-external data definitions can now be specified in a D-specifications that are new to ILE RPG. In addition you can define "named constants " that greatly simplify coding in the C-spec's. Also C-spec formats have changed slightly to provide for variable names of up to 10 characters (up from 6 in RPG/400) and longer operation codes. New Operations: Several have been added. One that I like is EVAL which allows you to evaluate a mathematical expression similar to Cobol and other mathematical programming languages such as Basic, FORTRAN, PL/1, etc. Modularity: This is a big plus. You can now write modules (non-executable) in several languages a nd bind them together into a single ILE program. Thus you can use the best language (ILE C, ILE Cobol, ILE RPG, ILE CLP) for a process or use existing modules to write a program. You can also write callable procedures or procedures that function like built-in functions.

Question: I am trying to create a command which will accept five or six parameters. One parameter is called CONO. I would like this parameter CONO to only accept two-character text strings (Z1, AB, JW etc) *OR* the special value of *ALL. The problem I have is with the length of the parameter. If I define it of type *CHAR with a length of two then it will not compile, saying that *ALL is not valid. If I compile it with a size of four then it will accept any string upto four characters (ABCD etc) and does not enforce the twocharacter rule. I know it can be done, as I have seen a non-IBM command do ikt (EXCAMTASK, in the JBA software) but I cannot figure it out for myself. All help very much appreciated! Answer(s): One way to do this is to define the parameter with a length of 2 and specifying in the special value list something like (*ALL xx), where xx is the value the CPP will receive when the command is executed using the special value *ALL. The value you assign to xx can be a value you cannot type using the keyboard (e.g. x'0000') so you don't have to reserve a two-character combination for this special value. HTH Try this PARM KWD(CONO) TYPE(*CHAR) LEN(2) RSTD(*YES) VALUES(Z1 AB JW) SPCVAL(*ALL *A) ... When the value *ALL is entered, you receive the translated value *A in your command processing program. If you allow multiple values eg MAX(n) you should use SNGVAL(*ALL *A) Good luck! You can specify a RANGE (e.g. from "AA" to "ZZ") in the PARM-Keyword. Just prompt with F4, then you will see it. First thought (speaking from memory, I'm away from my AS/400), is to make sure that your PARM statement says something like SPCVAL((*ALL ' ')). Of course, you hereby lose the ability to process a CONO of all blanks (or whatever other value you are willing to give up). If this does not work, let me know and I shall be glad to look further

Try this: PARM KEYWORD(CONO) TYPE(*CHAR) LEN(2) SPCVAL((*ALL ' ')) The special values allow you to override type and length checks on a parameter with special values (imagine that?). Each special value has two values value 1 is what the user types in the command. Value 2 is the replacement value that is sent to your program. Value 2 must be of the same base type and length declared for the PARM. Special values are exempt from most of the validity checking rules and so are the replacement values. In the above example, when the user types CONO(*ALL) your program will actually see " " (blanks) See the CL programmer's guide for details Here's one way: PARM KWD(CONO) TYPE(*CHAR) LEN(2) RSTD(*YES) + VALUES(Z1 AB JW) SPCVAL((*ALL '**')) Here's another: PARM KWD(CONO) TYPE(*CHAR) LEN(2) RST(*YES) + SPCVAL((*ALL '**') (Z1) (AB) (JW)) Note that if the user enters *ALL, your CPP will see **. 1

This really does not resolve the *all option, but we have a program that accepts a parm value of '01' thru '10', or 'AL' for all values. We then have program logic that recoginizes that it needs to handle 'AL' differently. It may be a workaround for you. Good luck.

Question: Is there a easy way to convert a character to decimal? We have a PC file that we are getting weekly and uploading to the AS/400 and then using a RPG program to convert the data to a physical file. The file has a number field at the beginning that can be up to 6 digits long. The numbers are being zero suppressed when we get it. So for example it may be 1, 100, 1000, 10000 and so on. So I moved the field to and array and then moved each digit from right to left to a number field. Is there an easier way of doing this? There is probably a easy solution that I just am not thinking of... Thanks.. Answer(s): The 'atoi' and 'atof' C functions handle conversion from numbers containing decimal points and signs. If the number may contain a decimal point, use 'atof' (it doesn't allow comma - you'd have to xlate ',':'.') Be sure to use half-adjust with atof because the result is floating-point. Here's an example.

H BNDDIR('QC2LE') D ATOI PR D NUM D ATOF PR D NUM D I S D P S C MOVEL C EVAL * > Eval I * I = -100 C EVAL(H) * > Eval I * P = -000100.0000000 C MOVEL C EVAL(H) * > Eval P * P = -000005.6700000 C RETURN

10I 0 EXTPROC('ATOI') * OPTIONS(*STRING) VALUE 8F EXTPROC('ATOF') * OPTIONS(*STRING) VALUE 10I 0 13P 7 '-100 ' NUM I = ATOI(%TRIM(NUM))

6

P = ATOF(%TRIM(NUM))

'-5.67 ' NUM P = ATOF(%TRIM(NUM))

6

No reason to use C, when you've got RPG.... Example #1 - Packed Numeric to Alpha: Use the Z-ADD opcode to decompress the packed field, and then the MOVE opcode to place it ito a same sized alpha field. C* C* FIELDA = 7,0(P) C* C C C*

FIELDB = Z-ADD MOVE

7,0(S) FIELDA FIELDB

FIELDC = 7(A) FIELDB FIELDC

Example #2 - Zoned Decimal with two decimal places to a Character field. C* C* FIELDA = 7,2(S) FIELDB = 7,0(S) FIELDC = 7A C* C FIELDA MULT 100 FIELDB C MOVE FIELDB FIELDC C* Example #3 - Character field to Zoned Decimal with two decimal places. C* C* FIELDA = 7(A) FIELDB = 7,0(S) FIELDC = 7,2(S) C* 2

C C C*

FIELDB

MOVE MULT

FIELDA .01

FIELDB FIELDC

Question: I wonder if anyone could help me out...... I'm trying to automate an FTP job using CL. On a daily basis we need to FTP several files to another system and I would like to automate this process using CL, is this possible...... we don't have any additional FTP software just what came with the OS version 3.2 by the way. I can see how to actually connect.... ie FTP ('123.123.91.230') but I'm lost as to where to go next, how do I add my BIN and PUT commands? Answer(s): FTP in batch is very simple. You need three files. 1. A CL program. 2. A file containing FTP commands. 3. An empty file to receive the FTP log messages. The command and log files can have any name you want. Create 2 Physical files each with a single 80 character data field. The command file in this example FTPCMD might look like this. USERID PASSWORD Put your own userid and password here. get LIBRARY/FILE.MEMBER LIBRARY/FILE.MEMBER (REPLACE CLOSE The message file, in this example FTPMSG is empty. The CL looks like this

PGM CLRPFM FTPMSG OVRDBF FILE(INPUT) TOFILE(TOLCLINIC/FTPCMD) OVRDBF FILE(OUTPUT) TOFILE(TOLCLINIC/FTPMS) FTP RMTSYS() ENDPGMWhen you run this program it will execute the commands in the INPUT file and write the log to the output file. I have used this method to move source members from a test AS/400 to a production AS/400. When I tried to move a data file it didn't work right.

In the TCP/IP guide there is an example of batch FTP. The Operating system support is extremely weak but here goes... In your CL execute the following code: OVRDBF INPUT TOFILE(MYFTP) TOMBR(MYFTPIN) OVRDBF OUTOUT TOFILE(MYFTP) TOMBR(MYFTPOUT) FTP 'xxx.xxx.xxx.xxx' MYFTP is a source file created with at least a record length of 92 The MYFTPIN member contains a script of commands you want to execute. The first record must contain the user id and password to log in ------------------------------------------Myid mypassword binary namefmt 1 mode s mget remotefile.* qgpl/myfile quit ---------------------------------------The MYFTPOUT member will contain the response to your input script You must examine this file to determine if your script executed properly

We do this exactly in our shop. Look at section 7-36 of the TCP/IP manual. You will see a similar example to what we do: 3

/*-------------------------------------------------*/ /* Process FTP commands to send file */ /*-------------------------------------------------*/ OVRDBF FILE(INPUT) TOFILE(QFTPSRC) MBR(FTPCMD) OVRDBF FILE(OUTPUT) TOFILE(QFTPSRC) MBR(FTPLOG) FTP RMTSYS(SYS01) .... FTPCMD is a member which contains the ftp commands to process (we actually build this member on the fly as username, password and filenames change): username password put localfile remotefile quit ... Member FTPLOG produces output like this: Output redirected to a file. Input read from specified override file. Connecting to host name SYS01 at address xxx.xx.x.xxx using port 21. 220 sys01 FTP server (Version 4.29 Thu Jan 30 14:58:02 CST 1997) ready. 215 UNIX Type: L8 Version: BSD-44 Enter login ID (username): 331 Password required for username. 230 User username logged in. Enter an F TP subcommand. > put LOCALFILE REMOTEFILE 200 PORT command successful. 150 Opening data connection for LOCALFILE. 226 Transfer complete. 1219 bytes transferred in 0.745 seconds. Transfer rate 1.636 KB/sec. Enter an FTP subcommand. > quit

Question: Does anyone have an idea how to make an rpg program which executes an dynamic sql-select-statement where the number and the type of the selected columns are not known. Where are the limits of such an sql-statment ?

Answer(s): Funny because this same subject just came up on the NEWSLINK400 forum. Yes you can prepare a dynamic select statement for a cursor where you don't basically have to know anything at compile time including the columns, the files, etc. However, if you are planning on doing multiple opens and closes of such cursors and then changing the columns or something else like that, then you must re-prepare the select statement and reopen the cursor. An SQL Prepare statement is potentially very slow because the system must build an access plan based on available access paths, etc. Therefore, SQL does allow you to place something called a parameter marker (which is a question mark) in any place where static sql allows you to place a host variable. Excluded from this are things like column lists and file names. So SQL wouldn't permit me to prepare a statement that was formed as follows. EvalSqlStm = 'Select ? from ?' Too bad! However, SQL would allow me to prepare the following statement. EvalSqlStm = 'Select * From Customer Where CuName > ? You substitute for parameter markers with the Using clause of the Open cursor SQL statement. I will show an example below. The following is a program I wrote and checked out with the debugger to make sure it was executing correctly. Notice this example does show how to use parameter markers. Notice the open cursor statement must specify the name of a host variable to substitute for the question marks. Hope this helps.

4

D GenCust D CuName D CuStat D CustAp D CuOv30 D CuOv60 D CuOv90 D OrdDs D SelectStm D PrmChoice D ChoiceGenCust D ChoiceCustAp D ChoiceOrd D GtName D GtCuOv30 D EqOhStat

DS 25 1 DS 9P 2 9P 2 9P 2 E DS S S C C C S S S 200 1 'G' 'A' 'O' 25 Inz( 'H' ) 9P 2 Inz( 100 ) 1 Inz( 'C' ) ExtName( OrdHdr )

C/Exec SQL C+ Declare DynamCsr Cursor for C+ DynSqlStm C/End-Exec C C C C C C C *Entry Plist Parm Select When Eval PrmChoice = ChoiceGenCust SelectStm = 'Select CuName, CuStat ' + 'From Customer ' + 'Where CuName > ?'

PrmChoice

C/Exec SQL C+ Prepare DynSqlStm C+ From :SelectStm C/End-Exec C/Exec SQL C+ C+ C/End-Exec C/Exec SQL C+ C+ C+ C/End-Exec C C C C C C

Open DynamCsr Using :GtName

Fetch Next From DynamCsr Into :GenCust

When Eval

PrmChoice = ChoiceCustAp SelectStm = 'Select CuOv30,' ' CuOv60,' ' CuOv90 ' 'From Customer ' 'Where CuOv30 > ?' + + + +

C/Exec SQL C+ Prepare DynSqlStm C+ From :SelectStm C/End-Exec C/Exec SQL C+ C+ C/End-Exec C/Exec SQL 5

Open DynamCsr Using :GtCuOv30

C+ C+ C+ C/End-Exec C C C C

Fetch Next From DynamCsr Into :CustAp

When Eval

PrmChoice = ChoiceOrd SelectStm = 'Select * ' 'From OrdHdr ' 'Where OhStat = ?'

+ +

C/Exec SQL C+ Prepare DynSqlStm C+ From :SelectStm C/End-Exec C/Exec SQL C+ C+ C/End-Exec C/Exec SQL C+ C+ C+ C/End-Exec C C/Exec SQL C+ C/End-Exec C

Open DynamCsr Using :EqOhStat

Fetch Next From DynamCsr Into :OrdDs

EndSl

Close DynamCsr

Eval

*INLR = *On

So far so good, but i think i did not express my problem clear enough - second try: A user enters a sql-statement like: select kskos, kv#01, kv#02 from ks, su where kskos = sukos (or any other valid select statement) My little program does not know which tables were concered and how many (and of course which type) of columns will come out of this. So my question is - is this possible and if it is how is it done. I was hoping you weren't expecting to do something this dynamic. Oh well. Let's give this a whack. The SQL programming guide suggests that what you want to do is possible with the SQL DESCRIBE statement. I'm going to cut and paste from that manual and then make some comments. ===Start Manual excerpt=== 8.3.2 Varying-List Select-Statements In dynamic SQL, varying-list SELECT statements are ones for which the number and format of result columns to be returned are not predictable; that is, you do not know how many variables you need, or what the data types are. Therefore, you cannot de fine host variables in advance to accommodate the result columns returned. Note: In REXX, steps 5b, 6, and 7 are not applicable. If your application accepts varying-list SELECT statements, your program has to: 1. Place the input SQL statement into a host variable. 2. Issue a PREPARE statement to validate the dynamic SQL statement and put it into a form that can be run. If DLYPRP (*YES) is specified on the CRTSQLxxx command, the preparation is delayed until the first time the statement is used in an EXECUTE or DESCRIBE statement, unless the USING clause is specified on the PREPARE statement. 3. Declare a cursor for the statement name. 4. Open the cursor (declared in step 3) that includes the name of the dynamic SELECT statement. 5. Issue a DESCRIBE statement to request information from SQL about the type and size of each column of the result table. Notes: a. You can also code the PREPARE statement with an INTO clause to perform the functions of PREPARE and DESCRIBE with a single stateme nt.

6

b. If the SQLDA is not large enough to contain column descriptions for each retrieved column, the program must determine how much space is needed, get storage for that amount of space, build a new SQLDA, and reissue the DESCRIBE statement. 6. Allocate the amount of storage needed to contain a row of retrieved data. 7. Put storage addresses into the SQLDA (SQL descriptor area) to tell SQL where to put each item of retrieved data. 8. FETCH a row. 9. When end of data occurs, close the c ursor. 10. Handle any SQL return codes that might result. ====End of Manual Excerpt== First of all, doing the things described here is non-trivial to say the least. You have to make sure you have created your SQLDA properly. You have to programatically de cipher the outcome of the SQL Describe statement well enough to know the size of the buffer necessary to hold a row (or record). Or you could simply allocate a very large buffer of say 32,767 (or even smaller) if you know the maximum record length of all possible files on your system. But when you get all done, you need to parse the SQLDA to pick up the type and size of each field. If that isn't bad enough, these 10 steps probably left off the hardest part of all. So I've performed the 10 steps above and I have a record in a dynamically allocated chunk of memory. I have in my SQLDA a description of each field in this chunk of memory. If a particular field is character, I can probably deal with that because of RPG's powerful %Subst and other string-handling BIFs. But what if the field is packed? There are 9,920 possible packed numeric descriptions. I suppose you can have a based data structure where you can overlay these 9,920 possibilities on top of each other. But it seems to me you would also need an RPG Select statement with 9,920 possible When clauses. Since that's not practical, we have to take another approach. Here is what I would do. I would write a subprocedure which accepts a character string of any length from 1 to 16 bytes and returns the numeric value. The simplest thing to do here is have the subroutine accept a varying length field by value. That way you can pass it a fixed length field and not have to pass the length. The subroutine can use the %Len BIF to pick up the length. This subroutine would ignore the number of decimals and assume that the packed field has 0 decimals. It would then examine the string byte by byte and would build up the actual value from the packed value contained in the string. The procedure then returns this value. The SQLDA gives me the number of decimals. From that point forward you can use RPG's %Dec BIF. For example if the procedure returns a value (ignoring decimals) which you put into a 30,0 field called DecVal. Also suppose you store the number of digits into a field called Digits. And if you have placed the number of decimals into another 2,0 (or 3,0 if you're fussy about a silly performance issue) called NumDecs. Then from this point forward, you can refer to your packed field with %Dec( DecVal: NumDigits: NumDe cs ). Obviously this is a lot of work and my guess is you're going to conclude that it is not worth it. But anyway, it was worth it to learn something. I made a mistake (surprise!). >Then from this point forward, you can refer to your packed >field with %Dec( DecVal: NumDigits: NumDecs ). You would actually have to refer to your packed field as follows... %Dec( DecVal / ( 10 ** NumDecs ): NumDigits: NumDecs ) Mike Cravitz NEWS/400 Technical Editor

Question: Can anyone that actually know, explain to me what are data queues, what the benefit of using them are, and how to use them. An IBM book number would greatly be appreciated? I am considering using them in a system we are writing at work to get our AS400 to dial out on an asynchronous line to Trans union for credit checks. This program will be accessed by a number of Service Reps. and I am hoping that a Data Queue Guru will shed some light on the in's and out's. Answer(s): Data Queues are a cross between data areas, and message queues. They are a method for asynchronous communication between programs. A typical use for a data queue is to have a job sitting in a batch subsystem waiting for a data queue entry to be created, and multiple programs dropping entries into the data queue. The ERP system my company uses has a single process to print invoices which is triggered by entries from multiple order entry staff to the data queue. It sounds like your application fits the bill for using data queues. There are API programs to read and write to data queues, and they are quite straight-forward to use. If memory serves, they are QSNDDTAQ and QRCVDTAQ, and they are well documented in the book, although I don't know the number. If you like I can send you examples. Benefits: - performance can be dramatically improved over individual submits if the job is complex - record locking conflicts are eliminated if only one job is updating. - they can facilitate clean modular design Drawbacks 7

- they are hard to document well - the next programmer will have to think to figure them out - they can't really be audited, backed up, or for that matter conveniently examined. - the contents are almost invisible, although smart programmers have written program to read the queue, print the entry, and re write it. - Once the entry is read it is gone; if the program halts the entry is lost. This can be gotten around with an audit file; write a record when the entry is written, nad have the receiver program update a status field when done. Also, data queues don't support data definition, so you do need to use data structures if you intend to pass more than a single data element. In the example above, the data queue holds the order to be invoiced as well as the output queue to place the spooled file and the user to notify when it is printed. Explaining them to a 'data queue beginner' is maybe easiest by comparing them to other objects to see similarities and differences. Then you can get into purpose and usability. A data queue is similar to a database file that has records written to it. One program (or many programs) can send entries to the queue. Each entry is similar to a record. Another program (or many programs) can read entries back from the queue, similar to reading records. Differences to begin with are in formats (record descriptions), reading the same entries more than once and speed. An entry on a data queue has no external description; it's just a string of bytes. If you want something like "fields", you'll have to do all the concatenating and substringing yourself. Normally, an entry is read only once. When the entry is read off the queue, it is gone. The first program to read the entry gets it and then it's gone. (It's possible to get around this, but there's seldom a reason to.) Data queues are designed to provide fast communication between programs. You might have a dozen programs feeding entries onto a queue and a single program receiving those entries. The entries might represent transactions that you want performed against your database and you don't want those dozen programs all doing it individually. You centralize the process in the receiver program. The time it takes for an entry to be sent from one program and be received by another is minimal, less than if you used a file to hold records. Alternatively, you might have one program feeding entries as fast as it can onto a queue and have a dozen programs receiving entries. By having the transactions processed by a dozen programs, you can multiply the work being done. And since each entry is removed from the que ue when it's received, you don't have to worry about another program getting the same entry. The speed is partially achieved by eliminating any overhead done by the system. An example is the way the system handles the space used by a data queue as entries are added and removed. If you start a program up to add entries to the queue but there's no program started to receive the entries, the allocated space gets bigger. When the entries are later received and removed from the queue, the space allocated does _not_ get smaller. You must delete and recreate the data queue to recover excess space if want it back. This means you must know the original parameters used to create the *DTAQ object so you can recreate one to match. (There's an API to get this info that you can get into later.) If you prefer, you can think of a *dtaq as being similar to a message queue. You can send messages from one program and another can receive them from the *msgq. If you do a RCVMSG RMV(*YES), the message is gone from the *msgq, similar to how an entry is removed from a *dtaq. And a *dtaq entry has a format similar to a message; i.e., there's no format except what you create yourself. (Note that MSGDTA() can be used to provide some general formatting with a message.) Entries are ge nerally sent by calling the QSNDDTAQ API and received by calling the QRCVDTAQ API. One handy use for me is in CL programs where you're limited to a single file declaration. If you use these APIs, you can use any number of *dtaqs to simulate physical files, either for passing info from one part of a program to another or for passing to a different program(s). Perhaps start by creating a *dtaq with CRTDTAQ and writing a program to send some entries to it. Then do a DMPOBJ and examine the output. Then write a second program to receive the entries and do a second DMPOBJ. Testing it out can be done with some pretty small CLPs. Data queue APIs are technically described for Version 4 in the OS/400 Object APIs manual on the Systems Programming Support Bookshelf. They work quite easily: One program stores information in then : CALL 'QSNDDTAQ' 90 PARM 'DTQ_PMC' P1DTAQ 10 (name of the dataqueue) PARM '*LIBL' P1DLIB 10 ( libl of the dataqueue) PARM 8 P1LEN 50 (length of answer) PARM P1RC ( answer) The background job reads the information CALL 'QRCVDTAQ' 9192 PARM 'DTQ_PMC' P1DTAQ 10 PARM '*LIBL' P1DLIB 10 PARM P1LEN 50 PARM P1RC PARM -1 P1WAIT 50 (wait till somebody puts something in it) If the background job rece ives a 9 the program stops receiving. Quit simple, but effective. 8

Others described how data queues enable asynchronous communications between multiple jobs running on AS/400. Another aspect is the ability to communicate between PC programs and AS/400 jobs via Client Access APIs.

The data queue is a very simple concept. In your application you would have a single job that handles credit checks. When a credit check is needed, the program talking to the service rep sends a message to the data queue for the credit check job. This "wakes up" the waiting credit check job and it proceeds to dial and do the credit check. When it's done it sends a message back to a data queue for the requesting job, waking that job back up and giving it the results of the check. You can do various things, like have the credit check job check the incoming data queue for new messages needing processing before hanging up the line after completing a credit check. Just use a "dequeue but don't wait" operation in this case, vs the usual "dequeue with wait" operation. For some reason queues are rarely used (or provided as primitives) in computer systems, even though they are one of the most efficient and easiest to manage mechanisms for synchronizing multi-threaded applications. A RPGLE Example: DDAT C 'hello world' C PSNDDQ PLIST C PARM DataQ C PARM DataQLib C PARM DataLength C PARM Data C C PRCVDQ PLIST C PARM DataQ C PARM DataQLib C PARM DataLength C PARM Data C PARM Wait C ...... * * Place an entry in a dataq * C MOVEL 'MyDataQ' DataQ C MOVEL 'MyLib' DataQLib C Z-ADD 11 DataLength C MOVEL DAT Data C CALL 'QSNDDTAQ' PSNDDQ ....... * * Read from dataq until the data read is 'QUIT' * C dqdata doueq 'QUIT' C movel 'MyDataQ' DataQ C movel 'AGCTI' DataQLib C move *BLANKS Data C z-add *ZERO DataLength C z-add -1 Wait C call 'QRCVDTAQ' PRCVDQ * Add code to process the data received C enddo Question: Now I want to compare two strings letter by letter using RPG. Who knows any function I can implement this(except %scan)? Answer(s): I'm not sure what you want to achieve, but to compare two fields character-by-character you could use the following code: (sample definitions) D FIELD1 S 999 D FIELD2 S 999 D LENGTH S 3 0 INZ(%SIZE(FIELD1)) D INDEX S + 2 LIKE(LENGTH) 9

10 10 5 0 50

10 10 5 0 50 5 0

//Wait forever

C C C C

1

DO IF ENDIF ENDDO

LENGTH INDEX %SUBST(FIELD1:INDEX:1) =

%SUBST(FIELD2:INDEX:1)

Why can't you just compare them? IF Field1=Field2? If Field1 is 7 char long and Field2 is 8 char long, RPG will test with the greatest length padded with blanks so you will test : "America " (with a blank) = "American" and that's false. How about If Trim(Field1) = Substr(Field2,Len(Field1)) Then Shouldn't it be If Trim(Field1) = Substr(Field2,Len(trim(Field1))) ?? But they don't matc h... with or without the blank...so I don't see the point. Unless you want If Str1 = %SubSt(Str2:1:%Len(%Trim(Str1)) you could use the c function strcmp D strcmp PR D s1 D s2

10I 0 1000 1000

ExtProc('strncmp') Value Value

To use this you would have to terminate the string with nulls C Eval rc = strcmp(s1+x'00':s2+x'00) But using strcmp more complicated It should be one 2 parms, strncmp D strcmp D s1 D s2 gives the exact same answer as comparing the strings in RPG, only in a slower and way. of these (two ways to fix the parameters + two different functions (strcmp takes takes 3) PR 10I 0 ExtProc('strcmp') 1000 Const 1000 Const ExtProc('strcmp') Value options(*string) Value options(*string)

D strcmp PR 10I 0 D s1 * D s2 * OPTIONS(*STRING) isn't strictly necessary. D strcmp D s1 D s2 D len D strcmp D s1 D s2 D len PR 10I 0 1000 1000 10u 0 10I 0 * * 10u 0

ExtProc('strncmp') Const Const Value ExtProc('strncmp') Value options(*string) Value options(*string) Value

PR

Question: There is an ILE C example for the Dynamic Screen Manager API in the book but we need one in ILE RPG - If someone has done this yet or knows where to find on - please let me know.

Answer(s): I once got this sample from someone, it's definitely a good start. * * Bind with *SRVPGM QSNAPI * D F3 c x'33' D sa_norm c x'20' D txt s 128 inz('Press Enter to Roll, F3.') D txtlen s 9b 0 inz(32) D err s 8 inz(x'0000000000000000') D aid s 1 D lines s 9b 0 inz(1) D wf1 s 1 D wrtn s 9b 0 10

D ClrScr D mode D cmdbuf D env D error D WrtDta D data D datalen D fldid D row D col D strmatr D endmatr D strcatr D endcatr D cmdbuf D env D error D GetAID D aid D env D error D RollUp D lines D top D bottom D cmdbuf D env D error C C C C C C C C C C C C C

PR

9b 0 extproc('QsnClrScr') 1 options(*nopass) const 9b 0 options(*nopass) const 9b 0 options(*nopass) const 8 options(*nopass) 9b 128 9b 9b 9b 9b 1 1 1 1 9b 9b 8 0 extproc('QsnWrtDta') 0 0 options(*nopass) const 0 options(*nopass) const 0 options(*nopass) const options(*nopass) const options(*nopass) const options(*nopass) const options(*nopass) const 0 options(*nopass) const 0 options(*nopass) const options(*nopass)

PR

PR

1 extproc('QsnGetAID') 1 options(*nopass) 9b 0 options(*nopass) const 8 options(*nopass) 9b 9b 9b 9b 9b 9b 8 0 extproc('QsnRollUp') 0 const 0 const 0 const 0 options(*nopass) const 0 options(*nopass) const options(*nopass)

PR

Eval DoW Eval

Eval If Leave EndIf Eval EndDo SetOn Return

wrtn = ClrScr('0' : 0 : 0 : err) wrtn = 0 wrtn = WrtDta (txt : txtlen : 0 : 23 : 2 : sa_norm : sa_norm : sa_norm : sa_norm : 0 : 0 : err) wf1 = GetAID (aid : 0 : err) aid = F3

wrtn = RollUp (lines : 1 : 24 : 0 : 0: err)

Lr

Question: Does anyone have any examples of calling APIs from CL? Answer(s): /*-------------------------------------------------------------------*/ /* Program Summary: */ /* */ /* Initialize binary values */ /* Create user space (API CALL) */ /* Load user space with member names (API CALL) */ /* Extract entries from user space (API CALL) */ /* Loop until all entries have been processed */ /* */ /*-------------------------------------------------------------------*/ /* API (application program interfaces) used: */ /* */ /* QUSCRTUS create user space */ /* QUSLMBR list file members */ /* QUSRTVUS retrieve user space */ 11

/* See SYSTEM PROGRAMMER'S INTERFACE REFERENCE for API detail. */ /* */ /*-------------------------------------------------------------------*/ PGM /*-------------------------------------------------------------------*/ /* $POSIT - binary fields to control calls to APIs. */ /* #START - get initial offset, # of elements, length of element. */ /*-------------------------------------------------------------------*/ DCL &$START *CHAR 4 /* $POSIT */ DCL &$LENGT *CHAR 4 /* $POSIT */ DCL DCL DCL DCL START OFSET ELEMS LENGT *CHAR *DEC *DEC *DEC 16 (7 0) (7 0) (7 0)

/*-------------------------------------------------------------------*/ /* Error return code parameter for the APIs */ /*-------------------------------------------------------------------*/ DCL &$DSERR *CHAR 256 DCL &$BYTPV *CHAR 4 DCL &$BYTAV *CHAR 4 DCL &$MSGID *CHAR 7 DCL &$RESVD *CHAR 1 DCL &$EXDTA *CHAR 240 /*-------------------------------------------------------------------*/ /* Define the fields used by the create user space API. */ /*-------------------------------------------------------------------*/ DCL &$SPACE *CHAR 20 ('LSTOBJR QTEMP ') DCL &$EXTEN *CHAR 10 ('TEST') DCL &$INIT *CHAR 1 (X'00') DCL &$AUTHT *CHAR 10 ('*ALL') DCL &$APITX *CHAR 50 DCL &$REPLA *CHAR 10 ('*NO') /*-------------------------------------------------------------------*/ /* various other fields */ /*-------------------------------------------------------------------*/ DCL &$FORNM *CHAR 8 ('MBRL0200') /* QUSLMBR */ DCL &$FIELD *CHAR 30 /* QUSRTVUS */ DCL &$MEMBR *CHAR 10 DCL &$FILLB *CHAR 20 ('QDDSSRC JCRCMDS ') DCL &$MBRNM *CHAR 10 ('*ALL ') DCL &$MTYPE *CHAR 10 DCL &COUNT *DEC (5 0) /*-------------------------------------------------------------------*/ /* Initialize Binary fields and build error return code variable */ /*-------------------------------------------------------------------*/ CHGVAR %BIN(&$START) 0 CHGVAR %BIN(&$LENGT) 50000 CHGVAR %BIN(&$BYTPV) 8 CHGVAR %BIN(&$BYTAV) 0 CHGVAR &$DSERR + ( &$BYTPV || &$BYTAV || &$MSGID || &$RESVD || &$EXDTA)

/*-- Create user space. ---------------------------------------------*/ CALL PGM(QUSCRTUS) PARM(&$SPACE &$EXTEN &$INIT + &$LENGT &$AUTHT &$APITX &$REPLA &$DSERR)

/*-------------------------------------------------------------------*/ /* Call API to load the member names to the user space. */ /*-------------------------------------------------------------------*/ A: CALL PGM(QUSLMBR) PARM(&$SPACE &$FORNM &$FILLB + &$MBRNM '0' &$DSERR) CHGVAR %BIN(&$START) 125 CHGVAR %BIN(&$LENGT) 16 12

/*-------------------------------------------------------------------*/ /* Call API to return the starting position of the first block, the */ /* length of each data block, and the number of blocks are returned. */ /*-------------------------------------------------------------------*/ CALL PGM(QUSRTVUS) PARM(&$SPACE &$START &$LENGT + START &$DSERR) CHGVAR ELEMS %BIN(START 9 4) /* # OF ENTRIES IF (ELEMS = 0) GOTO C /* NO OBJECTS CHGVAR OFSET CHGVAR LENGT %BIN(START 1 4) %BIN(START 13 4) */ */

/* TO 1ST OFFSET */ /* LEN OF ENTRIES */

CHGVAR %BIN(&$START) (OFSET + 1) CHGVAR %BIN(&$LENGT) LENGT

/*-------------------------------------------------------------------*/ /* Call API to retrieve the data from the user space. ELEMS */ /* is the number of data blocks to retrieve. Each block contains a */ /* the name of a member. */ /*-------------------------------------------------------------------*/ CHGVAR &COUNT 0 B: CHGVAR &COUNT (&COUNT + 1) IF (&COUNT *LE ELEMS) DO CALL PGM(QUSRTVUS) PARM(&$SPACE &$START &$LENGT + &$FIELD &$DSERR) CHGVAR &$MTYPE CHGVAR &$MBRNM %SST(&$FIELD 11 10) /* MEMBER TYPE */ %SST(&$FIELD 1 10) /* EXTRACT MEMBER NAME */

IF (&$MTYPE = 'PRTF ') DO /* ANZPRTFF PRTF(&$MBRNM) SRCFILE(JCRCMDS/QDDSSRC) ENDDO CHGVAR OFSET %BIN(&$START) CHGVAR %BIN(&$START) (OFSET + LENGT) GOTO B ENDDO C: ENDPGM

*/

/* This program was done as an example of working with APIs in */ /* a CL program. */ /* */ /*-------------------------------------------------------------------*/ /* Program Summary: */ /* */ /* Initialize binary values */ /* Create user space (API CALL) */ /* Load user space with object names (API CALL) */ /* Extract entries from user space (API CALL) */ /* Loop until all entries have been processed */ /* */ /*-------------------------------------------------------------------*/ /* API (application program interfaces) used: */ /* */ /* QUSCRTUS create user space */ /* QUSLOBJ list objects */ /* QUSRTVUS retrieve user space */ /* See SYSTEM PROGRAMMER'S INTERFACE REFERENCE for API detail. */ /* */ /*-------------------------------------------------------------------*/ PGM /*-------------------------------------------------------------------*/ /* $POSIT - binary fields to control calls to APIs. */ /* #START - get initial offset, # of elements, length of element. */ /*-------------------------------------------------------------------*/ DCL &$START *CHAR 4 /* $POSIT */ 13

DCL DCL DCL DCL DCL

&$LENGT START OFSET ELEMS LENGT

*CHAR 4 *CHAR *DEC *DEC *DEC 16 (5 0) (5 0) (5 0)

/* $POSIT

*/

/*-------------------------------------------------------------------*/ /* Error return code parameter for the APIs */ /*-------------------------------------------------------------------*/ DCL &$DSERR *CHAR 256 DCL &$BYTPV *CHAR 4 DCL &$BYTAV *CHAR 4 DCL &$MSGID *CHAR 7 DCL &$RESVD *CHAR 1 DCL &$EXDTA *CHAR 240 /*-------------------------------------------------------------------*/ /* Define the fields used by the create user space API. */ /*-------------------------------------------------------------------*/ DCL &$SPACE *CHAR 20 ('LSTOBJR QTEMP ') DCL &$EXTEN *CHAR 10 ('TEST') DCL &$INIT *CHAR 1 (X'00') DCL &$AUTHT *CHAR 10 ('*ALL') DCL &$APITX *CHAR 50 DCL &$REPLA *CHAR 10 ('*NO') /*-------------------------------------------------------------------*/ /* various other fields */ /*-------------------------------------------------------------------*/ DCL &$FORNM *CHAR 8 ('OBJL0100') /* QUSLOBJ */ DCL &$FIELD *CHAR 30 /* QUSRTVUS */ DCL &$DEVNM *CHAR 10 /* RMT002P */ DCL &$OBJLB *CHAR 20 ('*ALL QSYS ') DCL &$OBJTY *CHAR 10 ('*LIB ') DCL &COUNT *DEC (5 0) /*-------------------------------------------------------------------*/ /* Initialize Binary fields and build error return code variable */ /*-------------------------------------------------------------------*/ CHGVAR %BIN(&$START) 0 CHGVAR %BIN(&$LENGT) 5000 CHGVAR %BIN(&$BYTPV) 8 CHGVAR %BIN(&$BYTAV) 0 CHGVAR &$DSERR + ( &$BYTPV || &$BYTAV || &$MSGID || &$RESVD || &$EXDTA)

/*-- Create user space. ---------------------------------------------*/ CALL PGM(QUSCRTUS) PARM(&$SPACE &$EXTEN &$INIT + &$LENGT &$AUTHT &$APITX &$REPLA &$DSERR) /*-------------------------------------------------------------------*/ /* Call API to load the object names to the user space. */ /*-------------------------------------------------------------------*/ A: CALL PGM(QUSLOBJ) PARM(&$SPACE &$FORNM &$OBJLB + &$OBJTY &$DSERR) CHGVAR %BIN(&$START) 125 CHGVAR %BIN(&$LENGT) 16 /*-------------------------------------------------------------------*/ /* Call API to return the starting position of the first block, the */ /* length of each data block, and the number of blocks are returned. */ /*-------------------------------------------------------------------*/ CALL PGM(QUSRTVUS) PARM(&$SPACE &$START &$LENGT + START &$DSERR) CHGVAR ELEMS %BIN(START 9 4) /* # OF ENTRIES IF (ELEMS = 0) GOTO C /* NO OBJECTS CHGVAR OFSET %BIN(START 1 4) /* TO 1ST OFFSET 14 */ */ */

CHGVAR LENGT

%BIN(START 13 4)

/* LEN OF ENTRIES */

CHGVAR %BIN(&$START) (OFSET + 1) CHGVAR %BIN(&$LENGT) LENGT

/*-------------------------------------------------------------------*/ /* Call API to retrieve the data from the user space. ELEMS */ /* is the number of data blocks to retrieve. Each block contains a */ /* the name of a object and information about that object. */ /*-------------------------------------------------------------------*/ CHGVAR &COUNT 0 B: CHGVAR &COUNT (&COUNT + 1) IF (&COUNT *LE ELEMS) DO CALL PGM(QUSRTVUS) PARM(&$SPACE &$START &$LENGT + &$FIELD &$DSERR) CHGVAR &$DEVNM %SST(&$FIELD 1 10) /* EXTRACT DEVICE NAME */ INSERT CODE HERE */

/*

CHGVAR OFSET %BIN(&$START) CHGVAR %BIN(&$START) (OFSET + LENGT) GOTO B ENDDO C: ENDPGM Question: I need to use the Retrieve Database File Description (QDBRTVFD) API but I cannot get it to work. Does anyone have an example of how this one works. I need to see whether a file is journaled. Thanks Answer(s): **-- API Error Data Structure: -------------------------** D ApiError DS D AeBytPrv 10i 0 Inz( %Size( ApiError )) D AeBytAvl 10i 0 Inz D AeExcpId 7a D 1a D AeExcpDta 128a ** D FilNam s 10a Inz( 'QADBXREF' ) D FilLib s 10a Inz( '*LIBL ' ) ** D RfFilNamQ s 20a D RfFilNamRtnQ s 20a D RfFmtNam s 8a Inz( 'FILD0100' ) D RfFilOvr s 1a Inz( '0' ) D RfFilRcd s 10a Inz( '*FIRST' ) D RfFilSys s 10a Inz( '*LCL' ) D RfFmtTyp s 10a Inz( '*EXT' ) ** D RfFilInf Ds 4096 D RfFilInfRtn 10i 0 OverLay( RfFilInf: 1 ) D RfFilInfPrv 10i 0 OverLay( RfFilInf: 5 ) D Inz( %Size( RfFilInf )) D RfFilRcdLen 5i 0 OverLay( RfFilInf: 305 ) D RfFilJrnInf 10i 0 OverLay( RfFilInf: 379 ) ** D JrnInf Ds D JiJrnNam 10a D JiJrnLib 10a D JiJrnOpt 1a D JiJrnSts 1a ** C Eval RfFilNamQ = FilNam + FilLib ** C Call 'QDBRTVFD' C Parm RfFilInf C Parm RfFilInfPrv 15

C C C C C C C C ** C C C ** C **

Parm Parm Parm Parm Parm Parm Parm Parm Eval JrnInf =

RfFilNamRtnQ RfFmtNam RfFilNamQ RfFilRcd RfFilOvr RfFilSys RfFmtTyp ApiError %Subst( RfFilInf : RfFilJrnInf + 1 : %Size( JrnInf ))

Return

Question: I have two identical files except for their object names. One is a current production file, the other is a history file with last years data. Of course, I need a program that use these two files as one. But, I can't seem to get a Join Logical to compile. Keeps running into duplicate field and key field names.

Answer(s): Here an example of a multiformat logical file : A R FORMAT1 A FIELD1 A FIELD2 .... A K FIELD1 A* A R FORMAT2 A FIELD1 A FIELD2 .... A K FIELD1

PFILE(FILE1)

PFILE(FILE2)

In the program, you can chain with the file name and you'll get records from both physical files, or the format name and you'll get records only from the specifc PFILE. If you want to update or write a record with this logical file, you must use the format name.

Question: The question I have is how can I redirect the output that goes to STDERR to a file or to the joblog within a RPG-IV program? Whenever I use perror() to print/see the latest error message I see some flashing red lines at the bottom of the screen. Unfortunately my eyes and my brain are to slow to recognize the output. :-( Any suggestions? Answer(s): To redirect stdout or stderr, use an override command, e.g. OVRPRTF STDERR QSYSPRT To see the STDOUT and STDERR after they've flashed on the screen, I have a little command called DSPSTDOUT: Here's the command: CMD PROMPT('Display stdout') Here's the CPP for the command. It must be in activation group *NEW: H dftactgrp(*no) actgrp(*NEW) bnddir('QC2LE') D printf pr extproc('printf') D msg 2a const D newline C X'1500' C callp printf(newline) C return

Question: I'm trying to use the validation list API's from an ILE RPG program. I know this can be done (IBM does it as part of its *ADMIN 16

web server instance) but I'm having trouble converting data types, etc from C to RPG. Anyone have any program samples I could use? Answer(s): Are you talking about QSYADVLE, QSYCHVLE, ... If so, what's the problem with converting the datatypes ? As far as I can see, only character fields (to be defined as A in ILE-RPG, and don't care about the *) and binary fields (to be defined as 9B 0 in ILE-RPG) are required. The API is similar in use than any other API.

I just found your post, and hope the attached can still be of some assistance. D* Provide sample usage program of validation list APIs D* D* To create sample program (call VALIDATE) use: D* CRTBNDRPG PGM(VALIDATE) DFTACTGRP(*NO) BNDDIR(QC2LE) D* D* Refer to Validation List chapter of System API Reference for D* usage details. D* D* get validation list structures from QSYSINC member D* D/copy qsysinc/qrpglesrc,qsyvldl D* D* API Definitions D* Daddvle PR 10I 0 EXTPROC('QsyAddValidationLstEntry') D 20 D 108 D 608 D 1008 OPTIONS(*OMIT) D 1 OPTIONS(*OMIT) Dvfyvle PR 10I 0 EXTPROC('QsyVerifyValidationLst+ D Entry') D 20 D 108 D 608 Drmvvle PR 10I 0 EXTPROC('QsyRemoveValidationLst+ D Entry') D 20 D 108 Derrno PR * EXTPROC('__errno') D* D* Miscellaneous Variables for sample program D* D* The following variable is for the validation list name. This D* validation list must be created prior to program execution using D* CRTVLDL VLDL(QGPL/SAMPLE) D* Dvldl S 20 inz('SAMPLE QGPL ') D* D* The following variable is for API function return value testing D* Dresult S 10I 0 D* D* The following variables are for determining the value of errno D* when API errors occur D* Derrno_val S 10I 0 based(errno_ptr) Derrno_ptr S * D* D* End of miscellaneous variables C* C* Add validation list entry for 'Bruce' C* C* Set entry id length to length of name 'Bruce' C* C eval qsyeidl = 5 C* C* Set CCSID of entry id to Job default C* C eval qsyccsid03 = 0 17

C* C* C* C C* C* C* C C* C* C* C C* C* C* C C* C* C* C C C C C C* C* C C* C* C* C C C C* C* C* C C* C* C* C C C C C C* C* C* C C C* C* C* C C C C* C* C* C C* C* C* C C C C C* C* C* C C C

Set entry id to 'Bruce' eval qsyeid = 'Bruce'

Set encrypted data length to length of 'N1LJDTS' eval qsyedl = 7

Set CCSID of encrypted data to Hex (65535) eval qsyccsid04 = 65535

Set encrypted data to 'N1LJDTS' eval Add the entry for Bruce eval result=addvle(vldl : qsyeidi : qsyeedi : *omit : *omit) qsyed = 'N1LJDTS'

Test for successful add if Verify entry for Bruce eval result=vfyvle(vldl :qsyeidi :qsyeedi) result = 0

Test for successful verify if result = 0

Now attempt to verify 'bad' entry eval eval qsyeid = 'Harry' result=vfyvle(vldl :qsyeidi :qsyeedi) result = 0

if

Incorrect validation has taken place 'inc validate'dsply else Correct validation and rejection has taken place 'correct' dsply end else

Incorrect validation of non-existent entry 'inc invalid' dsply Error on vfyvle, get errno and display it eval dsply end else errno_ptr = errno

errno_val

Error on addvle, get errno and display it eval dsply end errno_ptr = errno

errno_val

18

C* C* Unconditionally clean up added entry C* C* C* Reset entry id to Bruce C* C eval qsyeid = 'Bruce' C eval result=rmvvle(vldl C :qsyeidi) C* C* Return to caller C* C eval *inlr = '1' C return Question: I am looking for any randomize function in AS/400, has anyone done that before? Please advice. Answer() The Basic Random Number Generation (CEERAN0) API generates a sequence of uniform pseudorandom numbers between 0 and 1 using the multiplicative congruent method with a user-specified seed.________________________________________________________________________ | | | Required Parameter Group: | | ____ _______________________________________ ________ ______________ | | | 1 | seed | I/O | INT4 | | | |____|_______________________________________|________|______________| | | | 2 | random_no | Output | FLOAT8 | | | |____|_______________________________________|________|______________| | | Omissible Parameter: | | | 3 | fc | Output | FEEDBACK | | | |____|_______________________________________|________|______________| | |________________________________________________________________________|

Question: Is there anyway to pass the data library into a DB2/400 stored procedure. I am trying to write select statements using the same table name, but want to use that table from different libraries. I am somewhat familiar with the method of building the SQL statement in a character field and then doing a Prepare and Execute. I'm hoping the re is an easier way to do this. Thanks. Answer(s): How about not coding the library name and then utilizing library lists to accomplish the same affect? You could also use SQL ALIASES starting with V4R3 where you could create a single alias name and the n recreate the alias as needed. You'd get the best performance by having a different stored procedure for each library. Switching between libraries will cause more open overhead. How about a routing Stored Procedure where you pass an input parameter and then call different versions of the same stored procedure (but processing table in a different library) based on that input parm? Kent Milligan, DB2 & Business Intelligence team AS/400 Partners In Development Question:

SQL in a CL program Answer(s): SQL in CL is no problem. Add this line to your CL program: RUNSQLSTM SRCFILE(WWLIB/QCLSRC) SRCMBR(MYSQL)COMMIT(*NONE) MYSQL:INSERT INTO ELABACK/TAGESVORG SELECT DISTINCT(A7AENB) FROM ELABACK/ASA7SIC; -INSERT INTO ELABACK/ASALSIC SELECT B.ALAENB, B.ALACDA, B.ALABTM, B.ALAUCD FROM ELABACK/TAGESVORG A, ELADTA/ASALCPP B WHERE A.A7AENB = B.ALAENB; -DELETE FROM WEISS/ASALCPP WHERE ALAENB IN (SELECT A7AENB FROM ELABACK/TAGESVORG); INSERT INTO WEISS/ASALCPP SELECT * FROM ELABACK/ASALSIC;

Question: I want to make sure that the two character Unit of Measure code that my user has entered is valid. At the same time, for

19

each code I want to have a corresponding description and equivalent X12 code. My first thought would be to have a 'Unit of Measure Code Master File' with the following fields. UUNMSR, UNDESC, UUOMCD With records like EA Each EA CS Case CA BX Box CA SP Shelf Pack PK But my concern is the performance hit I would take chaining to this file so often, for example when creating a PO to be sent via X12 EDI I would have to chain to it for every detail record. I definitely do not want to hard code this information into the programs that I want to use it on. I suppose I could load a multiple occurrence data structure with this info. But how do I perform the lookup? Are there other, better(?) ways to do this? Answer(s): Instead of loading the file into a multiple occurrence data structure, use an array. Then you could use a lookup function on the array to find the particular element you need. Thanks for the reply. Maybe I am misunderstanding something here, but I thought that an array was made up of single elements. ie. an array of 10 two character codes. This would be fine for validation, but would not allow me to store the other two fields. Am I missing something here? Yes. You missed the possibility to have other arrays contain the info you need! For instance, you have the array with your unit of measure and find your user's input e.g. in element 2. Then, you can access another array with your descriptions with index 2, and so on. You can load the arrays at program initialization time from a file to avoid hardcoding (manually or by means of a prerun-time array [you'll find this in the RPG reference]), or, which is slightly more complicated, but also a common method, first lookup your array, and if you do not find it there, chain to a file. If you find it there, you can put it into the array(s). But I do not think that there is much performance won. The AS/400 doesn't really access the hard disks every time to get records, OS/400 will move the stuff to the main storage, but you do not have to care about that. A more difficult method, but performing very good, is the use of an user index, but that's not quite a point to start for a beginner. Thanks for the reply. I hadn't thought of breaking the data up like that...I was thinking about working with the entire record as a whole from the file. Yes. Alternate arrays are supported. This lets you define 2 arrays that are related. The first array would be your unit of measure. The second array would be a composite field of the description and X12 unit of measure. Use a data structure to split the second array into its subfields. Normally I would just load the arrays in ordered sequence at initialization time and do a lookup for each record when the new lookup is differant from the prior lookup. This approach saves 1 disk I/O request per output record and minimizes the number of times lookup is actually executed. I/O is much slower than lookup. Lookup is slower than reusing the prior values. You don't need to use MO Arrays. You can define arrays in data structures, and even sort with them. For example: DDS D UOMArray 34DIM(100) D UOMKey 2 OVERLAY(UOMArray) D UOMDesc 30OVERLAY(UOMArray:3) D UOMCode 2OVERLAY(UOMArray:33) Now, you load the fields by saying UOMKey(index) = xxx, UOMDesc(index) = xxx, and UOMCode(index) = xxx This way you have all your fields together. If you wish to sort the arrays and keep the indecies intact, simply sort by the subfield of your choice. For example * sort by Description C SORTA UOMDesc20

* sort by Code C SORTA UOMCode Alternating arrays, who needs em! This is the best array technique I have learned in years. Thanks for the reply. You make some very valid points, especially about not doing the lookup if the last one is for the same code. Since 95% of my items are coded with the same code this should eliminate any performance problems. I had already thought of this after my original post, but in the back of my mind I thought I remembered reading about something that was ideal to this scenario. For a table of this size/complexity (not!), maybe you could just use SETOBJACC to load it into a small memory pool. Then, ignore thoughts of performance degradation. Other than that, if loading into an array doesn't suit your taste, I wouldn't even think twice about the performance aspect unless you're already hitting a performance curve -- in which case you've got other bigger problems. If you only need to see if the entry made exists in your file you can do a SETLL using an indicator in the = position. This technique uses less overhead because no data is brought into the buffer at any time. Also if you are using a CHAIN and the key value does not change from the previous CHAIN, the values still remain in the buffer. This is also less overhead than if the value changes and a read( disk)/write( buffer) actually occurs. It may be the same overhead or less than checking for a changed value in your program before chaining. Just to test the performance hit, I wrote a small program to read the entire article file (88000 records) sequentially. On our model 620, this took 1.5 CPU secs. After adding a chain to our Unit of Measure file the program took 7.1 CPU secs. This shows that a chain takes a significant amouont of time (relatively). If you have a large AS/400 and/or a small article file, you probably won't bother if the job takes 5-10 secs. longer to execute. If you do care, the methods suggested by others (array lookup or chain with array caching), will work fine. If you're using ILE you may write a function that checks the UOM code using SETLL (which requires less CPU than a chain), and functions to retrieve each of the corresponding UOM attributes. These routines may well use arrays, caching, or simply hardcoded data (since they're only specified in one source member, you can move them to a file later if you want to). you make a wrong assumption here. If we talk I/O operations, CPU time is not a concern. The question is runtime. For each sync I/O, the cpu timeslice is ended and the job (after completing the I/o) will have to compete for a new timeslice again. Database I/O is what the AS/400 is designed for. I can't imagine that a simple read to a code table can impact performance in a perceptible manner. Certainly, the coding simplicity of a simple chain is much more desirable than the complexity of other approaches (arrays or data-structures). In many cases simplicity of code is much more desirable than the marginal improvement in response time a caching algorithm would give. However, one thing you should do in whatever design you choose: don't do the lookup if the last lookup was for the same code. This technique alone can save a good percentage of necessary I/O.Question: I need a way to detect if an IFS file exists from an RPG program. Anyone familar with a way to do this? It needs to be detected by file name. Answer(s):

I created a command that functions similarly to the CHKOBJ command, except for an IFS object. The "heart" of the command is the following C code, which could also be created in ILE RPG by correctly protyping the "access" API being used by the C function. /* ** CHKIFSOBJC * * PARAMETERS: Path to file * * DESCRIPTION: Check for IFS object * * RETURNS: Y if objects exists * N if object does not exist 21

* */ char CHKIFSOBJC(const char* reffile) { if( access(reffile, F_OK) != 0) return 'N'; else return 'Y'; } The only problem with using any IFS API, is that adopted authority does not work, which means the user executing the program must be a uthorized to the entire path, and to the file itself, or else the function will look like the object does not exist. I have solved this problem by front-ending the above code with other code that temporarily changes the job user to a profile with sufficient authority. This uses the QSYGETPH and QSYSETP API's. Hope this helps,

You do not specify what version of RPG. With ILE you can do the following: * FileExists * Nick Roux * 1997/10/02 * * NOTE: Compile with DFTACTGRP(*NO) * * IFS API prototypes * * Access * Daccess PR 10I 0 extproc('access') Dpathptr1 * value Dmode1 10I 0 value * * IFS API Constants * DF_OK S 10I 0 inz(0) * * Some working environment for us * DFile_exists S 10I 0 Dpathptr S * Dpathname S 21 DExists C 'File Exists' DNotExists C 'File does not exist' * * Main{} * C *entry plist C parm filename 20 * Set a character pointer to the file name string C eval pathname = %trim(filename)+x'00' C eval pathptr = %addr(pathname) * Call the IFS API C eval File_Exists = access(pathptr:F_OK) * Did we find it? C File_exists ifeq 0 C Exists dsply C else C NotExists dsply C endif * Thats all folks C move *on *inlr The filename should be supplied as //dir/dir/file, i.e. CALL FILEEXISTS ('//etc/pmap') is a valid call.

Question: I'm trying to create an open query file that sorts on a location field in my item file. Here's the problem, my location is a six digit number: aisle (XX) - rack (XX) - shelf (X) - position (X). I need to sort the file such that all the even racks within an aisle are together as are all of the odd racks within that aisle. IE. I need 01-01-1-1 22

01-03-1-1 01-05-1-1 ... 01-02-1-1 01-04-1-1 01-06-1-1 .... Now here is the OPNQRYF I'm working with. OPNQRYF FILE((IORINVMS)) FORMAT(IORINVCT) KEYFLD((IDIVSN) (IWHALS) (IWHREO) (IWHRAK) (IWHSHF) (IWHPOS) (IITNIM)) MAPFLD((IWHALS '%SST(IWHLOC 1 2)') (IWHRAK '%SST(IWHLOC 3 2)') (IWHSHF '%SST(IWHLOC 5 1)') (IWHPOS '%SST(IWHLOC 6 1)') (IWHREO '*MAPFLD/IWHRAK - (2 * ( *MAPFLD/IWHRAK / 2))' what I'm trying to do here is create a field (IWHREO) that is 1 when the rack field (IWHRAK) is odd and 0 when it is even. For this to work I need IWHRAK / 2 to round down so that for example rack 5 / 2 = 2.5 needs to be 2. Lastly, IWHLOC is the six character location field that is in the file. IWHASL, IWHRAK, are defined in the format IORINVCT as zoned 2 digit no decimal. IWHSHF, IWHPOS, IWHREO are defined as zoned 1 digit no decimal. I'm thinking that I may have to have some sort of work field with a couple of decimal places, or perhaps I need to subtract .25 after the division so that the results will round down when the rack is odd ( 5 / 2 -.25 = 2.25) but up when the rack is even ( 6 / 2 - .25 = 2.75). Any thoughts??? Or can someone tell me or point me to a manual that explains the precision being used here? Answer(s): Ok, this seems to work but I'm open to suggestions on how to improve it. OPNQRYF FILE((IORINVMS)) FORMAT(IORINVCT) + KEYFLD((IDIVSN) (IWHALS) (IWHREO) (IWHRAK) + (IWHSHF) (IWHPOS) (IITNIM)) + MAPFLD((IWHALS '%SST(IWHLOC 1 2)') + (IWHRAK '%SST(IWHLOC 3 2)') + (IWHSHF '%SST(IWHLOC 5 1)') + (IWHPOS '%SST(IWHLOC 6 1)') + (XRAKD2 '*MAPFLD/IWHRAK / 2' *ZONED 4 2) + (XRKD2R '*MAPFLD/XRAKD2' *ZONED 2 0) + (IWHREO '*MAPFLD/IWHRAK + (2 * *MAPFLD/XRKD2R)') + ) All you need to do is have the *MAPFLD become the modulus, or remainder, after diving by two. In OPNQRYF, you can get the remainder by using // instead of / for divide: (IWHREO *MAPFLD/IWHRAK // 2) Note that this will cause even numbers to return 0, and odd numbers to return 1 as you requested. However this sorts the even numbers ahead of the odd numbers, and your message also made it sound like you wanted racks 1,3,5,... prior to 2,4,6,... If this is the case, you can reverse the order by something like: (IWHREO %ABSVAL((*MAPFLD/IWHRAK // 2) - 1)) This is untested, but should get the remainder (0 or 1), subtract 1 (giving -1 or 0), then take the absolute value (giving 1 or 0). I think this will give you odd racks, then even racks. Question: Hello all, Has anyone used the named indicators feature in V4R2 ? Specifically I'm trying to find out how to use them to name a general indicator. Say 50, suppose I have it conditioning an Ouput spec. I'd like to be able to say for example EVAL PrtSeq# = Yes instead of EVAL *IN50 = *ON. I've used them with display files indicator area and they are great !! Using named indicators to signify a keying error for example, EVAL CusNumErr = Yes is WAY better then EVAL *IN50 = *ON. Any help is greatly appreciated TIA Answer(s): Here is how to accomplish what you want. D IndPtr S D IndAra S D PrtSeq# 50

* 1 50

Inz( %Addr( *IN ) ) Dim( 99 ) Based( IndPtr ) 23

Question: Hi, I would like to make a dspdtaara for every dtaara named dsp* to an outfile. Is that possible? (DSPDTAARA doesnt do it) (My problem is that we have a dtaara for every Session (Terminal or PC) and in that dtaaras is saved, which printer is to be used from the Session - and I would like to check this Data) Answer(s): With some quick and dirty programming this can't be a problem. I would suggest to use following steps; 1. DSPOBJD the necessary *DTAARA to an outfile 2. Write a CL that reads this outfile and does a RTVDTAARA for each of them 3. Call an RPG program for each of them with name and contents to write to a file.

Question:

CVTDAT command to convert an *MDY to a *LONGJUL Answer(s): *************** Beginning of data *************************** Pgm Dcl Dcl CvtDat &FromDate &ToDate Date( ToVar( FromFmt( ToFmt( *Char 06 '010198' *Char 08 ) + ) + ) + )

&FromDate &ToDate *MDY *LongJul

EndPgm ************************************************************* Question: Currently I get a list of jobs, by user, and place that into a user space. Unfortunately, when I push that list into a UIM interface for a user to scroll and select from the list is not sorted. I would like to sort the data in the user space. Does anyone know of a resource that I can use? I did not find a sort api anywhere...... My other choices are to sort the list in the uim (can this be done?) or put the list into a phyical file and do the sort there. I don't want to do either of those :-( Answer(s): You can use the QLGSORT API, or a user index. I would suggest reading the user space into an overlaying array. Then you can sort by any field in the array very easily... something like this... D USpaceArrDS100DIM(9999) D UserName 10overlay(USpaceArr:1) D JobName 10overlay(USpaceArr:10) etc.... (hope this is right... not at work....) The size of USpaceArr should be the total of bytes from all the fields defined using overlay. This way, you can sort by any subfield using SORTA keeping the data intact and sequenced. Refer to subfileds as UserName(i) or JobName(i) as you would any other array element. Hope this helps! Question : What are the attributes of a JOB Answer(s): Status of job . . . . . . . . . . . . . . . : Current user profile . . . . . . . . . . . : Job user identity . . . . . . . . . . . . . : Set by . . . . . . . . . . . . . . . . . : Entered system: Date . . . . . . . . . . . . . . . . . . : Time . . . . . . . . . . . . . . . . . . : Started: Date . . . . . . . . . . . . . . . . . . : Time . . . . . . . . . . . . . . . . . . : Subsystem . . . . . . . . . . . . . . . . . : 24

ACTIVE TRAIN11 TRAIN11 *DEFAULT 06/06/05 17:12:53 06/06/05 17:12:53 QINTER

Subsystem pool ID . . . . . . . . . . . . : Type of job . . . . . . . . . . . . . . . . : Special environment . . . . . . . . . . . . : Program return code . . . . . . . . . . . . : Controlled end requested . . . . . . . . . : System . . . . . . . . . . . . . . . . . . :

2 INTER *NONE 0 NO S103DCHM

Question: Does anybody know how I can high light a line of code or comment in my RPG source we I am editing it with SEU? Answer(s): Are you interested in causing specific lines of source to appear highlighted when you view the member in SEU? or do you want the current line to be automatically highlighted whenever you e dit it? For the first, you can imbed the hex value for the display attribute (highlight, blink, underline, color, etc.) directly in the source statement. I do that by copying it in from another member. I have a source member named COLORS that has one line for each attribute that I want. I copy the line that I want into the member I'm editting and type over it. (I originally created this member by doing STRCPYSCN to an outfile while viewing various panels that had different display attributes on different fields -- STRCPYSCN includes attribute bytes in the output. You can then get that file into a source member and edit it to arrange things as you like. Not very high-tech, but it was simple.) For the second, you'll either need to install something other than SEU to do your editting or rely on the facility that SEU provides. SEU will highlight the line number if you place the cursor on a line and press . Essentially, SEU shows you the line that you just changed, not the one that you're changing 'now' (which I thought was what you asked for). As far as I know, highlighting the line number is as far as it goes for SEU. There was a program in the November 1993 of NEWS/400 magazine called "SEU in Colors". This is parm driven and can be modified. Alternately, you can use DBU in Hex mode (f9 multiple record display) to enter hex codes for source color. You must write hexadecimal value '22' on 5th position of the command RPG line. You cannot do it from within SEU, you have to write a program that reads a source file and inserts hex code 22 before the text that you wish to highlight. Use the BITON/BITOFF command to setup the hex field and insert it at the begining of the text that you want to highlight. Once you have got one highlighted line you can copy it on from within SEU. Looks quite pretty for comment lines (but it's not to everyones taste) The attribute byte that does highlighting can't be entered on the keyboard, so you'll need another way. One option is to use a program that does it for you (for example on all comment lines), or copy a line with the attribute byte from another source (that way you can use Copy-Overlay in SEU). The best way to do this, you must insert the hexadecimal code for highlight, underlined an so on. You have a Byte that contains 8 Bits. Bit 0 = 1 Bit 1 = 2 Bit 2 = 4 Bit 3 = 8 Bit 4 = 16 Bit 5 = 32 Bit 6 = 64 Bit 7 = 128 -----------255 this is the maximum of one Byte The following table show you, which bit you must activate for the attributes: Display : BIT 5 = Hex : 20 Highlighted : BIT 5 + BIT 1 = HEX : 22 Underlined : BIT 5 + BIT 2 = HEX : 24 Reversed : BIT 5 + BIT 0 = HEX : 21 Blinked : BIT 5 + BIT 3 = HEX : 28 Seperator : BIT 5 + BIT 4 = HEX : 30 NonDisplay : BIT 5 + BIT 0 + BIT 1 + BIT 3 = HEX : 27

25

You can combine this BITS. An example you want to show a string between a field "underlined and highlighted" the you must combine the following BITS: BIT 5 + BIT 1 + BIT 2 = DEC = 32 + 2 + 4 = 38 , HEX = 26 Also you must move X'26' to the field position. And at the end you must set only the BIT 5.

RPG record locking Question: Hi everyone. We have a problem with several users trying to acces the same record , at the same time. The timeout parm on the logical is set to 60 sec. and ma y not be changed, because of another problem, but that another story. :-) Is there anyway to tell there is a lock on a record without having to wait until the request times out.?? I use rpg/400. Please help.. Answer(s): Maybe you can create another logical file with the same key, using the same DDS but giving the object a different name. That way you don't have to use OVRDBF if at creation time you set the waiting time to just a few seconds. In the other hand you have to determine if that is a good option; maybe you have already a lot of LF's and adding a new one would slow down you system's performance. I hope you find what you need between all the answers. :-) Great. OSITim has mentioned OVRDBF in the first answer to the question. If you prolong the time, the user doesn't know why she/he cannot proceed. Most users will think that the AS/400-program is looping or crashed, just like their PC-programs do from time to time. Or they think that the AS/400 is extremely slow and they ask whether to buy a Pentium-processor for the AS/400. They might even use Attn-2 to cancel or turn off the terminal (don't laugh! This shit happens!) to get rid of the Input Inhibited syndrom. This is the worst case scenario, as the application program is most likely in a dangerous situation, from the point of view of the data. --- So if the techniques described so far aren't good enough, combine them with the old-fashioned way: some fields in the database that say "This record is locked by interactive jobnbr/user/job since timesta mp". The applications are not allowed to lock records when they wait for user's input. They fill the fields mentioned above instead. They have to use error indicators on every update/write, however. If something goes wrong, the program should do what the MIS personnel would do. (An AS/400 is said to be operator-less. Programmers should pay attention to this point, especially when they have an OS that enables them to!) Batch programs do not change the locked-fields, except they change the same fields that the user is enabled to change. But this should be avoided by means of mutexes or a self-written mechanism. If this is not possible, the batch-program has to exclude soft-locked records. (10 of 11 records processed - 1 not processed. Does this sound familiar?) Applications are not allowed to crash, they have to monitor everything (as mentioned above). Otherwise the soft-locker-fields still soft-lock the record, although the application isn't active any more. (Extremely uncomfortable with TCP/IP and changing device names!) That's why i suppose to store the job number, user and job name. If the application runs into a "soft-lock", it might check whether the other job is still running. So, put as many thought-pieces together as you want and build a solution. OS/400 offers enough power to do it. You can overwrite the recordwait time in a CLpgm before calling the RPG pgm or in a QCMDEXC call before opening the file in the RPGpgm itself I have this problem and the solution i came up with is: 1. use the error indicator on your chain command 2. immediately after your chain check the error indicator 3. at this point you can send the user a message telling them that the record is in use or as i did call a cl to send the operator/mis person a message to the effect tha t ???? has a record lock. 4. they can then find the person who probably went on break with the record left on their screen and have them get out of it. 5. you can then let the user enter a "r" to retry and have the program loop back to the chain command or have the mis person handle the retry and the program automatically loop back to the chain. That's right... UserB can be batch just as easily as interactive. In one application, with five users updating a database of 25,000 applicants and 40,000 certificate records, the record-lock condition happened around twice a year over a period of six years. No batch was involved, so take care on taking the ris Another scenario: 1. UserA reads record 1 (nolock) and sits on it. 2. Another (batch) process changes information in the record that is not on UserA's screen. 3. UserA wants to update the record, but is informed that the record has changed. How annoying! We generally take the risk that 2 users might interfere and don't worry about it. In the more than 10 years that my company is in business we never had a complaint from any of our about 200 customers (which doesn't mean that it did not happen, of course). We once developed a program template where only the fields that were modified on the screen were output to the file, but it was a lot of coding, so we dropped it. Since the original poster is new to the technique, it should be pointed out that the record contents should be rechecked before the update occurs. 1. UserA reads record 1 (nolock) and sits on it. 2. UserB reads record 1 (nolock) also. 3. UserB quickly re-reads the 26

record (with lock) and updates it. 4. UserA finally re -reads the record (with lock) and updates it also. If the program doesn't verify that the record contents have changed between ste ps 1 and 4. UserA will overwrite UserB's changes. You might try using the error indicator on the CHAIN or READ operation you are using. I believe it's the LO column, but not sure. On the other hand, why is everyone locking a record for such a long time? Is this a file update program that someone likes to sit on for a while, and not realize the problem they are causing? Or, could it be solved by using a nolock on the input operation and then reinputting (ie. CHAIN, READ) when actual update occurs? Then again, you could use a dataq attached to the display file to kick someone off if they sit on the same record for more than say 45 seconds or so. This could be a design problem. I would look into using the nolock option on your input operation until right before you do the update as a first solution. How is a SETLL and a READ different from a CHAIN on an input only file? This is was you're saying, right? If you are not doing updates, then use SETLL then READ to retrieve the record. If you are using CHAIN, then use the 'N' to not lock the record. Hope this helps. When you chain to the file, also use a low indicator. If it comes on, the record is unavailable -so give your user a message to try later. Also, you might want to explore techniques that limit re cord locking. If you can't *change* the WAITRCD parameter, then why don't you try using the OVRDBF with WAITRCD(0) in those programs that you don't want to have wait.

OVRDBF and SECURE() keyword in an ILE environment Question: Does anyone know what the SECURE() keyword is supposed to do on the OVRDBF? It seems to have no effect at all. Also, is it true that OVR's are not scoped to the call level in a native ILE application but rather they are scoped to an activation group name? IOW, if I have programs A, B, and C all compiled to run in activation group FUNNY, and program A calls, B calls C, and C does an override to file PF, and then returns to B which in turn returns to A which performs an OPEN on file PF -- will program A use this OVR? Did you get that? Answer(s): The ILE program must be running in an ILE activation group for activation group level scoping to take effect. If it is running in the default activation group, call level scoping will be in effect. Thanks for the tip! I'll go back and re -read your News/400 article, and perhaps we will change our OVR commands to use *CALLLVL. Actually, I'm not even sure this will work. You see, we are creating a proecdure called CRTHUBDDM() that the user will call from their programs. This procedure will create a DMM to the "hub" machine Db, and override the "F" spec to use this DDM file. The problem is, trying to determine if there are any outstanding overrides in effect already against this file that the tool will OVR() unbeknownst to the programmer. If you do the following: PGMA issues OVRDBF FILE(FUNNY) TOFILE(*LIBL/FUNNY) SECURE(*YES) This then calls PGMB which calls PGMC PGMC issues OVRDBF FILE(FUNNY) TOFILE(QGPL/FUNNY) SECURE(*NO) YOURLIB Library . . . . . . . . . . . *LIBL Object type . . . . . . . . . . > *LIB

Name Name, *LIBL, *CURLIB *ALRTBL, *AUTL, *CFGL...

To see the detail screen as shown below, press . Note that the owner of "YOURLIB" has *ALL authority on the object. Edit Object Authority Object . . . . . . . : Library . . . . . : YOURLIB QSYS Object type Owner . . . . : *LIB JOHNDOE

. . . . . . . :

Type changes to current authorities, press Enter. Object secured by authorization list Object ----Object----. . . . . . . . . . . .: ----------Data----------*NONE

48

User JOHNDOE *PUBLIC

Authority *ALL *EXCLUDE

Opr X _

Mgt X _

Exist X _

Read X _

Add X _

Update Delete X X _ _

F3=Exit F5=Refresh F11=Nondisplay detail

F6=Add new users F12=Cancel

F10=Grant with reference object F17=Top F18=Bottom

Press to add a user to the list of users authorized to this object. Type in the name of the user and *USE for the object authority. Press to return to the previous screen. Notice that *USE gives the user *OBJOPR and *READ authorities on "YOURLIB". (Note: If you want to edit a specific authority, type "X" in the position relating to that authority to grant authority or a space to delete that authority.) Next, we need to allow the user access to the file "SRCFILE". Use EDTOBJAUT to edit the authority on the file "SRCFILE". Type EDTOBJAUT OBJ(YOURLIB/SRCFILE) OBJTYPE(*FILE) or use the prompt to fill in the parameters. Press to add the user to the authorization list with *USE authority. This will allow them to do perform various operations on "SRCFILE" including copying members from the file. To allow them to copy the entire file (i.e. "SRCFILE"), *OBJMGT must be granted. To do that, type "X" under "Mgt" in the detail screen for that user. Note that the object authority changes from *USE to USER DEF (meaning a customized authority).

49

Changing Authorities with GRTOBJAUT and RVKOBJAUT To use GRTOBJAUT and RVKOBJAUT type the command and prompt . Fill in the library name, object name, object type along with the user you are granting authorities and the respective authority being granted. At any time press for more help. Sending and Receiving Network Files Users can send and receive network files to and from each other. The "Send Network File" (SNDNETF) command can be used to send a member of a physical database file (PF-DTA or PF-SRC) to another user. In the example shown below, the member "SNDMBR" of the physical database file "SNDFILE" (which is contained in the library "SNDLIB") is to be sent to the user "RCV". "MKTAS400" is the address of the AS/400 at Minnesota State University, Mankato. When the network file arrives at its destination, a message is sent to both the sender and receiver. Send Network File (SNDNETF) Type choices, press Enter. File . . . . . . . . . . . . . . > SNDFILE___ Library . . . . . . . . . . . > SNDLIB____ User ID: _ User ID . . . . . . . . . . . > RCV_______ Address . . . . . . . . . . . > MKTAS400__ + for more values _ Member . . . . . . . . . . . . . > SNDMBR____

Name Name, *LIBL, *CURLIB Character value Character value Name, *FIRST

Additional Parameters To file type . . . . . . . . . . *FROMFILE_ *FROMFILE, *DATA VM/MVS class . . . . . . . . . . Send priority . . . . . . . . . F3=Exit F4=Prompt F24=More keys F5=Refresh A *NORMAL__ F12=Cancel A, B, C, D, E, F, G, H, I *NORMAL, *HIGH F13=How to use this display

The receiver will have to run the "Work with Network Files" (WRKNETF) command to inspect their network files. Work with Network Files (WRKNETF) User . . . . . . . . . . . . : User ID/Address . . . . . . : Type options, press Enter. 1=Receive network file 3=Submit job 4=Delete network file 5=Display physical file member File -------From----------Arrival---Opt File Member Number User ID Address Date Time __ SNDFILE SNDMBR 1 SENDER MKTAS400 08/26/92 16:37 RCV_______ RCV_______

MKTAS400

F3=Exit F4=Prompt F12=Cancel

F5=Refresh

F9=Retrieve

F11=Display type/records

Type 1 in the "Opt" blank in front of the network file to receive and press to prompt. The following screen will show up. Receive Network File (RCVNETF) Type choices, press Enter. From file . . . . . . . . To data base file . . . . Library . . . . . . . . Member to be received . . To member . . . . . . . .

. . . . .

. . . . .

. > 'SNDFILE'__ Character value . *FROMFILE__ Name, *FROMFILE . *LIBL____ Name, *LIBL, *CURLIB . > 'SNDMBR'__ Character value, *ONLY . *FROMMBR____ Name, *FROMMBR, *FIRST F10=Additional parameters F24=More keys F12=Cancel

F3=Exit F4=Prompt F5=Refresh F13=How to use this display

Fill in the "To data base file", "Library", and "To member" blanks with the appropriate receiving file, library and member names and press . Note that the receiving file must already exist before trying to receive members. 50

Question: Can anyone tell me how to edit / change the signon screen on the as400. All I need to do is to add a sentence to the bottom, company warnings about mis-use etc. Cheers Answer(s):

usually, you can find member QDSIGNON in file QDDSSRC in library QGPL. Copy this member to new one and edit by SEU or SDA. After creating new object you have to change subsystem description (usually QINTER) - CHGSBSD - keyword SGNDSPF.

I just did this for our AS/400 and added a "security" message and the company logo on it. What you need to do is get the QDSIGNON source and edit it to show/say what you want. then for any subsystem you want this to show up on (don't use QCTL so you can at least get into your console) you will have to do a ENDSBS on the subsystem. Then compile the DDS source into a library other than QSYS I put mine in QGPL. Then STRSBS on the subsystems you want the QDSIGNON used in. Then do a CHGSBSD SBSD(QINTER) SGNDSPF(QGPL/QDSIGNON). (Change SGNDSPF to whatever Subsystem(s) you want to use the new signon screen. If you have any problems let me know.

The source file member is QDSIGNON in QGPL/QDDSSRC. Do not change the order of inout capable fields or remove them. If you only want users to be able to enter User ID and Password, you can protect and hide the other fields in the changed DDS. Compile the source into one of your libraries and then change the sub system description for the subsystem such as QINTER by using WRKSBSD. I would advise that you do not change your controlling subsystem just in case! You can add many lines of output text and you can move the positions of input capable fields providing you do not alter their sequence in the DDS.

go to http://as400bks.rochester.ibm.com/bookmgr/home.htm and look up the the book OS/400 Work management there you wil find some information on changing QDSIGNON. The source of QDSIGNON is shipped in QGPL/QDDSSRC. I copied the source and added the following lines: A MSG001 79 O 11 2MSGID(S000001 SIGNON) A MSG002 79 O 12 2MSGID(S000002 SIGNON) A MSG003 79 O 13 2MSGID(S000003 SIGNON) A MSG004 79 O 14 2MSGID(S000004 SIGNON) A MSG005 79 O 15 2MSGID(S000005 SIGNON) A MSG006 79 O 16 2MSGID(S000006 SIGNON) A MSG007 79 O 17 2MSGID(S000007 SIGNON) A MSG008 79 O 18 2MSGID(S000008 SIGNON) create a MSGF SIGNON and add the MSGID's with your text. When creating the sign-on display file with the Create Display File (CRTDSPF) command, secify 256 on the MAXDEV parameter. I created QDSIGNON in QGPL, and changed the SBSD QINTER to look at QGPL/QDSIGNON. I would recommmend not to change the controling subsystem. HTH

How you make use of this powerful ILE feature can greatly impact the performance and flow of your ILE

Published June 2005

51

An ILE activation group is a substructure of a job. It is used to allocate and handle resources used by the programs running within the activation group. Activation groups are a vital component of ILE programming. In this article, we'll explore what activation groups are and how they affect the way your ILE programs run. Program Activation To understand the activation group concept, you have to first understand what ILE program activation is. Any ILE program or service program must be activated before the program is run. Activation initializes resources used by the program, including static variables, open files, SQL cursors, and open files. The activation process also handles binding of programs to associated service programs. The process of activating a program needs to occur only once within a given activation group. When a program is called, if it is not activated, program activation will occur. If, on the other hand, that program has already been activated, the existing activation is used. When a program is activated, any static variables are initialized. Once a program has been activated, these variables remain available for access within the given activation group. It's important to remember that each job running a program has its own copy of each of these static variables. This means that if two users execute the same program, the static variables within each will be unique. Activation Group Options When determining what activation group a program will belong to, you have several options. The default activation group is automatically created when any job starts and destroyed when that job ends. While you can create ILE programs using the default activation group, you really lose much of the functionality that you use ILE for. For example, an ILE RPG program compiled to use the default activation group cannot contain any subprocedures. The default activation group is used by all non-ILE (OPM) programs. When compiling an ILE RPG program, this option is specified on the DFTACTGRP parameter. Valid values are *YES to use the default activation group and *NO to define the activation group to be used. When *NO is specified, additional parameters for the activation group and binding directory to be used are displayed. You have several options when specifying the activation group: You can specify a named activation group that you've defined for the program, you can specify the special value *NEW to create a new activation group, or you can use *CALLER to identify that the program being compiled should always run in whatever activation group the program calling it is running in. With this last option, it is possible to have a true ILE application exist in the default activation group. The default value for this parameter is the QILE named activation group. Each of these has its own merits and purpose. Here's a breakdown of the life cycle of each type of activation group. ? ? ? Named Activation Group--When a program with a named activation group is called, if the activation group does not exist, it is created. It remains in existence until any and all programs using that activation group are no longer active. *NEW Activation Group--A program that was compiled with an activation group of *NEW creates a new activation group each time the program is called. This newly created activation group exists until the program that created it is no longer active. *CALLER Activation Group--When *CALLER is specified, the activation group is already in existence when the program is called and will continue to exist until the activation group is deleted based on one of the two scenarios described above. It's important to mention that the *NEW option is not available when creating a service program using the CRTSRVPGM command. The other two options, however, are both valid on that command. This is because the general idea behind a service program is that it will be used by many other programs. Creating a new activation group each time the service program is accessed wouldn't make much sense. It's also important to note that a program within a given activation group can remain active even after the program has ceased execution. This can be accomplished in RPG, for example, by executing a RETURN statement without first turning on *INLR. In this circumstance, the activation group containing the program will remain in existence until the job under which the activation group has been created ends.52

Activation Group Resources As I mentioned, static variables keep their values as long as an activation group exists. In addition, open files remain open in their current state until their activation group is deleted. Static (or global) variables are either those defined within the main procedure of a program or those defined in subprocedures with the STATIC keyword. As you've already learned, these variables hold their value on concurrent calls to the same program. The source shown in Figure 1 is a simple ILE RPG program that can be used to illustrate static variables. -----------------------------------------------------------Program: AGR001RG Description: Sample of Global Variables Compile Command: CRTBNDRPG PGM(xxx/AGR001RG) SRCFILE(xxx/QRPGLESRC) SRCMBR(AGR001RG) DFTACTGRP(*NO) ACTGRP(TEST) -----------------------------------------------------------DAGR001RG PR Action 1 DAGR001RG PI Action 1 Variable1 S 5 0 C/FREE Select; When Action = 'A'; Vari