You are on page 1of 94

Understanding Physical and Logical Files

solving problems without completely understanding the details. But with a better and more fundamental knowledge of the iSeries, the capability to leverage creative abilities and problem-solving skills increases. This is the third installment in an ongoing series of articles that will help you expand your iSeries skills in only a few minutes. Whether you are a manager who is spread too thin or a beginner with too much to learn in too little time, this condensed overview of database files will make your job easier and more rewarding. The iSeries database, DB2/400, is part of the OS/400 operating system. It is a relational database and has features similar to other databases you may have used such as Microsoft Access, Oracle, or dBase. The standard method of creating files is to define the specifications of the file using DDS. (Of course, DDS is used for other things on the iSeries such as defining display files and print files.)

Create a Physical File To create a physical database file, enter your source statements in a DDS source file member. For now, name the member CUS for customer file. Make sure the member type is PF. Specifying PF tells the compiler that this source code is for a physical file. Enter the records in Figure 1 to define a simple customer file. Notice that the first record has an R in position 17. This means that this line is specifying the record name, in this case CUREC. The TEXT is optional but helps to document the file. After the line naming the record, each line describes a field. The field name is followed by the field length and its data type. The three most-used data types are A for alpha or character, S for numeric, and P for packed decimal. For numeric fields, you must also specify the number of decimal positions. So here, the company number field is a three-digit number with no decimal places. The customer number and ZIP code are also numeric fields with no decimal places. The credit limit is a numeric, packed decimal field with nine digits, two of which are after the decimal point. The rest of the fields are character fields. Once you have entered the DDS source code, you must compile it. You do this by keying option 14 next to your member name on the PDM screen. If you pay attention, you will see that the iSeries is actually executing the Create Physical File (CRTPF) command. It knows to use this command because the member type of the source code is PF. You now have a database physical file. This file has built into it the fields and their attributes.

Create a Physical File with Key Fields Now, I'll modify this file definition to add key fields. If a physical file has key fields, programs can access the records randomly or read them sequentially in the order of the key fields. You can see in Figure 2 that it is simple to add key fields. The UNIQUE record at the beginning of the source is used if you want the iSeries to insist on having no records with duplicate keys. This is optional. At the end of the source code, there are two lines with K in position 17. These lines define the key fields. So, this file will build a key using the company number and then the customer number. Further, it will not allow a duplicate company number/customer number record to be written. The records are written to the file in arrival sequence. If you read the data by specifying keyed access, the records will read as though they have been sorted by company number and customer number. Also, your programs can randomly retrieve records. For example, a CHAIN instruction in RPG can now randomly read the record for a specific company number/customer number. Create a Logical File to Resequence Order of Records Now for the real magic. With only a few lines of source code, you can create a logical file. This is a way of reading the file with a different key. A logical file is a list of key values that point to the physical file. It does not contain copies of the data in the physical file.

To create a logical file, again enter your source statements in a DDS source file member named CUS01. This time, make sure the member type is LF. Figure 3 shows the source code to enter. The first line tells the compiler that this logical file will resequence the records in the physical file named in the PFILE specification. In this case, it is the CUS file. Also, notice that, here, you are using the same record name as the physical file, CUREC. The only remaining lines are like the last two lines of the physical file source in Figure 2. They name the fields that you want to use as keys. Again compile the file by using option 14. This time, the iSeries will execute the Create Logical File (CRTLF) command. You now can read the customer file in city/state sequence simply by reading the file CUS01 in keyed sequence.

Create a Logical File to Select and Resequence Records Finally, look at Figure 4. The S-type record will select only records that have customer status equal to A. Also, this logical file will read the records in customer name sequence. So, a program reading this file in keyed sequence will read the records alphabetically by customer name and will read only records of active customers.

-------------I'm not sure if this is possible with Logical files? I've never seen logicals used for this, and can't find any reference to doing this. Say I have two physical files(sales history and ancient sales history). Both physical files have the same record layout (say Cust#[5A],Date, Sales[3N2]). I'd like to normally access just the "sales history" and not "ancient sales history". On occasion though, I'd like to join the two files.

Sure, I could just copy one, and append the other to it. But, that would eat up some space. Example... Sales History File; Cust# Date Sales Bubba's 1998/01/05 632.23 Fido's 1998/01/01 32.44 Ancient Sales History File; Cust# Date Sales Waldo 1996/12/30 335.23 Bubba's 1993/12/15 201.15 Joined Files View???;; Cust# Date Sales Bubba's 1998/01/05 632.23 Fido's 1998/01/01 32.44 Waldo 1996/12/30 335.23 Bubba's 1993/12/15 201.15 Can DB2/400 do This?? Answer(s):
yes you can, but it is not a join file, just a logical file. if pysical files P1 and P2 have the same format name you can do : R format PFILE( P1 P2) K key1 You can make a 2 format logical file. R format1 PFILE(P1) K key1 R format2 PFILE(P2) K key1 In your program use this second file by file name, not format name. Regards

You definitely can't use multiple format logicals with SQL or Query, so ODBC seems out of the question too. The format selector is especially useful when using program described files in RPG.

Not only can DB2/400 do this, it has been doing this since long before it was called DB2/400. What you are describing can be accomplished with a multi-format logical file.It is not even, striclty speaking, a join file. It is more similar in concept to the UNION ALL sql operation. In your DDS for the logical file specify the following A R CURREC PFILE(CURFILE) A CUSTNUM A CUSTDATE A CUSTSALES A K CUSTNUM A K CUSTDATE DESCEND A* A R OLDREC PFILE(OLDFILE) A CUSTNUM A CUSTDATE A CUSTSALES A K CUSTNUM A K CUSTDATE DESCEND In your HLL when you want to reference current sales history you read/chain/setll using the record format name (CURREC). When you want to reference ancient sales history you read/chain/setll using the record format name (OLDREC). When you want to

reference all the sales history you read/chain/setll using the File Name. You can also perform updates, deletes, and writes to this file. (not possible with a join lf) OS/400 provides another feature called a format selector program where you specify an exit program to call if someone writes to the multi-format logical but doesn't specify which record format to write to. Remember, SQL hates multi-format logicals so you may have trouble using it with ODBC and certain queries. -------------Question: Hi ! I have some trouble creating logical files on my system, hope you can point me in the right direction. I have a database table TEST_TABLE for which I need to create a logical file. When I enter the command CRTLF it prompts for a DDS ( Data Description Specification ). I have seen a few examples of the DDS files for various C programs in the ILE C/400 Programmers Manual. But I dont know How to create one or what format it should be for my file. Suppose the table format is as follows : TEST_TABLE ----------------------------- Field_1 Integer Field_2 Character (10) Field_3 Integer What will be the format or the simplest DDS file ? How do i create the DDS file ? Do I just edit the file in my Editor or is there a special command CRT* somthing ? Thanks Answer(s): the most common way to create a logical is to create the DDS source, which pseudocodes something like pfile() a keyfield1 a keyfield2 a otherfield k keyfield1 k keyfield2 this describes a logical file over file myPhysicalFile, selecting fieds keyfield1, keyfield2, and otherfield, and ordering by keyfield1 and keyfield2. you can also do select and omit logic. you would create this using SEU (STRSEU) or within PDM (WRKMBRPDM). traditionally it will reside in a file QDDSSRC in your library. if you are creating it in PDM, give it file type LF. you can then either use option 14 in PDM to compile, or CRTLF specifying this text as your DDS source. for more specifics, look it up: http://as400bks.rochester.ibm.com/bookmgr/home.htm Question: Help! I've accidentally added thousands of duplicate records to a database table, and now I need to remove them. Is there an easy way to clean them out so I can change the table to not allow duplicates? Answer(s):

If you have SQL, this is possible in a few steps as long as you have fields you can use to determine what is a duplicate. These fields would be the fields you want to be unique in your file. Assuming your file is named MyFile, start by creating a grouping view that includes just the key fields and a field for COUNT(*); use a HAVING clause such as HAVING COUNT(*) > 1. Name the view MyFileGV. This view now shows every set of keys that are duplicated (or triplicated or more). Next, create an inner join view over MyFile and MyFileGV. Include the key fields from MyFile and a field for RRN(MyFile). Name the new field RRNfld. Since the original table is joined to the table of duplicate keys, you now have a view that shows all the record numbers for each duplicate. Name this view MyFileJV. Next, create a grouping view over MyFileJV that includes the key fields and a field for MAX(RRNfld). This view will provide you with the highest RRN() for any of the duplicates.

So far, all you've done is create three new views. The last view tells you exactly which record numbers to delete. A DELETE statement with a WHERE clause can then be based on this last view to delete specific record numbers. The first time you run through the DELETE, the highest record numbers for each duplicate will be deleted and the temporary views will all readjust automatically. The second time you run through it, records that were originally triplicated will be deleted. And so forth for higher multiples. When there are no more records to delete, the temporary views will be empty.

What are data queues and how to use


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 Transunion 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 - 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, and 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. Hope this helps! 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 useability. 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 queue 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 generally 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. Good luck.

They work quite easily: One program stores information in then : CALL 'QSNDDTAQ' 90 PARM 'DTQ_PMC' P1DTAQ 10 PARM '*LIBL' P1DLIB 10 PARM 8 P1LEN 50 PARM P1RC (name of the dataqueue) ( libl of the dataqueue) (length of answer) ( 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 receives a 9 the program stops receiving. Quit simple, but effective. 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. For info on this go to the Info Center: ---------------------------------------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.

Retrieving SMTP Name


Question: I want to create a cgi program (in RPG IV) to create a dynamic page of all users' e-mail addresses. I haven't been able to find an API (or command that output to a file) to retrieve SMTP names. I can use WRKNAMSMTP to display them on screen and DSPDIRE to and outfile to retrieve proper name information, but no SMTP name. Anyone point me in a direction? Answer(s): Look at my AS/400 home page for sample code http://www.robin.no/~nfisketj/srvdire.rpgle Since you put up an ILE RPG example, I thought a CLP would be nice to compare against. Having more or less the same thing in two radically different languages using differing techniques might make the API easier to figure out. It isn't one of the nicer APIs to make work on your own. The CLP can be found at: http://zap.to/tl400 under Files/View my files. The CLP accepts a system distribution directory user ID and address and returns the associated SMTP address. It does not include any handling of the API's error code; it's just a program I

put together for my own use. You can find all defined SMTP names in QUSRSYS/QATMSMTPA.
Before accessing QATMSMTPA, you might want to check out the note from Preventive Service Planning (PSP) Information SF98036 for V4R3: The SMTP files QATMSMTP and QATMSMTPA in library QUSRSYS should not be modified directly or used to replicate SMTP information to other systems. These files may be removed at some future release and you should change any application you have that modifies these files. For more information on how to change SMTP data, see the OS/400 TCP/IP Configuration and Reference, SC41-5420. RPGLE example for Dynamic Screen Manager API 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 D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D txt txtlen err aid lines wf1 wrtn ClrScr mode cmdbuf env error WrtDta data datalen fldid row col strmatr endmatr strcatr endcatr cmdbuf env error GetAID aid env error RollUp lines top bottom s s s s s s s PR 128 inz('Press Enter to Roll, F3.') 9b 0 inz(32) 8 inz(x'0000000000000000') 1 9b 0 inz(1) 1 9b 0 9b 0 extproc('QsnClrScr') 1 options(*nopass) const 9b 0 options(*nopass) const 9b 0 options(*nopass) const 8 options(*nopass) 9b 0 extproc('QsnWrtDta') 128 9b 0 9b 0 options(*nopass) const 9b 0 options(*nopass) const 9b 0 options(*nopass) const 1 options(*nopass) const 1 options(*nopass) const 1 options(*nopass) const 1 options(*nopass) const 9b 0 options(*nopass) const 9b 0 options(*nopass) const 8 options(*nopass) 1 extproc('QsnGetAID') 1 options(*nopass) 9b 0 options(*nopass) const 8 options(*nopass) 9b 0 extproc('QsnRollUp') 9b 0 const 9b 0 const 9b 0 const

PR

PR

PR

D cmdbuf D env D error C C C C C C C C C C C C C Eval DoW Eval

9b 0 options(*nopass) const 9b 0 options(*nopass) const 8 options(*nopass) 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

Eval If Leave EndIf Eval wrtn = RollUp (lines : 1 : 24 : 0 : 0: err) EndDo SetOn Return Lr

Calling APIs from CL - with examples! Question: Does anyone have any examples of calling APIs from CL? Answer(s): Craig Rutledge posting the following examples to the group: /*-------------------------------------------------------------------*/ /* LSTMBRC - List processing for file members - Example Pgm */ /* */ /* By Craig Rutledge, 1/14/95, Version 1.0 */ /* */ /* 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 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 */ /* 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 16 *DEC (7 0) *DEC (7 0) *DEC (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 */

10

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 */ 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 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 %SST(&$FIELD 11 10) /* MEMBER TYPE */ CHGVAR &$MBRNM %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 /*-------------------------------------------------------------------*/ /* LSTOBJC - List processing for OBJECTS - Example Pgm */ /* */ /* By Craig Rutledge, 1/03/94, Version 1.0 */ /* */ /* 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 */ /* */

11

/*-------------------------------------------------------------------*/ /* 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 */ DCL &$LENGT *CHAR 4 /* $POSIT */ DCL DCL DCL DCL &#START &#OFSET &#ELEMS &#LENGT *CHAR 16 *DEC (5 0) *DEC (5 0) *DEC (5 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 ('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 */

12

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 */ 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

How to search all pgms in QCLSRC for a keyword

13

Question: I have to change TAP03 to TAP01 in every CL. To avoid forgetting one, I'm looking for a tool to search all members of QCLSRC and list where it occurs. Anybody knows one? Answer(s): Use option 25 against the file in PDM. This gives you the find string screen. You can set the option in this screen to 2 to bring up any matched source in edit mode. I cant remember, but I think that if you set up your replace options before using this, you can just do replace once the source is displayed.
You can sbmjob the following command: FNDSTRPDM STRING(TAP03) FILE(Your_source_lib/QCLSRC) MBR(*ALL) OPTION(*NONE)PRTMBRLIST(*YES) And before doing this, create a data area to store the device name in. Then when you locate every occurrence in every CL, change the CLs to get the device from the data area rather than hard-coding it into the programs. Next time you have to change the device name, just change the data area. How to redirect the output to STDERR from RPG-IV 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 Barbara Morris -Spec *LIKE DEFN Question: I have a C-spec as follows: 0533.02 C *LIKE

DEFINE

CTN(1)

How do I define this same statement in a D-spec. I tried the following but X gets define as an array. I only want X to have the same length a 1 element of the array CTN. 0277.00 DX S LIKE(CTN) DIM(1)

Answer(s): DX

LIKE(Ctn)

The above is all you need. X will be defined as a field similar to one element of CTN. Remove the DIM(1) from your definition. That's what's causing X to be defined as an array. RPG: Converting Character to Decimal 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

14

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 10i 0 extproc('atoi') D num * options(*string) value D atof pr 8f extproc('atof') D num * options(*string) value Di s 10i 0 Dp s 13p 7 C movel '-100 ' num 6 C eval i = atoi(%trim(num)) * > EVAL i * I = -100 C eval(h) p = atof(%trim(num)) * > EVAL i * P = -000100.0000000 C movel '-5.67 ' num 6 C eval(h) p = atof(%trim(num)) * > EVAL p * P = -000005.6700000 C return Barbara Morris IBM Toronto Lab RPG Compiler Development 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) FIELDB = 7,0(S) FIELDC = 7(A) C* C Z-ADD FIELDA FIELDB C MOVE FIELDB FIELDC C* 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* C MOVE FIELDA FIELDB C FIELDB MULT .01 FIELDC C* Jerry Collins [MISJGC@marsh.net] Handling ILE RPG Numeric Overflow Question:

15

Does anyone know how to identify in ILE RPG that a numeric overflow will occur BEFORE actually executing the EVAL that results in an overflow? I would like to program for this situation instead of continually increasing the size of fields that are usually the result of numbers that are exceptions. I just had to code a field representing a percentage that was 5 positions 0 decimals. The numbers used to calculate the %, although correct, were extreme. I'd rather program around them or have the system truncate as in previous RPG releases. Thanks in advance for any help....

Answer(s): If you want truncation: just use the old instructions; they still work like they used to do. If you want to program around it: IF FieldA * FieldB <= 99999 EVAL FieldC="FieldA" * FieldB ELSE do some error handling ENDIF
If you want to handle it gracefully: use an exception handler. AFAIK there is a special compiler option (which can be set in the "H" spec) which will activate the "old" method of truncating it. But a check with the %error function should work too (or not?). Sorry, but "no" to both. The special compiler options you're probably thinking of (FLTDIV and EXPROPTS) only reduce the likelihood of overflow during expression processing by controlling the size of the temporaries used for sub-expressions. Even with these options, it's still possible to get overflow. %error doesn't act like MONMSG. You have to code an (E) extender on an opcode (not all opcodes allow it), and then afterward you can check %error to see if an error occurred. Just coding %error won't stop the exception. Joep already gave the possibilities for solving this: use the old opcodes if truncation really is a safe and correct thing to do, or use some form of exception handling (use an ILE condition handler, put the calculations in a subprocedure with its own *PSSR). -- Barbara Morris IBM Toronto Lab RPG Compiler Development Help with Subfile Programming in RPG III Question: I am doing a subfile coding in RPGIII, and I would need some help on it. Here is the scenario: The subfile is a display only subfile, and therefore no subfile option is needed. As the no. of records is not that much, all records will be loaded to the subfile in the beginning, let say, SFLSIZ is 150 and SFLPAG is 14. There're certain function keys available, of which, there's a SHIFT-LEFT and a SHIFT-RIGHT key, which allows the user to shift the screen so as to view all columns. I am stuck with the problems: (1) the relative record key; I defined a variable, D1RRN in the File Specification, and incremented it by one while I was loading the records to the subfile; I reset D1RRN to 1 when the subfile is first displayed, but I found it didn't incremented or decremented correctly while paging up and down. Besides, is it right that the paging is handled by the subfile feature? 'cos I found it didn't loop through the Page Up and Page Down routine in my program, unless top of page or bottom of page is hit. (2) I used an indicator to control whether the left half or the right half of the screen is being displayed; the indicator commands a DSPATR(ND) for the corresponding fields. When the SHIFT-LEFT or -RIGHT key is hit, the indicator is turned on or off correspondingly, and then the subfile is displayed again. I didn't get the correct response. Is it right that I should UPDATE the subfile again before I display it? I read some examples that use READC to identify changed records and UPDAT those before writing the subfile; however, per my case, there wouldn't be any change to any record, how can I update every records on the current page? I appreciate anyone who provides me some help on this issue! Books on this subject: Subfile Programming Essentials Answer(s): You found the answer yourself... once a subfile is loaded, it never gives control back to the program for paging (unless PAG=SIZ). You do however get control back when you press the function key, but since the subfile isn't been rebuild it won't be of any use. A solution... keep the subfile static (since you say it's small enough), but make the following changes; 1. Define a big hidden field on te subfile record to contain a full line. 2. Define a single output field with the size of your screen 3. Either seton the SFLNXTCHG when writing the subfile, or use the CHAIN/UPDATE (yes, it works) method to

16

update every record in the subfile 4. Substring out of the big hidden field, the part that you want to show The hidden field can also be replaced by an array if you wish, but I consider the hidden field easier. Regarding the ND approach, I remember from long time ago that it doesn't work on subfiles (but I'm not sure). / Zoned parameter in ILE RPG Question: Hello all! I have a serviceprogram built in ILE/RPG with 2 numeric (packed) parameters. These programs is being called from both RPG/400 programs and other ILE/RPG programs in our application. These calling programs have sometimes zoned and sometimes packed fields used as parameters sent to the service program. My problem is that I get a decimal data error in the service program when I send a zoned field as a parameter. How can I make my serviceprogram understand both datatypes? I have tried the FIXNBR(*ZONED) parameter with no luck. I do not want to change all of my calling programs since they come in hundreds. Any suggestions? Answer(s): If it's straightforward to tell which programs call with zoned parameters and which with packed, I recommend creating a second program that expects zoned parameters and have it call through to the other program with a packed version of the parameter. If that's not possible, you'll have to change the called program to handle either packed or zoned parameters: 1. Declare the parameter as a data structure, and declare another standalone field to hold the value. Example for numeric field of 5 digits: D numparmDS D zonedparm D packedparm D numparm DS 1 1 S

5S 0 5p 0 5p 0

2. Set the value of the standalone field this way: C testn zonedparm 10 (lo indicator) C if *in10 C z-add zonedparm numparm C else C z-add packedparm numparm C endif -- Barbara Morris IBM Toronto Lab RPG Compiler Development If you do not use the parameters as I/O parameters you may also try to declare the parameters passed as CONST or passed by value since the compiler (or runtime???) will take care for the correct format, then. What I do not know at the moment is if there is a need for compiling the calling programs again. Last but not least a question of mine. If it is really a service program (CRTSRVPGM, EPM), how do you call it from a RPG/400 program (OPM)? Sorry but I don't mean a service program in that way. It's a program that any application can use to accomplish something. It's an ordinary CRTBNDRPG. A thought is to go back to RPG/400 which can handle both datatypes? By the way, we're at V4R3! Although RPG/400 allows you to supress decimal data errors for both packed and zoned values, that won't fix your problem. If you declare the parameter in the called program as packed, and pass a zoned value, the program may run without an exception, but the value used in the called program

17

would never be the same as the value that was passed. I wish IGNDECERR had never been allowed - it causes terrible to go unnoticed. Consider this pair of programs. P1 calls P2 with a zoned parameter. P2 is compiled with IGNDECERR(*YES). P2 assigns its parameter to 2 different fields, one set to zero and the other set to -13579. After the "assignment", the two fields still have their original values. ---------------------- P1 -------------------IDS DS I 1 50ZONED C Z-ADD12345 ZONED C CALL 'S2' C PARM ZONED C SETON LR ---------------------- P2 -------------------C *ENTRY PLIST C PARM P 50 * C Z-ADDP TEMP1 50 C Z-ADD-13579 TEMP2 C Z-ADDP TEMP2 50 * C TEMP1 DSPLY TEMP2 C SETON LR -- Barbara Morris IBM Toronto Lab RPG Compiler Development What's the best way to do modulus in CL? Question: I'm trying to do modulus 100 (x Mod 100 for VB folks, x % 100 for C people) in a CL program. In RPG, I'd simply define a 2-position variable and then MOVER a larger number into it (or use MVR). CL bitches about the receiver variable being too small to hold the result (MCH1210). Is there no way around this? Do I have to do some cheesy division-then-multiplication-then-subtraction crap to achieve this result?

Answer(s): Move the numeric value to a char variable and take the last two digits. DCL &Num *DEC (7 0) DCL &Char *CHAR 7 DCL &CMOD100 *CHAR 2 DCL &MOD100 *DEC (2 0) CHGVAR &CHAR &NUM CHGVAR &CMOD100 (%SST(&CHAR 6 2))
CHGVAR &MOD100 &CMOD10 RPG Nesting Source Print Utility Question: I recall in past years using a RPG source print utility that did a nice job of showing structured programming nesting levels. I have need of such a utility again but I am not sure where it came from. Does anyone know if such a utility comes with OS/400 as an IBM supplied program or is it something I have to get elsewhere or write myself. Any help on this endeavor to find an easier way to see if I am missing an 'END' statement prior to compiling the programs I am writing would be sincerely appreciated. Answer(s): The various RPG compilers can do this (not really a part of OS, but it sounds like you have them anyway). See the help text for the INDENT keyword, which defaults to *NONE. You may want to try '|' or ':' instead of just blank(s) for the indentation to see if you like it. You can add GENLVL(0) for CRTRPGPGM to keep from actually creating the program if it has at least one warning, or OPTION(*NOGEN) with CRTBNDRPG or CRTRPGMOD if you are using RPG IV. There have also been various user programs do to something similar, but you asked for an IBM supplied method. An even better IBM supplied method is to use the CODE/400 editor instead of SEU. It can display the source with indentation much like the compiler printout, except that it is nearly instantenous. It also has a host of other features too numerous to mention. The CODE/400 editor is now only available bundled with the Visual Age for RPG compiler, although you don't need to use VARPG to make the package worthwhile. An evaluation copy is available from IBM at

18

http://www.software.ibm.com/ad/varpg/ There is an indent (RPG & CL) utility at Click on the programmer icon. Also get the IFNUMB command while your there. It numbers 1B.. 1E etc. the IF, DO ENDxxx etc.. statements making it very easy to find missing ends. Many other as/400 commands are there (all free). Download the auto -extraction program. Then download the source. Instructions are at the top of each download explain the auto install procedure. More on changing the SIGNON screen Question: We have changed the signon screen to contain company information but we have a need to do more. I thought there was a way that you can use a variable field to display something on the signon screen. For example: If we will have the system down this saturday - on Friday we would like to have a message on the signon screen say "System will be unavailable on Saturday due to upgrade." or During nightly backups - "System will be unavailable until approx. 4am" I thought I had read a year or 2 ago that this was possible by using a message file. Then all a person has to do is to change the message description for the message file. I am not sure if this is true or not but I cannot find it anyplace. Any help would be appreciated - Thanks

Answer(s): Yes - it is possible to do this. Add extra fields to the display file after the last IBM-provided field UBUFFER. Use DDS keyword MSGID( ) where = name of the message file you want to use and = the message ID containing the text you want to display. You can use as many message-derived fields as will fit on the screen. Each message field size can be anything from 1 to 132 bytes (I think that's the maximum length of 1st-level message text - someone will correct me if not). I'm sure you know this already - but since I've been a victim in the past - ensure that you don't resequence your DDS fields in SDA and that you add all extra fields to the end of the DDS source. Also, you need to compile QDSIGNON as LVLCHK(*NO). There are products (check ProSign/400, AS/SURE) that will allow you to change your signon screen. They might help you do what you need to do. Now, a bit of a change in subject - both these products allow you to make changes while the subsystems are up, without recompiling the screen, bringing down the subsystem, etc. Just curious here, how do they do that? If you use message fields (see my earlier reply) then the contents of the sign-on screen change every time the underlying message text changes. I use this to show a real-time display of date & time + % disk used by updating the sign-on screen messages every minute from a background server job. Every time I press Enter on the signon screen without actually logging on (e.g. by leaving user-ID blank), I get the current time etc. refreshed on the sign-on screen. In addition to the other comments, here is an observation on the subject. If most of your users are connecting via Client Access they will not see the Sign On screen and therefore will not see your message. I had once changed the Sign On screen and put a daily message on it, but as we migrated to PCs it became a wasted effort so we stopped it. We have the following on our signon screen: A MSG001 60A O 19 11MSGID(ON001 DPLIBR/ONMSGF)
A MSG002 60A O 20 11MSGID(ON002 DPLIBR/ONMSGF) A MSG003 60A O 21 11MSGID(ON003 DPLIBR/ONMSGF) To change the message text on the signon screen, we just CHGMSGD for the line we want changed. This does not change the menu interactively - if a workstation is already dislplaying the signon screen, it does not change. The change will only show up on the next initial display of the signon screen - like when the terminal is powered on, the PC

19

starts emulation, or the user signs on then back off. A message field is the best way to do this for QDSIGNON (or your own signon display file). A routing program that pops up an info window before TFRCTL QCMD might be better, to catch those who bypass signon. For a message field, I currently use:

MSGLINE

640A O 13 1

with keyword MSGID(SYU 0001 SYUSRSYS/SYUSRMSG) The length of 640 gives me eight full lines I can fill. In order to actually get that much into the message, you must use second-level text rather than first-level text. In order to make second-level text available, all you need to do is specify nothing but the message ID in the first-level text. So, in the case of my example, first-level text for message ID SYU0001 is simply... 'SYU0001'. Be prepared to look long and hard for the documentation on this though. But it is more or less documented and it's a base part of the S/36 support, so IBM assures me it will continue to work. And if all you are doing is adding message fields, you shouldn't need LVLCHK(*NO). At least, I don't use it and my format level identifiers don't change. If you add an actual input or output field, however, this will no longer be true. A silly ILE RPG question Question: Since there's no such thing as a comp.languages.rpgiv group I need to bother you people with a rookie question I'm calling C routines from ILE-RPG, using the PCALL instruction. Works great. To pass null-terminated strings I figured to throw in the %STR function, like PCALL LOG(LOGLVL : %STR(LOGTXT)) but the compiler consistently flags %STR as an invalid token. (N.B. It works when I leave out the %STR() but then I have to modify my C routines to trim and terminate strings). I did read the FM ;-) but can't find any restrictions on using %STR. Perhaps it's not allowed in a PCALL ? That would be a shame because it's the most obvious place to use it. Any ideas most welcome. TIA

Answer(s): Is LOGTXT defined as a character field, or as a basing pointer? It sounds like it's defined as a character field. If so, try this (not properly aligned!) D LogTxt S 20A Based(LogTxtPtr)
D LogTxtPtr S * then use %Str(LogTxtPtr) Hope this helps. Gary Guthrie Editor, The RPG Source Technical Editor, NEWS/400 magazine Another thing that might work, but wouldn't be as elegant as the pointer solution, would be to concatenate an X'00 (Hex 00 - Null) to the end of the string On your prototype define your second parameter with the keyword Options(*String) and then use the built-in-function %TrimR to call your procedure like this: CALLP LOG(LOGLVL : %TRIMR(LOGTXT)) Here's another example:

20

Dperror D C

PR Eval

10I 0 ExtProc( 'perror' ) * Value Options( *String ) RC = perror( %TrimR( FileNam ))

YES ! That works. Hurray, I've written my 'hello world' program in RPG : D txt S 40A INZ('Hello, world !') D log PR EXTPROC('logit') D txt * VALUE OPTIONS(*STRING) C CALLP log(%TRIMR(txt)) C RETURN And the C module linked to this is simply void logit (char *t) { printf("'%s'\n", t); } Thanks for the solution. Are you sure the RPG compiler you use has the same release level as the manual? There are many enhancements to RPGLE between OS/400 3.2 and 4.2. If your compiler is old many of the other suggestions I read so far will also fail to work. In that case, using %trimr() and appending a x'00' manually is your only solution. Also, you cannot write your own str() function in rpg or C, unless you also pass the length of the character field, or know the length of the character field in advance. Since you want to find the last non-blank character, you have to start looking at the end of the character string, and the compiler has no way of finding out, unless you either pass fixedlength fields, or pass the length as a parameter. It would be a great idea though, if the rpg compiler writers gave us a macro or template facility, so that we could write our own functions, which would look like procedure calls, but would be able to handle arbitrary parameter variable lengths, just like the built in functions. Right now prototyped functions cannot handle parameters of different storage size very well. They can if they are character variables, but not numeric ones. You need to code the prototype with Options(*Varsize) which causes the compiler to pass "operational descriptors" along with the parameter. The function then must do a call to CEEDOD to obtain the size of the expression or variable passed. This allows you to do implement this type of function even in V3R2 on CISC machines. Unfortunately operational descriptors are not available for numeric variables so we don't have a good way of knowing length and decimal positions, which makes it hard to implement a %editc() look-alike. Check out the new (V4R2) keyword VARYING available to variables & parameters in RPG/IV. Once the length is set (using %Len or %TrimR where appropriate), the program keeps track of it. Passing procedure parameters this way you won't have to check the actual length, the program will know what storage to access (and what not to). Me thinks this does it: . . . * * * C C C * C * C Determine input parameter lenght for procedure parameter. CALLP CEEDOD(1 : DescType : DataType : DescInfo1 : DescInfo2: InLen : *OMIT) CenterString LeftByte WrkString =

''

check EVAL

21

C %subst(CenterString:LeftByte: C InLen - LeftByte + 1) * C EVAL NbrofChar = %LEN(%trim(WrkString)) * C IF NbrofChar < . %subst(RtnString:Adjust)="." eval C * 1 Adjust="InLen" 2 div NbrOfChar> Found it now.... It's because I use TGTRLS(V3R2M0). Leave that out and it works. Probbaly %STR was introduced after 3.2. Drat ! Thanks all for pointing me in the right direction syntactically. Calling Validation List API From ILE RPG 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 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*

22

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 C* C* Set entry id to 'Bruce' C* C eval qsyeid = 'Bruce' C* C* Set encrypted data length to length of 'N1LJDTS' C* C eval qsyedl = 7 C* C* Set CCSID of encrypted data to Hex (65535) C* C eval qsyccsid04 = 65535 C* C* Set encrypted data to 'N1LJDTS' C* C eval qsyed = 'N1LJDTS' C* C* Add the entry for Bruce C* C eval result=addvle(vldl C : qsyeidi C : qsyeedi C : *omit C : *omit) C* Test for successful add C* C if result = 0 C* C* Verify entry for Bruce C* C eval result=vfyvle(vldl C :qsyeidi C :qsyeedi) C* C* Test for successful verify C* C if result = 0 C*

23

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

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

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 errno_ptr = errno errno_val dsply end else Error on addvle, get errno and display it eval errno_ptr = errno errno_val dsply end Unconditionally clean up added entry Reset entry id to Bruce eval eval Return to caller eval *inlr = '1' return qsyeid = 'Bruce' result=rmvvle(vldl :qsyeidi)

Soft Coding Module Names Question: Anyone out there have a solution for this? I am writing an interactive application that will be made up of around 50 modules. (All RPG) As I complete a module and test it I need it to be available to the users. I have a main selection menu that is a full page sub file. To display the eligible options, I read through a DBF, and if the users authority level is >= the authority required to use the app I write the description of the app to the sub file along with a code to a hidden field in the sub file. Then as I process my READC to see which option the user selected, I call various other modules based on the hidden field.

24

My question: Has any one done something similar except write the name of the module to the hidden field and called the required module. This way I wouldn't have to add a WHEN to the select group, recompile the menu module and update the program every time I add a new app to this system I could just add a record to a DBF and no recompiling needed. Answer(s): If you're going to call a module, you must use CallB or CallP. Both of these instructions require that you hard code the name of the possible modules in your program. Even if you specify a procedure pointer in a CallB statement, you must set the procedure pointer by hard-coding the name of the procedure either in the Inz keyword for the procedure pointer or in an Eval statement in your program. As far as I know, there is no way to do what you want given that you are invoking modules. It may sound like heresy, but why do these have to be modules? If they were standalone programs, you could set a 10character variable with the name of a program can use old-fashioned Call. Unless you are invoking the module many times, there won't be a noticable difference in performance between a bound call and a dynamic call. Mike Cravitz NEWS/400 Technical Editor I don't know what mean by invoking a module, but if you want to call a procedure in a module it must be in a program or service program. The procedure to call can be determined at run time. Any procedure in a service program can be called dynamically with a procedure pointer. The procedure name could be in a file or program variable. Before calling the procedure in the service program you need to "activate" the service program. This can be done two ways. Call any procedure in the service program or use the activate it (you might just create an actsrvpgm) procedure which you bind into every service program). Or, you can activate the service program using the activate program API (This is easiest in C). After the service program is active you can use the retrieve exports api to retrieve a pointer to the procedure you want to call dynamically. I think the point you might be missing here is any program that invokes a procedure must know at bind time (before run time!) the name of that procedure unless you either use MI (as was suggested in a previous post) or you use one of the new APIs which allow you to invoke procedures in a service program without binding. Both of these run time techniques are a bit tricky. NEWS/400 has a web posting on how to invoke the APIs. However, it was written by a C programmer for C programmers. One of the things I want to do when I find the time is to figure out how to do this with RPG IV. Take care. Mike Cravitz NEWS/400 Technical Editor Mike, here's an RPG IV procedure that uses the APIs to return a procedure pointer to a procedure in a service program. Note that the "SysPtr" type depends on an unsupported feature where a declared procedure pointer can be used to hold a system pointer. If you use this feature, I recommend that you use a similar technique (using LIKE) so that if the feature goes away and RPG defines a system-pointer type, you only have to change one place. (I wrote this procedure a while back when we first found out about this "feature" - thought I might as well share it. It would need some additional errorchecking to be really useful - I leave that as an exercise for the keen programmer :-) Barbara Morris IBM Toronto Lab RPG Compiler Development * File: GetProcPtr * Sample invocations for GetProcPtr: * EVAL ptr = GetProcPtr('MYPROC' : 'MYSRVPGM' : '*LIBL') * EVAL ptr = GetProcPtr('MYPROC' : Srvpgm : Library) * EVAL ptr = GetProcPtr(%TRIM(Procname) : SrvPgm : Lib) * (Note that the first parameter can't have trailing blanks) D GetProcPtr PR * PROCPTR D Procedure 100A VARYING CONST D SrvPgm 10A CONST D Library 10A CONST * File: GetPPtr

25

H NOMAIN /COPY GetProcPtr * Define a "System-pointer" type. D SysPtr S * PROCPTR BASED(dummy) *------------------------------------------------------------------P GetProcPtr B EXPORT *------------------------------------------------------------------D GetProcPtr PI * PROCPTR D Procedure 100A VARYING CONST D Srvpgm 10A CONST D Library 10A CONST *----------------------------------------------------* Prototypes and templates for calling ResolveSystemPtr * (RSLVSP.H) *----------------------------------------------------D RSLVSP2 PR EXTPROC('_RSLVSP2') D Ptr LIKE(SysPtr) D Template LIKE(RslvTemplt) CONST D RSLVSP4 PR EXTPROC('_RSLVSP4') D Ptr LIKE(SysPtr) D Template LIKE(RslvTemplt) CONST D LibPtr LIKE(SysPtr) CONST D RslvTemplt DS D Gen_Mat_Id D TypeSubtyp D Object D Req_Auth 32A 2A OVERLAY(Gen_Mat_Id:1) 30A OVERLAY(Gen_Mat_Id:3) 2A INZ(AUTH_NONE)

* See QSYSINC/MIH/MICOMMON for authority constants D AUTH_NONE C X'0000' *----------------------------------------------------* Prototype and templates for ActivateBoundProgram * (QLEAWI.H) *----------------------------------------------------D ActBndPgm PR EXTPROC('QleActBndPgm') D SrvpgmPtr LIKE(SysPtr) CONST D ActMark 10I 0 D ABPInfo LIKE(ABP_Info) D ABPInfoLen 10I 0 CONST D ErrorCode LIKE(ErrCode) D D D D D D D D D ABP_Info ABP_Ret ABP_Avail DS 10I 0 INZ(%size(ABP_INFO)) 10I 0 8A INZ(*ALLX'00') 10I 0 10I 0 7A INZ(*ALLX'00') 1A 1A INZ(*ALLX'00')

ABP_ActGrp ABP_ActMark ABP_Flags

*----------------------------------------------------* Prototype and templates for ActivateBoundProgram * (QLEAWI.H) *----------------------------------------------------D GetExport PR EXTPROC('QleGetExp')

26

D D D D D D D D D D D

SrvpgmMark ExportId NameLen ExportName ExportPtr ExportType ErrorCode EX_NOT_FOUND C EX_PROC C EX_DATA C EX_NO_ACCESS C DS

10I 0 10I 0 CONST 10I 0 CONST 100A CONST * PROCPTR CONST 10I 0 LIKE(ErrCode) 0 1 2 3 10I 0 INZ(0)

D ErrCode D ErrProv

*----------------------------------------------------* Local Variables *----------------------------------------------------D LibPtr S LIKE(SysPtr) D SrvpgmPtr S LIKE(SysPtr) D ActMark S 10I 0 D ProcPtr S * PROCPTR D ExportType S 10I 0 *----------------------------------------------------* First, get the pointer to the service program *----------------------------------------------------C IF Library = '*LIBL' * They specified *LIBL C EVAL TypeSubtyp = x'0203' C EVAL Object = Srvpgm C CALLP RSLVSP2(SrvpgmPtr : RslvTemplt) C ELSE * They specified the library ... * Get the pointer to the library C EVAL TypeSubtyp = x'0401' C EVAL Object = Library C CALLP RSLVSP2(LibPtr : RslvTemplt) C * Get the pointer to the service program C EVAL TypeSubtyp = x'0203' C EVAL Object = Srvpgm C CALLP RSLVSP4(SrvpgmPtr : RslvTemplt : LibPtr) C ENDIF *----------------------------------------------------* Now, activate the service program *----------------------------------------------------C CALLP ActBndPgm(SrvpgmPtr : ActMark : C ABP_Info : %size(ABP_Info) : C ErrCode) *----------------------------------------------------* Finally, get the procedure pointer * We're using nameLen+name rather than export-number * so we pass 0 as the export number. *----------------------------------------------------C CALLP GetExport(ActMark : 0 : C %len(Procedure) : Procedure :

27

C C

ProcPtr : ExportType : ErrCode)

*----------------------------------------------------* Return the procedure pointer *----------------------------------------------------C IF ExportType = EX_PROC C RETURN ProcPtr C ELSE C RETURN *NULL C ENDIF P GetProcPtr E

Here's my test program. I created two service programs each containing a procedure called ABC the procedures just DSPLY the names of their service program. * File GetPPtrT /COPY GetProcPtr D ptr s * PROCPTR C *entry plist C parm srvpgm 10 C parm lib 10 C eval ptr = GetProcPtr('ABC':srvpgm:lib) C callb ptr C seton lr

RPG Multidimensional Arrays in Action

Question: Multidimensional arrays those that require multiple numbers, or dimensions, to uniquely identify each array element can be helpful when you have a list of values that depend on more than one independent set of criteria. Sales commissions, bonuses, insurance premiums, and customer discounts are among the kinds of values that multidimensional arrays can easily and naturally represent.
In "Multidimensional Arrays: The Basics" (May 1998), I introduced multidimensional arrays and discussed why they are useful in business application development. As I explained in that article, even though RPG doesnt support multidimensional arrays, you can simulate them by mapping a multidimensional array to a one-dimensional RPG array. In this article, I present an RPG IV service program that calculates real indexes for simulated multidimensional array indexes, letting you easily work with such arrays in your applications. You must be at V3R7 or later to use the service program. (You can obtain the code for service program MultiDim online at NEWS/400s Webs site, http://www.news400.com/code/newscode.) Build a Page-Equals-Size Lookup Window Question: In this article, Mike Cravitz writes: This month's subfile blueprint shows how easy it is to use subfiles defined with SFLPAG (the number of records that appear on a single screen) equal to SFLSIZ (the number of records initially loaded into the subfile). A page-equals-size subfile lets the user scroll through the complete file while minimizing Process Access Group (PAG) overhead, which can become quite large with a multipage subfile. The page-at-a-time approach is actually necessary when working with files that are frequently updated: with each paging request, the program again retrieves records, thus getting the most recent version of each record. Many programmers consider this type of subfile the most complex to code, but I disagree. My objective this month is to show you how easy it is to implement a page-equals-size subfile.

28

Answer(s):Read the rest here.

Procedures within an ILE RPG program Question: Hi all, We are not using ILE the way it's supposed to be used (just taking advantage of the extra built-in functions & creating pgm objects from them). I was reading briefly through the ILE Reference about prototypes & procedures & was wondering if it is possible to define user-created functions from within one source so as to use them with EVAL. I quickly tested this out but am obviously doing something wrong. Any help in this regard would be appreciated. John. Heres the source (for what it's worth ...) DCHAR30 S 30A DMOVER PR 30A DCHAR30 30A VALUE *--------------------------------------------------------------* MAIN - Main Code *--------------------------------------------------------------C EVAL CHAR30 = 'ABCD' C IF MOVER(CHAR30) = ' A+ C BCD' * whatever C ENDIF C SETON LR

*--------------------------------------------------------------PMOVER B DMOVER PI 30A DCHAR30B 30A VALUE DWRKFLD30A S 30 INZ(*BLANKS) DLEN S 2 0 C EVAL LEN = %LEN(%TRIM(CHAR30)) C EVAL %SUBST(WRKFLD30A:31-LEN:LEN) = %TRIM(CHAR30) C CLEAR CHAR30B C EVAL CHAR30B = WRKFLD30A C RETURN CHAR30B P E Answer(s): I believe this is allowed (can't recall the exact location, but one of the manuals discusses the implication that allocated memory is NOT freed on program termination). My understanding is that RCLRSC will NOT recover it either - you have to end the job to get it back! On that basis, I would use ACTGRP(*CALLER) with caution when calling from the default activation group. I limit it to small utility routines and the like, not large programs with lots of files etc. I'd be interested in hearing what other people do with respect to this issue - how many ILE programs with ACTGRP(*CALLER) are called from OPM, and has anyone had any noticeable problems or degradation? For new stuff, I've taken to creating CLLE with ACTGRP(*NEW) and RPGLE with ACTGRP(*CALLER), which can call other stuff such as service programs also with ACTGRP(*CALLER) ... then when the CL ends, the automatically generated activation group disappears automatically! Not sure whether this is a Good Thing (tm), but I prefer the idea of isolation of the program in its own activation group, with implicit clean up. Perhaps from a performance point of view, it would be better to use a named activation group ... so far, I've taken the view that it isn't too serious a penalty in the context of "user selects function x from menu", where a new activation group is created, but I wouldn't want to see a small subroutine create a new activation group each time it's called, for instance! Opinions?? Thomas, you CAN do prototyping, but just for program calls; you can't make bound calls in the dftactgrp. This is a restriction imposed on the compilers by the system. Apparently terrible consequences can ensue if is done by a bound call in the default activation group, and since the compiler can't tell what a called procedure might do, it can't allow any bound calls. (Sorry, I don't know what the is, and even if I did, I wouldn't be allowed to say). Barbara Morris IBM Toronto Lab RPG Compiler Development just try to compile your program with DFTACTGRP(*NO) and ACTGRP(*CALLER) and your program will run fine, then. I do not

29

know why but prototyping seems to be not allowed with DFTACTGRP(*YES). Can anybody enlighten this for us? You may decide to change the command default of CRTBNDRPG to match the values above. In addition you may set DBGVIEW(*SOURCE) in order to allow source level debugging of your programs. Last but not least you may change the default of STRDBG OPMSRC(*NO) to STRDBG OPMSRC(*YES) in order to allow mixed debugging of OPM and EPM programs. Be sure to compile your opm programs with CRTRPGPGM OPTION(*SRCDBG). By the way on my opinion it is a good idea to change the compiler from RPG-III to RPG-IV. It is not necessary to use the whole bunch of features coming with RPG-IV at once, although they are great, but make use of the enhancements of the language first and have a look the ILE concept, then. It IS very effective. The code is much clearer than a MOVE/EXSR/MOVE and if you take the procedures outside of your main programm (put the D/PR-prototype in a /COPY member and import it if needed) into a service program. And putting this service program into a linker directory doesn't "cost" much time, and your procedures will be reuseable the next time, when you have the same problem. Read about it in the IBM redbook about ILE. Thanks a lot for your help ! Worked perfectly ! I can, however, see that it's going to take quite a while to get into this new method of programming but it can prove to be effective. The F22/F10-topic is discussed in the answers before mine. But even if you have used F10 it should havee worked. Your program should have gone thru the then-part of you if statement. But the field CHAR30 is not changed in any way!!! You have used the value-keyword on your PR/PI-D-specs, and so the value and not a reference to the parameter is given to the procedure. You can change this parameter in your procedure, and the original field in the caller has it's old value. But the return statement at the end of the procedre should give you a proper result. So look at it is this was: With the return-statement, the complete procedure/function-call in your eval/if/or-what-soever-statement is "replaced" with the return-value. When you use a normal (not-value) parameter, the orginal field is changed, but when you do no return, you have no "replacement" value for your procedure call. This sounds silly, but having a little knowledge of C/Java/Pascal/or-whatsoever is quite good, when you are new to the RPG/IV procedures. The are very similar to procedures/function/methods in other languages. You can even use them recursivly. So try a bit more around, and use the F22-step-into key in the ILE-debugger, so you can see what happends. If you want to see the result of your function-call (the return value) put a "Eval xxx = YourFunc(param)" in your code, and you can see the result in xxx. Did you remember to use F22 to step into the subprocedure? This will compile into a pgm object but if I step through it in debug, it doesn't take me to the 'C' specs in the procedure. What I'm trying to do here is invoke the procedure through the EVAL expression & right-adjust whatever contents may be in field CHAR30 but if I EVAL the field after it should have been through the procedure, it is still 'ABCD'. Could you be more specific as to what doesn't work. Does the compilation fail, or does the program fail, or Break msg from RPG Question: Do you know of a way to send a break msg. to a user name from RPG? Answer(s): In general, you can't send break messages to users - you can only break into a workstation session; therefore, break messages can only be sent to workstation message queues. However,... the Send Message (QEZSNDMG) API will do some automatic checking to see what workstation a user is logged on to and route the message to that workstation. This is one of the Operational Assistant APIs. Note that it will break at multiple workstations if the user is logged on different places at the same time. The API QEZSNDMG; see this link. Or review for options the APIs in: this manual.

(1) Call QCMDEXC (2) Call message API to send non-program message Source Debugger for batch jobs

30

Question: Is there a way to debug a program running in batch ? \ Answer(s): Yes, you can debug batch jobs. Here's how. Begin by holding the jobq and submitting the job, or submitting the job hold(*yes) (SBMJOB ... HOLD(*YES)). Don't forget that hold(*yes) can be used in jobd too.

Then start a service job for the submitted job (strsrvjob 000000/*N/jobname)

Next, release the jobq or/and job. Enjoy your debugging. After the STRSRVJOB, then you can start your debug (i.e. the regular STRDBG). To complete your debug, you follow the reverse order (i.e. ENDDBG then ENDSRVJOB). Don Almario, via email, 1/2000>

RPGLE debugging Question: I have been using STRISDB for years, we have recently converted to RPGLE but STRISDB does not work any more. Any suggestions ? Thanks Answer(s): Try compiling your RPGLE program with *ALL rather than the default *STMT, then use the STRDBG command rather than STRISDB. It isn't as pretty as ISDB, but it's 'visual' so it'll get you pretty much the same benefit

31

If you prefer a graphical interface instead of "green screen", there is the cooperative debugger that is part of CODE/400 and VisualAge RPG. See: http://www.software.ibm.com/ad/varpg/codetour/index.html#debug More info and Lotus Screencam demos are here: http://www.software.ibm.com/ad/varpg/about.html Use STRDBG. It handles both ILE, OPM and a combination of both. Make sure that you compile your programs with the correct options on the CRTBNDRPG command. I forget the keyword but the value to use is *LIST (*SOURCE also works.) On OPM programs you also need to set the correct debug view at compile time and I think you need to use *SOURCE here. And if you want STRDBG to debug OPM there is a keyword (the second from the bottom I think) where you need to say *YES. Compile the program with DBGVIEW as *Source and run STRDBG. Timing out display sessions in DDS Question: Can you tell me about multiple device display files and how they're used to time out displays? Answer(s): See Display File Magic with Multiple Device Support, Part I. Building Dynamic Stored Procedures 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 there 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 then 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 Dynamic RPGSQL Question: Does anyone have an idea how to make an rpg programm 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.

32

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. D GenCust D CuName D CuStat D D D D CustAp CuOv30 CuOv60 CuOv90 DS 25 1 DS 9P 2 9P 2 9P 2 E DS S 200 1 'G' 'A' 'O' 25 Inz( 'H' ) 9P 2 Inz( 100 ) 1 Inz( 'C' ) ExtName( OrdHdr )

D OrdDs D SelectStm D D D D

PrmChoice S ChoiceGenCust C ChoiceCustAp C ChoiceOrd C S S S

D GtName D GtCuOv30 D EqOhStat

C/Exec SQL C+ Declare DynamCsr Cursor for C+ DynSqlStm C/End-Exec C C C C *Entry Plist Parm Select When PrmChoice = ChoiceGenCust PrmChoice

C Eval SelectStm = 'Select CuName, CuStat ' + C 'From Customer ' + C 'Where CuName > ?' C/Exec SQL C+ Prepare DynSqlStm C+ From :SelectStm C/End-Exec C/Exec SQL C+ Open DynamCsr C+ Using :GtName C/End-Exec C/Exec SQL C+ Fetch Next C+ From DynamCsr C+ Into :GenCust C/End-Exec C When PrmChoice = ChoiceCustAp

33

C Eval SelectStm = 'Select CuOv30,' C ' CuOv60,' + C ' CuOv90 ' + C 'From Customer ' + C 'Where CuOv30 > ?' C/Exec SQL C+ Prepare DynSqlStm C+ From :SelectStm C/End-Exec C/Exec SQL C+ Open DynamCsr C+ Using :GtCuOv30 C/End-Exec C/Exec SQL C+ Fetch Next C+ From DynamCsr C+ Into :CustAp C/End-Exec C When PrmChoice = ChoiceOrd +

C Eval SelectStm = 'Select * ' C 'From OrdHdr ' + C 'Where OhStat = ?' C/Exec SQL C+ Prepare DynSqlStm C+ From :SelectStm C/End-Exec C/Exec SQL C+ Open DynamCsr C+ Using :EqOhStat C/End-Exec C/Exec SQL C+ Fetch Next C+ From DynamCsr C+ Into :OrdDs C/End-Exec C EndSl

C/Exec SQL C+ Close DynamCsr C/End-Exec C Eval *INLR = *On

Mike Cravitz NEWS/400 Technical Editor 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.

34

===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 define 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 statement. 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

35

SQL where to put each item of retrieved data. 8. FETCH a row. 9. When end of data occurs, close the cursor. 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 decipher 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 stringhandling 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: NumDecs ). 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. Mike Cravitz NEWS/400 Technical Editor 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 Randomize function for the AS/400 Question: I am looking for any randomize function in AS/400, has anyone done that before? Please advice. Answer(s): An Idea but i don't how far it is going to help you out. You can use the API which martin has specified. If you feel like how to go about in creating a random number by your own here is method. You can generate a random number given a range . I have written a program to do so. The logic is like this. Input = range is 200 to 230 Solution: 1.Get the difference i.e.230-200 = 30 now get a random number between 0 to 30 2.Calculate how much seconds we are from a fixed date take a date which is around 1980. 3.Now add the seconds by any constant and multiply by another constant. This is to boost the random number for a small change in seconds. 4.Convert both the seconds and the difference to binary and then do an AND operation. 5.Convert the result binary to decimal. It will be in the range you desire. 6.Add

36

the result to base i.e. 200. You will get the random number. It may look hectic, but very iteresting to do. This i did when i was new to AS/400 as well as Software field so did the binary conversion and AND by using Array by division and addition. This really works good and we are using it to select a random part number for inspection. Or I think so you can call up a c program in AS/400 and get the random number which looks easier . I have not tried till now. Sorry if i have confused you through my poor english. Shouldn't the result of the random function be between 0 and 1 ? You'll only get overflows by your method. Being serious again, take a look at http://AS400BKS.rochester.ibm.com:80/cgi-bin/bookmgr/bookmgr.cmd/BOOKS/QB3AM M01/5.7.1 for information on the CEERAN0 API. Just hook up your AS/400 to an NT server, ping it regularly, and record the time it crashes, that should give you a nice random number. 5.7.1 Basic Random Number Generation (CEERAN0) API The Basic Random Number Generation (CEERAN0) API generates a sequence of uniform pseudorandom numbers between 0 and 1 using the multiplicative congruential method with a userspecified seed. as described in System API Reference OS/400 Integrated Language Environment (ILE) CEE APIs Document Number SC41-4861-01 What's the fastest way to do a simple RPG lookup? Question: I sure you have all been here. 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 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.

37

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. Hope this helps! 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 UOMDesc * 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

38

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. First time Data queue application in RPG Question: Can someone suggest a quick way to get up to speed on using data queues in an RPG program. Any manual references or code snippets would be much appreciated. Answer(s): DDAT C 'hello world' C PSNDDQ PLIST C PARM DataQ 10 C PARM DataQLib 10 C PARM DataLength 50 C PARM Data 50 C C PRCVDQ PLIST C PARM DataQ 10 C PARM DataQLib 10 C PARM DataLength 50 C PARM Data 50 C PARM Wait 50 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'

39

C movel 'MyDataQ' DataQ C movel 'AGCTI' DataQLib C move *BLANKS Data C z-add *ZERO DataLength C z-add -1 Wait //Wait forever C call 'QRCVDTAQ' PRCVDQ * Add code to process the data received C enddo

/ Print file overflow in ILE RPG Question: Can anyone tell me how to handle overflow with an externally described print file ( created with RLU )? I was going to add the OFLIND(*INOF) to the keyword section of the file specs, but the manual says thats only for program described printer files and SEU won't let me do it. HELP! Answer(s): Chris, OA-OG, and OV are for program described print only. Use a numeric indicator. So you may code your keyword...OflInd( *IN60 ). Also, this indicator comes on when there's overflow, but you must programatically turn it off. Your code will look something like this. If *IN60 = *On Write Hdg Eval IN60 = *Off EndIf Mike Cravitz NEWS/400 Technical Editor I usually check overflow by getting the information from a printer informational DS. As it contains the current line, and overflow line, you can make the necessary calculations in your program to see if the format you're planning to write still fits on the page ... and if not, you can write a new header. Here we typically place an error indicator on the write statements. when overflow occurs, the error is trapped, and you can perform your overflow logic. Mapping Fields To Arrays in ILE RPG Question: I'm trying to map a 10 numeric variables (7,6) into an array for clearer coding, but the exact syntax is defeating me. I can do it with alpha vars via the overlay keyword within a D spec, but not numeric. Help! Answer(s): Try this. Its a little bit strange but it works. D pbtw D pbtwpntr s s based(pbtwpntr) dim(10) * inz(%addr(ipbtw00))

The field pbtw is your array (10 elements), based on a pointerfield. The field pbtwpntr is a pointer to the FIRST field of a range of 10 fields (ipbtw00, ipbtw01......ipbtw09). Assumming none of the 10 variables are already defined in a data structure.... D D D D D D D D D MyDs Var01 Var02 Var03 Var04 Var05 Var06 Var07 Var08 DS 7P 7P 7P 7P 7P 7P 7P 7P 6 6 6 6 6 6 6 6

40

D Var09 7P 6 D Var10 7P 6 D MyArr 7P 6 Overlay(Var01) D Dim( 10 ) Mike Cravitz NEWS/400 Technical Editor Named inidicators 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): Thanks, I went to news400.com and looked up the named indicators, It told me it was Feb 97 so I went and got the magazine from our library. That's exactly what I wanted. Oscar, Gary Guthrie outlined a technique for this about a year ago (maybe more) in the magazine. Here is how to accommplish what you want. D IndPtr S * Inz( %Addr( *IN ) ) D IndAra S 1 Dim( 99 ) Based( IndPtr ) D PrtSeq# 50 50 Make sense? Mike Cravitz NEWS/400 Technical Editor STRQMQRY: comparision operator '=' isn't correct?! Question: I have a problem with the parameter SETVAR of the command STRQMQRY. The command looks like this STRQMQRY QMQRY... SETVAR(VAR 'XXX') ... The comparison value is character. Everytime I start the command I get the error message that the comparision operator '=' isn't correct.

Answer(s): Suppose you have a query management query that looks like the following. Select * From Customer Where CuName = &var There are two ways you can be tripped up here. First, query manager is case sensitive with respect to variable names. So if you invoke this query as follows... RunQmQry whatever SetVar( var 'A&P Grocery' ) 1) Since the variable was defined as lower case in the query and since the AS/400 command line monitor converts all names to upper case, the variable var will be converted to VAR which doesn't match the name of any variable in the query. The solution to this problem would be the following query. RunQmQry whatever SetVar( 'var' 'A&P Grocery' ) But we still have one more problem to solve. 2) The above query will attempt to run the following SQL command Select * From Customer Where CuName = A&P Grocery

41

The problem here is there are no apostrophes surrounding the string A&P Grocery as SQL syntax demands. In order to solve this problem, you must use two apostrophes wherever you want the command line monitor to pass a single apostrophe to the query. So the final solution to the problem would look like this. RunQmQry whatever SetVar( 'var' '''A&P Grocery''' )
Notice it takes 3 opening and closing apostrophes to solve the problem. The first apostrophe is simply the one the command line monitor needs in order to pass embedded special characters. The second and third apostrophe tell the command line monitor to pass an apostrophe. Similarly for the closing 3 apostrophes. Mike Cravitz NEWS/400 Technical Editor DSPDTAARA to an outfile - possible? 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. This can be done in 10 minutes.

PCL ESC codes in RPG Question: Hi, All I've got a situation where I'd need to embed some PCL printer codes in either CL or RPG code to be sent to the printer. Pardon that I'm no AS400 person, and can't give much details, but would appreciate some help, especially how to enter the ESC character on the terminal (or however else it's represented as). Any help appreciated. TIA. Answer(s): Create a spool file of type *USERASCII (not SCS), in RPG convert EBCDIC character to the ASCII equivelent. For example take an A and convert it to the ASCII equivelent hex code 41. Use the following snippets of code and a data file I used for holding the printer driver for inspiration.

1. 2. 3.

Snippet #1: The RPG I Specs Snippet #2: ASCII Code to Hex Code Conversion Subroutine Snippet #3: Character translation lookup PF

Webmaster's Note: Again, I found these in the newsgroup. I have provided them here for example purposes. They may or may not be the best way to do this. Please don't email me asking for help with these - they're provided as is. If you're creating a SCS spooled file, what i assume, you can use the SCS command ASCII Transparent, which is hex 03. Then you have to code the length of the ASCII transparent bytes in one byte (including the length byte), and then code the ASCII representation of the escape sequence you want to send. You might however face some problems. E.g. when you have some interface between your AS/400 and the printer, some interfaces go berserk when they do receive a x'03'. Cause: partial implementation of the SCS command set. You can find a description of how to do it on my site at: http://www.angelfire.com/oh/nickroux/tipprtpcl.html and download most of the common PCL codes in RPG and RPGLE format from http://www.angelfire.com/oh/nickroux/download.html

42

Including the constants from the download the following RPGLE will initialize the printer, put it in landscape mode and select A4 from the upper tray. 0100.00 0110.00 0120.00 0130.00 0140.00 O O O O O REPORT E 01 HEAD @INIT 10 @LAND +0000 @A4 +0000 @TUP +0000

The following will send $1B $45 ascii codes to the printer. 0100.00 O REPORT E 01 HEAD 0110.00 O +000 X'03021B45' Sharing DB files between two AS/400s Question: Hello! I've got such a problem here. We have 2 AS/400. One is V3R7, another V4R1. The second has SQL support (I mean the one that I get when I type STRSQL), another does not. Is it possible to access PFs that reside on the first AS from the second one. The problem is that all sort of SELECT type commands can be more or less easily implemented by query, but not the REPLACE. Any advice will be highly appreciated. Answer(s): You can use the SQL CONNECT command to connect to the other AS/400 and then run standard SELECT, etc commands. Although the other AS/400 does not have SQL it does have the SQL engine (as do all AS/400s for file transfer, query, OPNQRYF, etc). The select command will be transmitted to the other AS/400 and executed there, the results will be transmitted back to the first AS/400. The nice thing about AS/400 is that SQL is always on the system, as it is part of the database. The only thing missing on your V3R7 is the user interface ! A workaround for this is to create a SRC-PF with a length of 79, code the SQL statement in it and use the CRTQMQRY/STRQMQRY commands to execute it. I have written a CL-program and command for this to do some checking on the source member and things like that, created an option for it in PDM and now it looks like I have SQL (great for customers who don't). DDM is one way, But a better solution for those who use SQL or Query management is to use the remote database support... If you have APPC communications configured between the two machines (ie. if you can use STRPASTHR either system to the other), then you have all you need to execute remote database queries. (And, no, your remote V3R7 does not need the SQL licensed program installed, SQL runtime support is included in the base OS) Use the WRKRDBDIRE command to define the local database and the remote database locations on both your systems. You will need to define an entry for your local system and one for each remote database your system will connect to. You then use the CONNECT TO in your SQL or Query Management queries to access the remote system. If you move to V4R2, you will want to apply the ptf SF48822 to forego any problems with update/delete operations. DDM is the answer... If you use a lot of interactive inquiry, it can be slow but with casual use... works fine.. Help: AS/400 subfiles Question: Writing file maintenance program for open purchase order file. Typically provide a view screen in add-change-delete maintenance programs. However this program involves header and detail physical files with a logical file required for the detail record handling. Also the view portion will look at all detail records of a PO at the same time. Can I use the same subfile definition for the add and change transaction? Typically the PO generation program would add POs, but I want to include it here also. Can you also reccommend a good book with examples for subfile processing. Answer(s): Get the book, "Subfile Technique for RPG/400 Programmers 2nd Edition" By Jonathan Yergin and Wayne Madden. From DUKE Press (www.news400.com). It has source examples of order entry subfiles. It's a good book, I used it to learn more of the other subfile techniques. Michael Catalani's "Subfiles for RPG Programmers" from Midrange Computing is decent. He takes you from basic to complex subfile

43

design in a nice, progressive manner. Lots of detailed RPG III code and display file examples. A glaring omission is a lack of information about coding subfiles to allow option selection on a subfile line item, except by using cursor position. He promises he will cover doing this by option number 'later', but never does unless I've missed something. And sometimes new keywords and concepts appear in the examples without any explanation in the text. But overall, it is a useful book. Also, Pence & Hawkins' "Power RPG III" and "Power RPG IV" books (also MC) have brief, but informative sections on subfiles, again with examples. They do explain one technique for performing option selection on a subfile by option number. Yes, i would think so. When ready, chain to the file with an indicator in 54-55. If the indicator is on, write the record. if the indicator is off, update it. I think I know what you're asking for. We have many screens like this, including PO entery and Order Entry. Let me see if I have this straight.. here's an example of what I think you want. Purchase Order Entry Vender: _________ PO#:____________ any other header data (leave this area blank to update selected detail lines one at a time) Select options, press Enter. 2=Change 4=Delete Opt PO Line Purchase# Qty Cost Ext. Cost _ 1 Item1 1 2.45 2.45 _ 2 Item2 3 4.23 12.69 _ 3 Item3 2 1.00 2.00 .... ... ... F3=Exit F6=Add F12=Cancel message line ------------------------------------------------ So, this will not only display what is on the PO, but will also allow update of the PO lines. Use the option field to select lines to update, and display the PO line data in the area noted above (This is because there will most likely be more info that can be updated on the subfile line itself, also makes it a little easier). When enter is hit, update the PO detail file. When all lines that were selected have been updated, reload the subfile do display new changes in the subfile. I made this display with IBM standards to make it easier to understand. F6 will display blank data in the detail line screen area with an incremented detail line number. Simple enuff.... Did this help at all?

How do you change the signon screen? 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 A A A A A A A MSG001 MSG002 MSG003 MSG004 MSG005 MSG006 MSG007 MSG008 79 79 79 79 79 79 79 79 O O O O O O O O 11 12 13 14 15 16 17 18 2MSGID(S000001 2MSGID(S000002 2MSGID(S000003 2MSGID(S000004 2MSGID(S000005 2MSGID(S000006 2MSGID(S000007 2MSGID(S000008 SIGNON) SIGNON) SIGNON) SIGNON) SIGNON) SIGNON) SIGNON) SIGNON)

44

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 Determining Even/Odd Values in queries 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 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)') + )

45

Thank you, how in the *!%$ did you know about this operator?? I did not see it in any of the manuals that I looked at. Thanks again, this worked perfectly.

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. Get day of the week in RPG Question: Hi Is there an easy way to get the day of the week in ILERPG, or do I need to use the CEEDAYS/CEEDYWK APIs. If so an example would be appreciated. Thanks Books on this subject: RPG IV Jump Start : Moving Ahead With the New RPG AS/400 Expert : Ready-To-Run RPG/400 Techniques RPG IV By Example Answer(s): Here is the day of week module which I lifted from Bryan Meyer's book RPG IV Jump Start. H NoMain H DatFmt( *ISO ) D GetDow D InpDate P GetDow D PI D InpDate D DayOfWk C C C C C C C InpDate S SubDur Div 7 MvR PR B 1P 0 D Value Export 1P 0 D Value 11P 0 D'1998-08-01' DayOfWk:*D DayOfWk DayOfWk

If DayOfWk > 0 Return DayOfWk Else Return DayOfWk + 7

46

EndIf

P GetDow E This procedure returns 1=Sunday, 2=Monday, etc. Here is a quick module I wrote to test this module. D GetDow PR 1P 0 D InpDate D Value D PrmDate D Dow C C C C C *Entry S S Plist Parm D DatFmt( *MDY ) 1P 0 PrmDate

Dow

Eval Dow = GetDow( PrmDate ) Dsply Eval *INLR = *On

Compile these two modules with PDM option 15. Then bind them together with the CrtPgm command. From the command line you invoke by call the program... Call WhatEverYouNamedTheProgram 'DateInMM/DD/YY-Format' Mike Cravitz NEWS/400 Technical Editor Here's some water to jump in: I swear that I copied this originally out of a RPG manual, but I cannot remember which version/release. I wasn't able to find it in V4R2. There is another example for functions, CvtToHex, and I have no access to the other manuals at this time. H DEBUG * D DayOfWeek PR 1P 0 D DateIn D * D Week_Date S D INZ D WeekDay S 1P 0 * D Date S 6S 0 INZ(120997) * C *DMY MOVE Date Week_Date C EVAL WeekDay = DayOfWeek(Week_Date) * 1=MON,2=TUE,3=WED,4=THU,5=FRI,6=SAT,7=SUN C DUMP C MOVE *ON *INLR P DayOfWeek B D PI 1P 0 D DateIn D * D Weeks S 7 0 D DayNbr S 1P 0 D AnySunday S D INZ(d'1997-04-27') * C DateIn SUBDUR AnySunday Weeks:*D C DIV 7 Weeks C MVR DayNbr C IF DayNbr <1 C EVAL DayNbr="DayNbr" + 7 C ENDIF C RETURN DayNbr P DayOfWeek E Hope this helps. Just jump in the water and copy the function DayofWeek in the ILE RPG manual to a SRC-PF. You then just code EVAL Weekday = DayofWeek(Datefield)

47

This piece of code tells you the day of the week today is that I made for another purpose, but you can modify it easily enough. ********************************************************************* * Compile-time array: AL = days to first of a month, not leap year: * Compile-time array: AR = days to first of a month, leap year: * Compile-time array: ARD = DAYS of the week(The field we want). DAL S 3S 0 DIM(12) PERRCD(12) CTDATA DAR S 3S 0 DIM(12) PERRCD(12) CTDATA DARD S 9A DIM(7) PERRCD(7) CTDATA * ................................................................. * Step 1: find the # of days through the end of last year: C *YEAR SUB 1 LASTY 40 C LASTY MULT 365.25 DAYS 70 * ............. * Step 2: add the days so far this year: * Is it Leap year? (TESTLY if = 0, then its leap year.) C *YEAR MULT 25 TESTLY 20 * Days so far this year, to the first of this month: C Z-ADD UMONTH I 20 C TESTLY IFNE *ZEROS C ADD AR(I) DAYS C ELSE C ADD AL(I) DAYS C END * Then, add in the days so far this month: C ADD UDAY DAYS * ............. * Step 3: Now we have the total number of days since 01/01/0001. * By removing all full weeks since 01/01/0001 we will have * established today's "offset" from 01/01/0001. That * "offset" is the remainder, and is useful as an array index. C DAYS DIV 7 DAYS C MVR I * ............. * Step 4: Logically the remainder can only be 0 - 6. * But 0 is a lousy index so by adding 1 we assure * that the index will be 1-7, and therefore useful. C ADD 1 I * ............. * Step 5: Now, with the index, look up the day of the week. C MOVE ARD(I) TODAY 9 * A frequently asked question is "Hey, how did you know what day * to start with in the ARD array?" Simple. The first time I * did this I started it with Sunday. It was off by one day so I * started the array with Monday instead; then I was off by two * days. But Saturday worked fine. * ................................................................. * ** FebMarAprMayJunJulAugSepOctNovDec (AR days, not leap year) 000031059090120151181212243273304334 ** (AL days, leap year. 000031060091121152182213244274305335 ** DAYS of the week: (becomes "TODAY") (ARD Day-of-week) Saturday Sunday Monday Tuesday WednesdayThursday Friday Well as a last resort, if you are calling it from a controlling CL program, use RTVSYSVAL to just pass the system value QDAYOFWEEK to your program. It's 4 byte char. values *MON *TUE *WED....etc. \ Convert UPPERCASE to lowercase

48

Question: Hi there! I need to convert names entered in the database in the format "JOHN SMITH" to "John Smith", and I've tried to find an utility, a program, a RPG statement or anything to help me do that, but sofar I have not found anything! I have searched in "Deja News" and in the IBM Softcopy Library and the TAATOOL library without any success. Is there such an utility, or do you have any suggestions how to build such a program?

Answer(s): Look at the XLATE opcode. That's what it does. If you wanna see a program using it, go to my web site, pick "RPG Sample Code" and choose the one about translating from UPPER to lower case. http://www.spy.net/~booth Hell, never mind, here's the code. (set your reader to fixed font)
An RPG program to demonstrate a technique to translate upper/lower case text The RPG code: * :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: * : PROGRAM PROPERTY OF: : *: DATE PROGRAMMER ACTION : *: 4/98 Booth Martin Create program : * :............................................................: *: TO TRANSLATE LOWER TO UPPER CASE. : * :............................................................: FXLATEFM CF E WORKSTN I* I 'ABCDEFGHIJKLMNOPQRST-C UP I 'UVWXYZ ' I 'abcdefghijklmnopqrst-C LO I 'uvwxyz,' * .. .. .. .. .. .. .. .. .. * raise lower case names to UPPER CASE. C *INKC DOWEQ*OFF C EXFMTFMT01 C LO:UP XLATEFIELD1 FIELD2 C END C* .. .. .. .. .. .. .. .. .. C SETON LR

Simply capitalizing the first letter of every word is not sophisticated enough. Since you are converting case on an individual's name, you also run the risk of mangling it.

49

For example: "JOHN MACDONALD" will becom "John Macdonald" when it should be "John MacDonald" and O'Reilly becomes O'reilly. I work for a mai-order company, and when an advertising piece mis-prints a prospect's name, that prospect is way more likely to toss the offer in the trash. From personal experience, I know that my name is one that is constantly mangled on junk mail. As a matter of fact I use that fact to screen my mail... if they can't get my name right, then I don't read their stuff. Restoring mixed case to a name is a complex topic, and you must weigh the cost of a sophisticated algorithm to avoid mangling names. Our experience as a company has shown that customers don't mind all CAPS in their printed name, they do mind mis-spellings and goofy capitalization. Try something as follows; DLoCConst('abcdefg... DUpCConst('ABCDEF.... CUp:LoXlateStrStr CEvalLen = %Len(Str) CEval Char = %SubSt(Str:1:1) CLo:UpXlateCharChar CEval%SubSt(Str:1:1) = Char C2DoLenn CIf%SubSt(Str:n-1:1) = ' ' CEval Char = %SubSt(Str:n:1) CLo:UpXlateCharChar CEval%SubSt(Str:n:1) = Char CEndIf I haven't tested this, but it should more or less do the job. I wrote a routine like that in RPG/IV recently; what it does is: a) break name into seperate parts and store leftadjusted in array b) xlate from position 2 upper to lower for each array element loaded c) cat together each part with one blank in between Not to mention the qdcxlate API if you want to externalize the mapping instead of hard coding it. Or even to create an ILE C/400 mod using a for-to loop with the "tolower" macro included. Can be linked into the RPG app. Then we have of course the qtvtbnam function, which is unsupported and undocumented and which I have not tried in a couple of years. Might have been removed.. RPG IV help using APIs Question: Some background: New to AS/400 and RPG Have spent a few months looking at RPG III now want to start working in RPG IV, specifically with API calls and pointers. previous expirence with COBOL (on Honeywell DPS 6), C and Pascal (on PCs) Now then, I've been looking at the following APIs: List Database File Members (QUSLMBR) List Database Relations (QDBLDBR) both of which use a user space to return a list. I would like to use pointers to access this user spaces, as opposed to the Retrieve User Space (QUSRTVUS) API since pointer access is supposed to be faster. First off I was supprised that the source members in QSYSINC/QRPGLESRC for these APIs did not have prototypes for the API calls. Is there a reason for this? Second, it seems to me that the members in QSYSINC/QRPGLESRC can't really be included in your module. By that I mean that for the most part you would not use a /COPY QSYSINC/QRPGLESRC,mbrname in your source code. I base this on the fact that there are no prototypes in those members, and the fact that to use the data structures with a pointer to a user space you have to

50

actually define the DS in your module with a BASED(ptrname) as shown in the example in "System API Programming" appendix B topic 3.2. I can find no way to use the pointer while leaving the DS in the include file. However, I did see this in the RPG manual(p 177): * * Define a basing pointer field and initialize to the address of the * data structure My_Struct. * D My_Struct DS D My_array 10 DIM(50) D D Ptr1 S 16* INZ(%ADDR(My_Struct)) But I don't think that this really has the same effect as D My_Struct DS BASED(Ptr1) D My_array 10 DIM(50) D D Ptr1 S 16* Am I correct in this assumption? Help me out here folks, am I not understanding something here? Thanks in advance, Answer(s): Yup, you must be using p = %addr(arr(offset+1)) to do pointer advancing. This is ok, although handling an offset greater than 32766 leads to additional ugly coding usually involving a based multiple occurrence DS with the char(1) array in it. Brrr. What you can't do with an array but you can with pointer arithmetic is EVAL p = p - offset and even better, EVAL offset = ptr1 - ptr2. I thought you could already do this in V3R2. I'm sure we're using it for APIs that access a user space. Or is it because we're actually advancing in an array.... Charles, you're right that the members in that file are not as useful as they could be. They were written in V3R1 before prototypes were supported in RPG IV. The problem with the data structures not being based is a holdover from the RPG III versions which couldn't declare based data structures. The RPG IV headers were supposed to be "the same" as the RPG III ones so customers could convert their programs to RPG IV without difficulty. But I agree that it's time to do something about them (note that I'm not speaking officially for IBM here). My guess is that we'd have to create a whole new source file to avoid disrupting current users of QRPGLESRC. For now, you can copy (not /COPY) in the data structure subfields into your own source, and unfortunately write the prototypes yourself. Sometimes it's helpful to look at the C prototypes in QSYSINC/H (although sometimes they're just confusing e.g. "void *"). P.S. not sure if anyone answered your question about pointer arithmetic, but yes, starting in V3R7 you can EVAL ptr = ptr + offset (zowie!). Thanks for reminding us about QSYSINC Thanks for your reply. Have a couple of things... > From V3R1 to V3R6, you cannot calculate with pointers. You can have them set > with QUSRPTRUS and %ADDR and that's it. With V3R7, you can calculate with > them like > EVAL Pointer = Pointer + %size(structure). > > Before, you have to use some kind of workaround. That explains this little code snippet in IBM's example correct? C* and then incrementing LSTPTR to the Input Parameter Header C* C EVAL LSTPTR = %ADDR(ARR(QUSOIP + 1)) I wondered why they needed that array defined. In v3r7 can you say add the offset to a pointer with the following: C EVAL LSTPRT = LSTPRT + SizeOfEntry Thanks for you help. you are completely right. Accessing user spaces with pointers is *much* faster than QUSRTVUS. The best thing about it, as you surely discovered, is the possibility of shifting a data area over the user space instead of retrieving them with a call. I saw programs that did then some MOVEing with the data. That has to be slower and it is. What we really miss with the /COPY is the possibility to change the source of the included /COPY member as we were able to with good old Auto Report. But there was only support for field names and attributes, and this can be done with ILE RPG, so i think that there's a little gap in programmer's support. At least is that my state of knowledge, i couldn't find anything about this feature on the handbook CD. Perhaps this is read by a IBMer/ress who solves the riddle or maybe this becomes a requirement? Back to the topic: When you code a DS that is "based", you have to get your pointer field filled. Before that, your DS is somewhere in nirvana. After that, you use a piece of storage that is accessable for you; that is a user space or variable storage of your program. A DS without "based" occupies the same place in the storage all the (run)time and cannot be shifted around. From V3R1 to V3R6, you cannot calculate with pointers. You can have them set with QUSRPTRUS and %ADDR and that's it. With V3R7, you can calculate with them like EVAL Pointer = Pointer + %size(structure). Before, you have to use some kind of workaround. You might be able to use this: D Structure DS BASED(PtrStruct)

51

D Struct_F1 10 D Struct_F2 10 D Struct_Next 1 to move the structure, you use EVAL PtrStruct = %ADDR(Struct_Next) If you have to deal with offsets, what you have to when you use APIs, you might want to use an array. D UserSpace DS BASED(PtrUsp) D Offset 9B 0 D Array 1 32767 DIM(32767) and use EVAL PtrUsp = %ADDR(Array(Offset+1)) Has the disadvantage that you can only jump in 32K-steps in storage. But that can easily be solved by a subroutine that does these jumps in a loop. Hope this helps. RPG IV help using APIs Question: Some background: New to AS/400 and RPG Have spent a few months looking at RPG III now want to start working in RPG IV, specifically with API calls and pointers. previous expirence with COBOL (on Honeywell DPS 6), C and Pascal (on PCs) Now then, I've been looking at the following APIs: List Database File Members (QUSLMBR) List Database Relations (QDBLDBR) both of which use a user space to return a list. I would like to use pointers to access this user spaces, as opposed to the Retrieve User Space (QUSRTVUS) API since pointer access is supposed to be faster. First off I was supprised that the source members in QSYSINC/QRPGLESRC for these APIs did not have prototypes for the API calls. Is there a reason for this? Second, it seems to me that the members in QSYSINC/QRPGLESRC can't really be included in your module. By that I mean that for the most part you would not use a /COPY QSYSINC/QRPGLESRC,mbrname in your source code. I base this on the fact that there are no prototypes in those members, and the fact that to use the data structures with a pointer to a user space you have to actually define the DS in your module with a BASED(ptrname) as shown in the example in "System API Programming" appendix B topic 3.2. I can find no way to use the pointer while leaving the DS in the include file. However, I did see this in the RPG manual(p 177): * * Define a basing pointer field and initialize to the address of the * data structure My_Struct. * D My_Struct DS D My_array 10 DIM(50) D D Ptr1 S 16* INZ(%ADDR(My_Struct)) But I don't think that this really has the same effect as D My_Struct DS BASED(Ptr1) D My_array 10 DIM(50) D D Ptr1 S 16* Am I correct in this assumption? Help me out here folks, am I not understanding something here? Thanks in advance, Answer(s): Yup, you must be using p = %addr(arr(offset+1)) to do pointer advancing. This is ok, although handling an offset greater than 32766 leads to additional ugly coding usually involving a based multiple occurrence DS with the char(1) array in it. Brrr. What you can't do with an array but you can with pointer arithmetic is EVAL p = p - offset and even better, EVAL offset = ptr1 - ptr2. I thought you could already do this in V3R2. I'm sure we're using it for APIs that access a user space. Or is it because we're actually advancing in an array.... Charles, you're right that the members in that file are not as useful as they could be. They were written in V3R1 before prototypes were supported in RPG IV. The problem with the data structures not being based is a holdover from the RPG III versions

52

which couldn't declare based data structures. The RPG IV headers were supposed to be "the same" as the RPG III ones so customers could convert their programs to RPG IV without difficulty. But I agree that it's time to do something about them (note that I'm not speaking officially for IBM here). My guess is that we'd have to create a whole new source file to avoid disrupting current users of QRPGLESRC. For now, you can copy (not /COPY) in the data structure subfields into your own source, and unfortunately write the prototypes yourself. Sometimes it's helpful to look at the C prototypes in QSYSINC/H (although sometimes they're just confusing e.g. "void *"). P.S. not sure if anyone answered your question about pointer arithmetic, but yes, starting in V3R7 you can EVAL ptr = ptr + offset (zowie!). Thanks for reminding us about QSYSINC Thanks for your reply. Have a couple of things... > From V3R1 to V3R6, you cannot calculate with pointers. You can have them set > with QUSRPTRUS and %ADDR and that's it. With V3R7, you can calculate with > them like > EVAL Pointer = Pointer + %size(structure). > > Before, you have to use some kind of workaround. That explains this little code snippet in IBM's example correct? C* and then incrementing LSTPTR to the Input Parameter Header C* C EVAL LSTPTR = %ADDR(ARR(QUSOIP + 1)) I wondered why they needed that array defined. In v3r7 can you say add the offset to a pointer with the following: C EVAL LSTPRT = LSTPRT + SizeOfEntry Thanks for you help. you are completely right. Accessing user spaces with pointers is *much* faster than QUSRTVUS. The best thing about it, as you surely discovered, is the possibility of shifting a data area over the user space instead of retrieving them with a call. I saw programs that did then some MOVEing with the data. That has to be slower and it is. What we really miss with the /COPY is the possibility to change the source of the included /COPY member as we were able to with good old Auto Report. But there was only support for field names and attributes, and this can be done with ILE RPG, so i think that there's a little gap in programmer's support. At least is that my state of knowledge, i couldn't find anything about this feature on the handbook CD. Perhaps this is read by a IBMer/ress who solves the riddle or maybe this becomes a requirement? Back to the topic: When you code a DS that is "based", you have to get your pointer field filled. Before that, your DS is somewhere in nirvana. After that, you use a piece of storage that is accessable for you; that is a user space or variable storage of your program. A DS without "based" occupies the same place in the storage all the (run)time and cannot be shifted around. From V3R1 to V3R6, you cannot calculate with pointers. You can have them set with QUSRPTRUS and %ADDR and that's it. With V3R7, you can calculate with them like EVAL Pointer = Pointer + %size(structure). Before, you have to use some kind of workaround. You might be able to use this: D Structure DS BASED(PtrStruct) D Struct_F1 10 D Struct_F2 10 D Struct_Next 1 to move the structure, you use EVAL PtrStruct = %ADDR(Struct_Next) If you have to deal with offsets, what you have to when you use APIs, you might want to use an array. D UserSpace DS BASED(PtrUsp) D Offset 9B 0 D Array 1 32767 DIM(32767) and use EVAL PtrUsp = %ADDR(Array(Offset+1)) Has the disadvantage that you can only jump in 32K-steps in storage. But that can easily be solved by a subroutine that does these jumps in a loop. Hope this helps. Detecing IFS Files from RPG 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.

53

/* ** CHKIFSOBJC * * PARAMETERS: Path to file * * DESCRIPTION: Check for IFS object * * RETURNS: Y if objects exists * N if object does not exist * */ 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 authorized 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)

54

* 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. SQL in a CL program Question: Is this posible and if so does someone have an example. I have no problem with SQL in RPG but I have not found any example of its use in CL programs. Answer(s): SQL in CL is no problem. Add this line to your CL program: 0037.00 RUNSQLSTM SRCFILE(WWLIB/QCLSRC) SRCMBR(MYSQL) COMMIT(*NONE) An SQL member looks like this: (the member type in PDM is 'SQL' instead of 'CLP') 0002.00 INSERT INTO ELABACK/TAGESVORG 0003.00 SELECT DISTINCT(A7AENB) FROM ELABACK/ASA7SIC; 0004.00 -0005.00 INSERT INTO ELABACK/ASALSIC SELECT B.ALAENB, B.ALACDA, B.ALABTM, 0006.00 B.ALAUCD FROM ELABACK/TAGESVORG A, ELADTA/ASALCPP B WHERE A.A7AENB 0007.00 = B.ALAENB; 0011.00 -0012.00 DELETE FROM WEISS/ASALCPP 0013.00 WHERE ALAENB IN (SELECT A7AENB FROM ELABACK/TAGESVORG); 0014.00 INSERT INTO WEISS/ASALCPP SELECT * FROM ELABACK/ASALSIC; Note: *** RUNSQLSTM runs the whole SQL member, no way to select only one statement of it. So you must create more SQL members for a CLP, if you need to run the statements from several places of your CLP. Perhaps like this: CLP = MYCLPGM, SQL members: MYCLPGM1, MYCLPGM2, ... then you have them right below the CL in PDM. *** Create and test the SQL statements with STRSQL and then use the save option to store them into a source member. *** Seen the ';' at the end of each line? It's important. *** You can also execute SQL with the Operations Navigator. Right click the "Database" item. Keep in mind that SQL SELECT statements aren't applicable in this environment. Calling AS/400 APIs from ILE RPG Question: I am working on a project that requires the use of the Dynamic Screen Manager APIs, but try as I might there seems to be no way of declaring the call to match the parameter requirements of the API. The problem seems to be that all RPG calls, ILE or otherwise, whether you specify that the parameter is passed by reference, value or as a constant, the parameter passed is always the address of the data. Now in normal inter language calls this is OK. CL expects this and C is quite happy because all the parameters are just declared as being passed as pointers. Then you try and use the AS/400 APIs. I don't know if you have studied the APIs much, but the vast majority of parameter calls require that the parameter is passed by value, not by address. Until now have had no choice but to write a noddy little wrapper routine in C so I can cast the data types and call the API, but surely there must be a way of calling the API directory from ILE RPG. What was wrong with the API designers when they wrote AS/400, didn't they think RPG would be used for writing programs? If anyone has any idea at all how the hell you can call an API where the parameters need to be

55

passed by value, please let me know. NOTE: The value keyword on procedure declarations does not actually pass the parameter by value, the procedure has code to copy the contents of the passed variable into the local variable at run time. Answer(s): The following little program in ILE RPG uses a few of the DSM API's. Browsing quickly through the API reference manual I couldn't see any DSM API's requiring value declarations. D f3 D sa_norm D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D D C C C C C C C C txt txtlen err aid lines wf1 wrtn clrscr mode cmdbuf env error wrtdta data datalen fldid row col strmatr endmatr strcatr endcatr cmdbuf env error getaid aid env error rollup lines top bottom cmdbuf env error c c s s s s s s s pr x'33' x'20' 128 inz('Press Enter to Roll, F3 to quit.') 9b 0 inz(32) 8 inz(x'0000000000000000') 1 9b 0 inz(1) 1 9b 0 9b 0 extproc('QsnClrScr') 1 options(*nopass) const 9b 0 options(*nopass) const 9b 0 options(*nopass) const 8 options(*nopass) 9b 0 extproc('QsnWrtDta') 128 9b 0 9b 0 options(*nopass) const 9b 0 options(*nopass) const 9b 0 options(*nopass) const 1 options(*nopass) const 1 options(*nopass) const 1 options(*nopass) const 1 options(*nopass) const 9b 0 options(*nopass) const 9b 0 options(*nopass) const 8 options(*nopass) 1 extproc('QsnGetAID') 1 options(*nopass) 9b 0 options(*nopass) const 8 options(*nopass) 9b 0 extproc('QsnRollUp') 9b 0 const 9b 0 const 9b 0 const 9b 0 options(*nopass) const 9b 0 options(*nopass) const 8 options(*nopass) 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

pr

pr

pr

eval dow eval eval if leave

56

C C C

endif eval wrtn = rollup (lines : 1 : 24 : 0 : 0 : err) enddo

C eval *inlr = *on Remember to bind this to serviceprogram QSNAPI You're clearly bringing up an interesting issue. Not being a C expert, I can't say much one way or another except that I've used numerous APIs with no noticeable problem. I've yet to run into an API that I can't call successfuly in ILE RPG in fact. (Unfortunately, I have yet to get into DSM.) The IFS APIs in 'OS/400 UNIX-Type APIs' are examples where prototyping for the VALUE keyword seems to work just fine. By just looking at the documentation, I don't see any significant difference between the ways the DSM APIs and the IFS APIs are defined. So, my first question is: How can you tell by looking at the documentation that the DSM APIs will react to parameter definitions differently than the IFS APIs, for example? Are IFS APIs *not* expecting parameters to be passed 'by value'? Unless a program (including an API) is expecting to receive a parameter passed by value, this will not work. C library functions receive parameters by value. However, most other APIs receive parameters by reference. Maybe you're referring to passing by read-only reference (i. e., specifying the Const keyword in an RPG IV prototype). Mike Cravitz NEWS/400 Technical Editor I have created ILE RPG prototypes for the APIs that I use. When you create the prototype you can specify that the parameter is passed by value, then just use the CALLP opcode. Works quite well. I will give you an example. The Dynamic Screen Manager APIs tend to have their parameter lists set up to pass by value rather than by reference. Here (off the top of my head) is one specific example. The API QsnCrtCmdBuf is passed 5 parameters as follows, p1 Initial buffer size int or 10I in rpg p2 increment size int ditto p3 max size int ditto p4 address of buffer handle long * p5 error data structure char * this differs say from the dataqueue send APIs which expect all of their parameters to be pointers. The above API, because pointers are 16 bytes long on the AS/400, decides to pass the variable contents instead, presumably for performance reasons. Now If you read the AS/400 ILE RPG manuals carefully you spot a pattern. The PARM statement explicitly states that parameters are passed by reference. Hence you can never receive a parameter into a data structure because it is already allocated in storage elsewhere. Using procedure calls you may be forgiven for thinking that parameters are passed differently, but this is not the case. Parameters can be passed by reference, value and as const. Pass by reference is exactly the same as using parm, and the parameters must match exact size ad type for obvious reasons. Pass by value allows you to use different field sizes and types. This is because the parameter is copied into the storage of the procedure. Pass as const is an interesting one. It is supposed to represent a read only variable, but if you read the manuals carefully you see that you have to make sure the procedure does not change the variable. Why? well obviously because it is really being passed by reference, and changing it would change the original storage, even if it is a constant !! So this leaves me believing that it is not possible to call a lot of the system APIs, because as I said earlier some of them expect the variable contents to be passed on the stack (if the AS/400 has a concept of a stack) instead of the reference to the variable. Another give-away is the fact that functions like QsnCrtCmdBuf returns a result field as well, and there is no support for this in RPG without procedures. Have you written much in C ? If you have then the above can be explained more simply by showing that variables are always passed by address e.g.. callproc(&parm1, &parm2, &parm3); . . . callproc(int *parm1, char *parm2, packed(7,2) *parm3) { . . If you have any examples of code you have written to such APIs as these, I would love to see how you coded them.

57

I guess I don't quiet understand your problem. I have used MANY system APIs with RPG with no problem whatsoever. Do you have any examples? CVTDAT command to convert an *MDY to a *LONGJUL Question: I am trying to use the CVTDAT command on V3R2 to convert an *MDY to a *LONGJUL. I keep getting the message that *LONGJUL is not supported. I've recently loaded PTF SF36257 but that doesn't fix it. Any ideas? Answer(s): Thanks for your help. Upon further inspection I found the Member Type was CLP38. When I changed it to CLP it amazingly worked! It's always the simple things, isn't it? Thanks again. I ran the following little test on my V3R2 system sucessfully. Everything ran fine. Used the debugger and it confirms the field &ToDate contains the value '1998/001' after the program invokes the CvtDat command. *************** Beginning of data *************************** Pgm Dcl Dcl CvtDat &FromDate *Char 06 '010198' &ToDate *Char 08 Date( &FromDate ) + ToVar( &ToDate ) + FromFmt( *MDY )+ ToFmt( *LongJul )

EndPgm ************************************************************* I never applied any special PTFs. I am at cume level 96317 (fairly old). CPYSPLF Automation Question: I had the same question that is posted here many times, about converting a spooled file to a PC file. I can do this manually thanks to the CPYSPLF command and FTP. However, now I want to automate the process, but the piece I'm missing is how to obtain the spool file information in a CLP program to automatically run the CPYSPLF command. There is a lot alluding to this process, but no examples of how to code it. Particularly, I need to be able to get the SPOOL FILE NUMBER, because, for the most part, the JOB and USER name are always constant. Thanks in advance. Answer(s): I use both *dtaqs and the APIs, depending on what I need. No weird results with the APIs except when I have more spoolfiles than they can handle. (We routinely have 100,000 or more spoolfiles in our biggest system.) So, I'm now digging into the various 'Open List' APIs to see how they can help. I just wrote a program that does pretty much the same thing. It constantly monitors an outq. When a new file pops up, it moves the file to a new outq. It is a never ending C/L program that sleeps once in awhile then calls an RPG program to do the dirty work. The heart of it is in the RPG program. Using QCMDEXC it runs WRKOUTQ OUTPUT(*PRINT), then copies that spool file to a data base file. Then it deletes the WRKOUTQ spool file. The data base file now has more than enough information in it (including job number) to make decisions and run further commands against the spool files on the outq being monitored. I am running CHGSPLF but CPYSPLF will work just fine. If this meets your needs and you need further information let me know. John P.S. I tried using API's to list the spool file into a user space and read the user space but I was getting weird results. I was running out of time (and getting pissed) so I did it the old fashioned way. There are two problems with the *dtaq method that irritate me. First, the *dtaq entry is sent when a spoolfile goes to *RDY status; *HOLD or any other status doesn't do it. And second, a *dtaq entry is sent *every* time a spoolfile goes to *RDY status; place the spoolfile on *HOLD, then release it and you get another *dtaq entry. That is, the *dtaq entry is not sent when a spoolfile arrives on the *outq; the entry is sent "when a spoolfile goes to *RDY status". Do you have

58

a way around either of those issues? The best way to retrieve spool file info is to use a data queue attached to the output queue. When a spool entry goes to ready in the output queue, an entry is written to the data queue with all the spool file's info. You can then receive the data queue entry and substring the field entry into info needed for CPYSPLF. The layout of the data queue entry can be found in the "IBM Printer Device Programming Guide". I have done this many times and works great. This can be pretty easy assuming a couple of things: (1) The copy take place withing the same job stream (2) There is only one spool file or the one you are coping is the last one. #2 can be worked around if the statement is not true. Use the SPLNBR parameter of the CPYSPLF command. SPLNBR can be *ONLY (the only spool file for the job), or *LAST (the last spool file for the job), or the actual spool file number. Yes, there are APIs to retrieve spooled file information, but maybe you'd like the use of a data queue connected with the output queue(s). You then receive an entry in the dtaq every time a spooled file gets the status READY. In the entry, you find all infos to access the spooled file, including spooled file number. This is the best performing method; you do also use a minimum of system ressources, when you wait "for ever" for a data queue entry. Two possible APIs, depending on exactly how much you know at the time the function runs and exactly what you need to know. Both are in the Print APIs section of the API documentation. The first is the List Spooled Files (QUSLSPL) API and the second is Retrieve Spooled File Attributes (QUSRSPLA). The first will return a list of spooled files depending on what you ask for, e.g., all spooled files for current user, all spooled files on a particular outq, etc. And the second returns the attributes for a specific spooled file. If you know you want the *LAST splf named QSYSPRT for the current job, then all you need is the second API. Lots of combinations possible. the more easy way if you can is to include CPYSPLF just after print program and use *last (or *only if it is) as number. Subfile Window background problem Question: IBMers Please help ... window background I know it as been discussed more than often! But I still dont know the right solution! Although Ive tried almost anything! Why do I get an OLD or totally EMPTY background for my windows the second time I display them? I prefer to end my RPGLE program with RETURN. There for Ive tried to open my displayfile USER CONTROLLED ... no solution when calling the same window from more than one processed subfile record of the main program! Ive expirmented with the USRRSTDSP keyword, no use ... Could somebody please help my out ... Thanks Ron =================================== See here my window example: PS The dummy record "WINDOW" is not really used within my program. PS The field *RTNUMM and *VTRVLG are numeric 6,0 and 3,0. A***************************************************************** A* Create DisplayFile Keywords: A* CRTDSPF RSTDSP(*YES) A***************************************************************** A DSPSIZ(24 80 *DS3) A REF(*LIBL/@FRF FRFR) A PRINT(*LIBL/QSYSPRT) A ALTHELP(CA01) A HELP A HLPSCHIDX(*LIBL/@IDX) ***************************************************************** * DUMMY WINDOW RECORD TBV ASSUME ***************************************************************** A R WINDOW A ASSUME A #PGMID 10 1 2 A***************************************************************** A* SELECT Display X1 A***************************************************************** A R SLCTX1 A WINDOW(MSGCTLX) A CF04 A CF06

59

A CF12 A CF18 A CF23 A CF24 A VLDCMDKEY(55 'Valid command key') A CSRLOC(#ROW #COL) A RTNCSRLOC(&#CSRRCD &#CSRFLD &#CSRPOA S) A CHANGE(40 'SLCTX1 Changed') A OVERLAY A #ROW 3S 0H A #COL 3S 0H A #CSRRCD 10A H A #CSRFLD 10A H A #CSRPOS 4S 0H A $1RTNUMM R O 5 32REFFLD(@@RTNUMM) A $1VTRVLG R B 6 35REFFLD(@@VTRVLG) A 80 DSPATR(PC) A 80 DSPATR(RI) A 69 DSPATR(ND) DSPATR(PR) A $2VTRVLG R B 6 52REFFLD(@@VTRVLG) A 81 DSPATR(PC) A 81 DSPATR(RI) A 69 DSPATR(ND) DSPATR(PR) A $2RTNUMM R B 8 32REFFLD(@@RTNUMM) A 82 DSPATR(PC) A 82 DSPATR(RI) A $3VTRVLG R B 9 35REFFLD(@@VTRVLG) A 83 DSPATR(PC) A 83 DSPATR(RI) A***************************************************************** A* MESSAGE Subfile Record X A***************************************************************** A R MSGSFLX SFL A SFLMSGRCD(12) A #MSGK SFLMSGKEY A #PGMQ SFLPGMQ(10) A***************************************************************** A* MESSAGE Subfile Control X A***************************************************************** A R MSGCTLX SFLCTL(MSGSFLX) A OVERLAY A 44 SFLDSP A 44 SFLDSPCTL A 44 SFLINZ A 44 SFLEND A SFLSIZ(0002) A SFLPAG(0001) A USRRSTDSP A WINDOW(*DFT 12 55 *NOMSGLIN) A WDWBORDER((*COLOR BLU) (*CHAR '...:A ::.:')) A #PGMQ SFLPGMQ(10) A*****************************************************************

Answer(s): >There for Ive tried to open my displayfile USER CONTROLLED ... This work !! always remember, you have to

60

open the Display file as you entre the program and close it before the RETRN. I've done this several time: works fine. Btw, if someone know a better way... or. if IBM is thinking about fixing this hole... There are other implications of changing RSTDSP to *NO that may present some problems to you. If you have any attention key programs that open display files, or message break handling programs that open display files, then you *MUST* use RSTDSP(*YES) otherwise your display files will not be properly restored when the attention or break handling programs finish. The only solution I have found when RSTDSP(*YES) is specified, is to have the window display file opened at program start and closed at program end. Setting LR on when your program finishes, automatically closes any files for you. If you simply want to RETRN then you must explicitly close the window display file. BTW, if your window display file as the KEEP keyword specified, then this prevents the display file from being fully closed, so take it out if it is there. It's in the RSTDSP(*YES) parameter. When the window is first displayed, the background over which it is displayed is saved. The next time it is displayed, the saved background is restored. You can try it out with CHGDSPF RSTDSP(*YES|*NO). Using IFS APIs w/ILE RPG Question: Sometime back, someone posted the correct way to write data to an IFS file using ILE RPG and the IFS write() API. I didn't bother looking at it because I use the QDCXLATE program to translate from EBCDIC to ASCII before writing. Now, I want to read from an IFS file (using read()) and then write a different file back out. Most IFS files that I read work fine but I've run into one from an outside vendor that doesn't work. The file is in ASCII, but when I write it back out, it gets translated back to EBCDIC. I verified this by converting the data using QDCXLATE before writing back out. Does anyone have the correct way of doing this? It had something to do with the codepage parameter.

Answer(s): Here is some code for writing and reading files to the IFS system. My understanding was you had to open the file with create first so that the conversion with the code page will work properly. I know the following code works good.
D D D D D D D D D D D D D D D D D RC FileNam FileNamP FileDescr S S S S 10I 0 50A INZ('/qualcomm/test1' * INZ(%ADDR(FileNam)) 10I 0

O_CREAT S 10I 0 INZ(8) O_RDWR S 10I 0 INZ(4) O_TEXTDATA S 10I 0 INZ(16777216) O_CODEPAGE S 10I 0 INZ(8388608) Oflag S 10I 0 INZ(0) Omode S 10U 0 INZ(511) cp S 10U 0 INZ(819) ZeroBin NLZero SI_Fmt SI_FmtP SI_Msg SI_MsgP S S S S S S DS 4A INZ(X'00000000') 10I 0 OVERLAY(Num_Hex) S S S 100A * INZ(%ADDR(Buf)) 10U 0 1A 2A 50A * 50A * INZ(*ALLX'00') INZ(X'1500') INZ('\n') INZ(%ADDR(SI_Fmt)) INZ(%ADDR(SI_Msg))

D Num_DS D Num_Hex D Num D Buf D BufP D BufLen

61

D/copy IFSHEAD 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 X'25' EVAL Z-add Add Add EVAL FileNam = %TRIM(FileNam) + ZeroBin O_CREAT Oflag O_RDWR Oflag O_CODEPAGE Oflag FileDescr=open(FileNamP:Oflag:Omode:cp)

IF FileDescr = -1 EVAL RC = perror(FileNamP) Return ENDIF EVAL RC = close(FileDescr)

IF RC = -1 EVAL RC = perror(FileNamP) Return ENDIF Z-Add Add EVAL O_RDWR Oflag O_TEXTDATA Oflag FileDescr=open(FileNamP:Oflag)

IF FileDescr = -1 EVAL RC = perror(FileNamP) Return ENDIF EVAL Buf='This is a Test number 1' + X'25' SCAN Buf BufLen 30 EVAL RC = write(FileDescr: BufP: BufLen) IF RC = -1 EVAL RC = perror(FileNamP) Return ENDIF RC = close(FileDescr)

* Close the File C EVAL C C C C

IF FileDescr = -1 EVAL RC = perror(FileNamP) Return ENDIF

The following is the IFSHEAD file that I copy in.. There are defined routines here that I use in other programs .. I just keep them all together as I create new headers for C functions.. Dperror Dconst Dsprintf D D D D PR PR * * 10I * 10I 0 EXTPROC('perror') * VALUE 10I 0 EXTPROC('sprintf') VALUE VALUE 0 VALUE OPTIONS(*NOPASS) VALUE OPTIONS(*NOPASS)

62

* Open Operations * value returned = file descriptor 0 (OK), -1 (Error) Dopen D D D D PR 10I 0 EXTPROC('open') * VALUE 10I 0 VALUE 10U 0 VALUE OPTIONS(*NOPASS) 10U 0 VALUE OPTIONS(*NOPASS)

* Read Operations * value returned = number of bytes read or , -1 (Error) Dread D D D PR 10I 0 EXTPROC('read') 10I 0 VALUE * Value 10U 0 VALUE

* Write Operations * value returned = number of bytes Written or , -1 (Error) Dwrite D D D PR 10I 0 EXTPROC('write') 10I 0 VALUE * VALUE 10U 0 VALUE

* Close Operations * value returned = 0 (OK) or , -1 (Error) Dclose D PR 10I 0 EXTPROC('close') 10I 0 VALUE

* Open Directory Operation * value returned = file descriptor 0 (OK), -1 (Error) Dopendir D PR * EXTPROC('opendir') * VALUE

* Read Directory Operation * Dreaddir D PR * EXTPROC('readdir') * VALUE

* Open Directory Operation * value returned = 0 (OK) or , -1 (Error) Dclosedir D PR 10I 0 EXTPROC('closedir') * VALUE

* Unlink a File from system... Delete File * value returned = 0 (OK) or , -1 (Error) Dunlink PR 10I 0 EXTPROC('unlink') D * VALUE I had to do some research to make it work. After a few hours I found UNISTD.H in QSYSINC which tells me I have to setup an environment variable of QIBM_USE_DESCRIPTOR_STDIO and make it's value "Y" to get the reserved file descriptors. I also had to dig to figure out that I have to bind to service program QC2LE in QSYS to take advantage of SPRINTF and PERROR. Packed or unpacked fields?

63

Question: Packed or unpacked ? We are in the process of deciding whether we should pack or not our fields in our files, and we are wondering the following : a) what are the advantages of unpacked vs packed b) it is deemed that unpacked is more performant, but "how more" (if true) ? c) are there any limitations with unpacked fields as far as coding (RPG) is concerned ? Any answer ? Thank you Answer(s): In general, packed is faster than unpacked. But the difference would be marginal in most cases. Packed: Less space (but what's a few bytes on todays disks) Unpacked: Better legibility with DSPPFM (but SQL and Query have no problems with packed) >b) it is deemed that unpacked is more performant, but "how more" (if >true) ? As I understand it arithmetic is done with packed fields, so unpacked fields will have to be converted. But the difference will probably only be notable when you do lots of arithmetic and hardly any screen or disk IO. >c) are there any limitations with unpacked fields as far as coding (RPG) >is concerned ? None that I know of. >Any answer ? >Thank you So you picks your choice and pays your money. Not too much difference, really. Packed is supposed to be much more efficient that signed (probably because of doing BCD arithmetic). One disadvantage I can think of is exemplified by what passed through this news group some weeks ago: FTP-ing data cannot translate packed into something readable by other systems (eg, ASCII). Within RPG programs there is no difference between packed variables and signed. One other thing to look out for is to try to make all your packed field lengths an odd number, like 9,2 or 11,4 etc. The extra MI instructions to pad evenly sized fields is supposed to be substantial. >fields in our files, and we are wondering the following : >a) what are the advantages of unpacked vs packed Packed decimal fields take about half the space to store as zoned decimal. (unpacked) Unsigned Zoned fields are more easily transferred with FTP. (Although you lose the decimal) >b) it is deemed that unpacked is more performant, but "how more" (if >true) ? I doubt there is a performance difference between the two representations. If there is, it is not likely to be significant. >c) are there any limitations with unpacked fields as far as coding (RPG) >is concerned ? RPG handles either type easily and does conversions as needed. QRYDFN to source and back Question: I know I can convert a qrydfn object into a source file but how do I take that source file and recreate the qrydfn. Answer(s): Ah, yes. It starts coming fully back to me now. How quickly we forget the good ol' days after something better comes along. Sure, but then it would not be a QRYDFN object ;-) The command for that would be CRTQRYAPP which creates a *PGM object; the interactive design method being DSNQRYAPP . Remember that RTVQRYSRC is a S/38 command with a corresponding CRT. But I can't recall what the CRT command was. Maybe Jack's using RTVQRYSRC or running on a S/38. The conversion I assume you refer to is RTVQMQRY . From a retrieved source for QM query, you can only create a *QMQRY object ; there is no CRTQRYDFN command to create a query definition from source. What does the "optimize" parm do? Question: really would like to know what exactly does the 'Optimize' parameter of the Generation Options on the crtpgm or chgpgm command does optimize. I remember that 4 or 5 years ago I once experimented with it, putting the pgm parameters of our most used programs in production on Optimize, and as a result our machines went crazy and lazy... Not much of an optimisation was done. Probably there are many 'optimisations' performed on the parameter, so , wath would it do now, either on a OPM or on a ILE pgm . Our machines are on V3R7 and V3R6 (will be upgraded to 7). Answer(s): "Optimize" does a lot, but it can only speed up program execution, not disk I/O, etc, so there may or may not be a significant improvement if you use it. While it is theoretically possible for "optimization" to make performance worse, in practice this doesn't happen to any extent. (There are some programs that are "hard to optimize", and these may end

64

up being 1-2% worse, but they are rare.) When optimization works well (best on large programs without a lot of calls), the improvement can be 4:1 or better (though this is also rare). Typical results would be 30% to 50% reduction in CPU time. Determining the calling program Question: Hello out there I have in my programming on the AS/400 reached a point of deadlock.... I have at programme in which I have to know the name of the calling progamme (the progam that called the current progam)... This sounds like a very easy task, but I can't use LDA or parameters... If any of you out there can help me PLEASE do!!!! Answer(s): You can examine the Invocation stack of your job by using one of the Retrieve job API's or the DSPJOB command. The following program returns in the &CALLER parameter, the program that invoked the program named in the &PGM parameter. Example of use in your own program: /* &MYNAME will contain the name of the program currently running */ CALL CALLER (&MYNAME '*') /* &MYCALLER contains the name of the caller of the current program */ CALL CALLER (&MYCALLER &MYNAME) PGM DCL DCL DCL DCL PARM(&CALLER &PGM) VAR(&CALLER) TYPE(*CHAR) LEN(10) VAR(&PGM) TYPE(*CHAR) LEN(10) VAR(&MSGKEY) TYPE(*CHAR) LEN(4) VAR(&SENDER) TYPE(*CHAR) LEN(80)

MONMSG MSGID(CPF0000) EXEC(GOTO CMDLBL(ERROR)) CHGVAR VAR(&CALLER) VALUE(' ') SNDPGMMSG MSG('Who called me?') TOPGMQ(*PRV (&PGM)) + KEYVAR(&MSGKEY) RCVMSG PGMQ(*PRV (&PGM)) MSGTYPE(*INFO) + MSGKEY(&MSGKEY) RMV(*YES) SENDER(&SENDER) CHGVAR VAR(&CALLER) VALUE(%SST(&SENDER 56 10)) GOTO CMDLBL(ENDPGM) ERROR: CHGVAR VAR(&CALLER) VALUE(' ') ENDPGM: ENDPGM Sockets in RPG? Question: Can anybody help me or say that it is posible to call a socket API from ILE RPG please I worked out most of it but i always get a return value -1 calling socket() = unsuccesfull a little working example would be nice i know i should use C400 but whe don't have it :( please help me out i'm really getting down here Answer(s): Since V3R2, ILE-RPG supports function prototyping and the use of pointers which made it possible to use/call almost any function written in any language. Since to these set of functions, which required languages like C in the past, also belong the Sockets functions no C/400 is required anymore. The following examples should make it clear how to use the prototyping, and how to call the socket functions; DOpenSock D prSockFam D prAddrTyp D prProtocol DSock_Descr PR 10I 0 ExtProc('socket') 10I 0 Value 10I 0 Value 10I 0 Value 10I 0

65

DAddr_Fam DAddr_Type DProtocol C C C

S S S Eval

10I 0 10I 0 10I 0 Sock_Descr = OpenSock(Addr_Fam: Addr_Type: Protocol)

The other prototypes (which are in C, but can be translated to ILE-RPG) can be found in the Sockets Programming manual. Remember to use 5U, 5I, 10U, 10I as replacement for unsigned/signed, short/long integers, or an * for pointers. I hope this gets you started... and BTW, translation from ASCII/EBCDIC can be done with QDCXLATE (however iconv() works as well, but is a bit more complex to use). Please, Please don't pull our legs...Please say its so ....I want to know too... I've read the same manuals and they say we need C/400. If you could give us a short source sample. The necessary information (function prototypes) can be found in the manual "OS/400 Sockets programming". Recently I programmed several Sockets applications from and to AS/400 and PC's using Delphi without any problem. All this is done from ILE-RPG, and there's really no need to use C. Sorting a user space 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! 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.

66

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) <--the default value PGMC then tries to OPEN file FUNNY. The original override will be the one in effect. If the second OVRDBF also specified SECURE(*YES) then the second override would be in effect. If an ILE program issues an OVRDBF command and the OVRSCOPE parameter is left at *ACTGRPDFN, then the scenario you describe will be true. If you specify OVRSCOPE(*CALLLVL), then it will work the "old" way. We were severely burned by this when we converted everything to ILE. I had a tech tip published in News/400 about this a year ago or so. We just went through all of our CL and specified the OVRSCOPE(*CALLLVL) to make the application work as before. Back to the SECURE issue, one interesting situtation I bet that comes up, is that after PGMC goes out of scope, the OVRDBF command that it issued would still be in effect, negating the original OVRDBF. If *CALLLVL were used, then the original OVRDBF would come back into the picture. 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 may 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 timestamp". 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 operatorless. 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 that ???? 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...

67

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 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 steps 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 record 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. RPG Differences: V2R3 to V3R2 upgrade Question: Can anyone tell me the best place to find out about the differences in RPG that we will find when OS400 is upgraded from V2r3 to V2r3? We developers will have little involvement in the upgrade process but will need to know how it is going to affect us. I understand that we should be able to use RPG as we do currently but that there could be better ways to use it when we upgrade. Also, will we automatically get RPG IV and ILE? (Not that I really know what these are yet) Also, is there anything in particular we should test for after the upgrade to confirm that our applications will still work and that we can continue to develop those applications? I believe that the upgrades will go ahead on both our development and production machines at the same time - ie there will be no lengthy testing. Does anybody think this is risky? Answer(s): >We developers will have little involvement in the upgrade process but will >need to know how it is going to affect us. Not, if you don't want it to, but you will have more possibilities. >I understand that we should be able to use RPG as we do currently but that >there could be better ways to use it when we upgrade. True. >Also, will we automatically get RPG IV and ILE? (Not that I really know >what these are yet) ILE: You already have it, but can't use it with RPG. You will be able to use it with RPG IV (or ILE RPG as IBM likes to call it). There are lots of advantages and lots of disadvantages and it will take time to get accustomed to it. RPG IV: You get it. Lots of improvements over RPG III. >Also, is there anything in particular we should test for after the upgrade >to confirm that our applications will still work and that we can continue >to develop those applications? Nope, everything will work. >I believe that the upgrades will go ahead on both our development and >production machines at the same time - ie there will be no lengthy testing. >Does anybody think this is risky? > We developers will have little involvement in the upgrade process but will > need to know how it is going to affect us. > I understand that we should be able to use RPG as we do currently but that > there could be better ways to use it when we upgrade.

68

Yes - there is a better way. Use RPG/IV. To use ILE means to learn more about static linkage and so on, but to use RPG/IV you will only have to learn abut the OP-codes, new built-in functions and the new source format. We have mae it in about 3 month without any additional help. The next step is to use procedures instead of sub-routines. This is easy to. In procs you have the advantage of local variables. In the third step, you will have to learn about static linkage, service programs and so on. But I think that the speed advantage that static linkage will give you isn't worth it. > Also, will we automatically get RPG IV and ILE? (Not that I really know > what these are yet) You will get ILE-RPG as a licensed program from IBM. If you have licensed RPG/400, you will now get ILE-RPG and RPG/400 automatically. > Also, is there anything in particular we should test for after the upgrade > to confirm that our applications will still work and that we can continue > to develop those applications? We have made the upgrade a time ago and everything went OK. > I believe that the upgrades will go ahead on both our development and > production machines at the same time - ie there will be no lengthy testing. > Does anybody think this is risky? If you have two machines, make the upgrade on you developent machine first and then give it a week to prove it. The upgrade isn't risky, but if you have the chance to test it, use this chance. Getting the relative record number (RRN) Question: Does anyone know how to get the relative record number? Answer(s): Which RRN, the First on the Display? FDISPLAY CF E WORKSTN F RRN KSFILE SFLRCD F KINFDS ###DSP I###DSP DS I *STATUS DSPSTS I B 378 3790@SFLRN @SFLRN will have the RRN for the first record on the subfile that is displayed, that is the number of the record that is first on the current subfile 'Page'. If you want the RRN of the last record read with a READC, that's RRN. IFDS. An answer as short and indescriptive as the question. Here is a part of the I/O-feedback area for RPG-IV. There should be no problems to translate it to RPG-III. FFILE00 UF F 4096 DISK USROPN F INFSR(*PSSR) F INFDS(File#00) F COMMIT(CommitLVL) * DFile#00 DS D F0Status 11 15S 0 D F0File 83 92A D F0Library 93 102A D F0Member 129 138A D F0NbrOfKey 387 388B 0 D F0RcdNbr 397 400B 0 Look for "IO feedback" subject in whatever you are trying to work with... I's all there. What is the longest parameter usable in RPG? Question: Does anybody know of any limitation to the length of a character parm to an RPG program, for example... *ENTRY PLIST PARM PARM1 70 PARM PARM2 6 PARM1 is 70 character string, PARM2 is 6 character string. Can I do this? Or is there any limitation to the length of a parm? Like.... 33 characters? Answer(s): Your final statement "Like 33?" is telling. Can you supply an exact example of how the parameter is created and used on the CALL statement? If the CALL happens at a command line or the parameter is a CL literal value or any of a number of possibilities, you'll run into possible problems. By default, every *CHAR parameter passed from CL has a

69

minimum 32 character length. Up to 32 is always padded with blanks even if you only pass a single character. Over 32, the parameter has a default declared length of whatever the length of the literal is. If you issue the following: ===> call pgm(ABC) parm('12345678901234567890') and receive it into a character variable declared as longer than 32 positions, there will be no padding at the rightmost end. Whatever was in memory after position 32 becomes part of the value of the received argument. Positions 21 through 32 should be blank, but the rest? Who knows? (Actually, you CAN make some predictions, but that's irrelevant.) If you supply some precise examples of the CALL, corrections can be given. If you need more you can use arrays (up to 9999 times 256 bytes) or data structures (up to 32K ?) Depends on how the program is called. There is no problem if it is called from another program using variables for the parameters (using the correct definitions, of course). The padding happens when it is called using literals, mostly when used from the command line. Do you have a specific example of what you are describing? What you are describing has never happened to me or to anyone else I know. And also runs contrary to any discussion of parameter passing I have seen in any of the HLL manuals. I used to work on a project were every I/O was handled by a specific program. So in order to read/write something, a program needed to call this routine with the record buffer (was always bigger than 32 bytes). In case it would give garbage as you stated, this project shouldn't have worked... however it did ! This is not entirely accurate, either. Calling a program with a character parameter (in a CL lets say) greater that 32 character could very likely end up with garbage in the trailing character, even in the parameter on the receiving program is declared the same size, type, etc. It's happened to me, and other, many times before. This is not entirely accurate, the 32 character limitation, only applies when you use the CALL command on a command line to pass parameters to a program. When a HLL program calls another program, a pointer to the string is passed to the subprogram. If the string declared in the main program is declared shorter than the string in the sub-program, then the extra characters in the sub-program may actually overrun some other storage from the main program. Any modifcation to the string by the sub-program could yield "unpredicatable results". On the other hand, the sub-program declares the string as shorter than what is passed, the subprogram will not have any problems but it won't be able to address all the positions in the string. While strings may be limited to 256 in RPG/400, Data structures are not and can be passed as a parameter. The main thing to watch out for with long character fields is that right-padding with spaces is not done consistently if the field is over 32 in length. If your content does not fill the field, garbage characters may end up padding the field on the right. In your example, the contents of PARM2 will probably end up in PARM1 following the last non-blank character. If you need a field with a length greater than 32, you must ensure that all characters are filled in. I usually handle this by making the field one character longer than needed, and place some character such as "." in the final character. PARM1 at 70 is more than acceptable. I constantly use 100 byte parms. The limitation is the max length of RPG, of course it depends on your version that you are using. RPG/400 max length is 256 for character fields. We are running V3R2 A C function that returns a string to an RPG pgm Question: I'm writing a C function that to retuns a string to an RPG program. The format looks like this. char *Func(parm) When the data gets back to my RPG program the string is all NULLs. I can pass the string in the arguments like this int Func(*Stringparm, parm) But I need the string returned not passed as an argument. Any suggestions? I'm stumped. Doesn't take much to stump me though since I am C novice Answer(s): he way that is typically done in C is to pass the string pointer as an argument and as the return value: char* foo(char* bar) { strcpy(bar, "a string"); return bar; } C isn't like PASCAL in which the run-time will allocate a specified as a return value -- you have to do the work yourself. This, by the way, was also confusing to me when I first began learning C after writing a lot of Pascal programs. Hope this helps Problems with ZADD *ZEROS Question: It seems to me that there is a wrong value in a unsigned integer field after executing a Z-ADD *ZEROS into that field!

70

After the Z-ADD *ZEROS there is a value of 15 in the field. Z-ADD 0 is working fine!!! Please have a look at the following test program: * D Unsigned * D Msg1 D Text1 D Value1 * D Msg2 D Text2 D Value2 * C C Msg1 * C C C Msg2 * C * S DS 15A 10S 0 DS 15A 10S 0 Z-ADD Unsigned DSPLY Z-ADD *ZEROS Z-ADD Unsigned DSPLY MOVEL (P) '1' INZ('New unsigned = ') INZ(*ZEROS) Value1 Unsigned Value2 *INLR INZ('Old unsigned = ') INZ(*ZEROS) 10U 0 INZ(*ZEROS)

Answer(s): Nice for the V3R7 users, but this is still happening in V4R1. After I had reported the problem to IBM they told me that there are some PTFs available for V3R7 that fix the problem. The PTFs are no available in a cumulative PTF yet. The numbers of the PTFs are: SF45417 and SF46327. Can you highlight code in SEU? 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 edit 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

71

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 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. <vogt@softship.de, via email, 1/2000> Reusing deleted records - OK? Question: Anyone have experience with reuse deleted records ? I understand the concept and how to setup, but I've heard horror stories about databases with it turned on ? We have multiple(4-5) databases with over 90 million records with 8-10 logicals over each one. We are also using a product called MIMIX that provides database mirroring at an application level between to 530's soon to be 640's. What does this do to my performance over time ??? Any thoughts ???? Is reuse deleted records a stable option for databases that large ? Answer(s): if you are using any logicals with LIFO sequencing then you cant use reuse as the LIFO depends upon the Relative record number to work...probably a good idea to check this first. I have seen a case were reuse caused problems. A batch program received records from a bisysc line and wrote records to several data files. It would periodically submit other batch jobs to process it's output files. These programs would then process the records and delete them. This created large numbers of deleted records so we changed these files to reuese deleted records. Duplicate records started appearing. We never determined whether duplicates were being wrtten or deletes were failing. We never saw any triplicates or more and duplicates were very rare (one an hour with several thousand an hour being processed.) Changed the files back and the problem dissapeared. This was 4 years ago on a B60 V2 somthing. I have see reuse used extensively on other machines with no problems. When I used it at a client site I didn't think through the ramifications and had problems for a few days until it came to light. The program reads an unkeyed physical file from start to end (by RRN), then deletes all records for the next cycle. Problem turned

72

out to be that records that were loaded in a certain sequence fell out of sequence since the indexes (RRN) went all over the place, so think it out first. BTW, performance did improve for those couple of days. Ever try to do a DSPPFM on a physical whose first record (because of deletes) was in the hundreds of thousands? If not, you have to wait sometimes minutes for the screen to come up. We have used reusedlt since it was introduced on the System/38 with no problems. One of our clients though some times uses relative record number to remove blocks of records added in error. Reusedlt will not work for that. We have used the REUSEDLT(*YES) parameters ever since it was introduced in V2R2. The Database Guide lists the warnings, but I will reiterate some of the more important ones. You cannot specify re-use delete if the physical file has any access path that specifies anything other than default ordering for duplicate keys. All LF38 files have an implicit default ordering of FIFO. So these would have to be created. You should not use EOFDLY on physical files unless you are reading sequentially by key. Any program that relies on new records being added to the end of the file will not work as expected. When we started REUSEDLT we dedicated a weekend for the access path rebuilds. We have had absolutely no problems with it for 6 years. Not even a performance degradation that anyone has noticed. Make sure with Mimx that they support re-use delete before you start it. In 1992, they did not. So long as you have some non-user time available you should be able to REORG the files periodically. I think this reclaims deleted records. Is there an easy way to change edit codes? Question: I am working with RPG/400 on a V3R1M0 and have following problem: Usually our edit format for numeric fields is comma for decimal seperator (111.111,00) now we have a new customer who wants a point (111,111.00). Do I have to define every output-field twice (al lot of maintenance) or does anybody know something better. Maybe there is a way to control the edit format in the program? Please let me know. Thanks Answer(s): This is a system value that can be found by using WRKSYSVAL. It allows you to change these settings. So, if they are on a different machine, no problem. Help - Windowed Subfiles Question: I am attempting to create a display that when opened is a subfile in a window. That much in itself is working fine. My problem is that when this program is called (currently from a command line, but eventually will be assigned to the Attention key.) The entire contents of the screen is erased before the initial window is displayed. I want the popup window to overlay anything already on the screen. In the DDS, I'm using the WINDOW, OVERLAY, and PROTECT keywords. I wish I had the DDS in front of me to explain what I already have in place better... Can anyone assist me that has done this type of thing before? Answer(s): You may insert the following dummy record instead: * * DER DUMMY SATZ MUSS VORHANDEN SEIN, * AUCH WENN KEIN ZUGRIFF AUF IHN ERFOLGT !! * *----------------------------------------------------* RADD A R DUMMY ASSUME RADD A 11 1' ' Try ASSUME for the keyword. If I remember correctly, you need to define a dummy record format with certain keywords. I'm not sure what they are but can get an example id you still need help One more thing if you intend to use your window program as an attention key handler, you must specify RSTDSP(*YES) on each and every display file that your attention key program may overlay, or as IBM is wont to say "unpredictable results may occur." We changed the command default for CRTDSPF for this very reason. Remember too, that you must explicitly close your

73

window display file or exit your program with LR on. If you don't do this the background screens from the first invocation of the program will be retained behind your window not matter what is on the display when the program is subsequently called. The easiest way is to add another format to the display file that is never used but contains the ASSUME keyword. The reason that your screen is being cleared is that when a display file is first used it always clears the current screen. By using the ASSUME keyword you are telling the program that the display file has already been opened. I hope this answers your question To accomplish what you are asking is relatively easy: create DDS record format DUMMY and specifiy the ASSUME keyword. You do not need to reference this format in your program. It's mere presence in the display file will prevent the screen from being cleared. Another thing: if this program is being called by other programs, make sure you do an explicit open/close of the display file, or exit with LR on All you have to do is create a new record format: R DUMMY ASSUME 23 1 ' ' and the existing panel will not be erased. This is a must for all windows. Strange is it not? I have an example, below, I can share that is using a windowed- subfile. Strip-out the DDS, and create a *DSPF, and strip-out the RPGIV code, and create a *RPGLE. Remember to compile the *DSPF with RSTDSP(*YES). Let me know what you think? --------------------------------------------------------------------------------The DDS Code... A DSPSIZ(24 80 *DS3 A 27 132 *DS4) A PRINT A R SUBF01 SFL A #FLDNAM 10A O 3 1 A #FLDTYP 1A O 3 14 A #DIGITS 4Y 0O 3 37EDTCDE(Z) A #DECIMALS 2S 0O 3 45 A #STRPOS 5Y 0O 3 25EDTCDE(Z) A R CNTR01 SFLCTL(SUBF01) A *DS3 SFLSIZ(0016) A *DS4 SFLSIZ(0016) A *DS3 SFLPAG(0015) A *DS4 SFLPAG(0015) A *DS3 WINDOW(SCRN01) A *DS4 WINDOW(SCRN01) A N75 ROLLUP(25) A CA03(03 'exit') A KEEP A BLINK A CSRLOC(SETROW SETCOL) A OVERLAY A SFLCSRRRN(&GETRRN) A 50 51 SFLDSP A 50 SFLDSPCTL A 52 SFLCLR A 75 SFLEND A @PAGE 4S 0H SFLRCDNBR A SETROW 3S 0H A SETCOL 3S 0H A GETRRN 5S 0H A 1 1'Record Format Layout...' A DSPATR(HI) A SCRFLDDBFL 10A O 1 38DSPATR(HI) A DSPATR(UL) A SCRFLDDBLB 10A O 1 25DSPATR(HI) A DSPATR(UL) A 1 36'/' A DSPATR(HI) A 1 49'(' A DSPATR(HI)

74

A SCRFLDDBRF 10A O 1 51DSPATR(HI) A DSPATR(UL) A 1 62')' A DSPATR(HI) A 2 1' Field ' A DSPATR(UL) A COLOR(PNK) A 2 12'Type' A DSPATR(UL) A COLOR(PNK) A 2 17'Starting Position' A DSPATR(UL) A COLOR(PNK) A 2 35'Length' A DSPATR(UL) A COLOR(PNK) A 2 42'Decimals' A DSPATR(UL) A COLOR(PNK) A R SCRN01 A *DS3 WINDOW(2 2 21 72) A *DS4 WINDOW(2 2 21 72) A OVERLAY A WDWBORDER((*COLOR BLU) (*DSPATR RI)A (*CHAR ' ')) A USRRSTDSP A 18 1' A A A A A cA A A A A A A A A R ASSUME ' DSPATR(HI) ASSUME 24 79' ' ' COLOR(PNK) 20 1'F3-Exit ursor and press ENTER... ' DSPATR(UL) DSPATR(HI) 19 1'To select a field, position your

-----------------------------------------------------------------------The RPGIV Code... * ********************************************** * This application allows the user to display the * Record Format Layout of a File(Passed Parm), * and Returns the FIELD-NAME, and LENGTH... * * ...sgroi ********************************************** * /EJECT *

75

FRCDL600D CF E WORKSTN F SFILE(SUBF01:@RRN) F INFDS(@LOCATE) * D@LOCATE DS D@CURSOR 388 389B 0 D@FIRST 378 379B 0 * D RETURNNAME S 10A D RETURNLEN S 2A D @RRN S 4S 0 INZ(*ZEROS) D @REBLD S 1A INZ(*OFF) D @BAD S 1A INZ(*OFF) D @DONE S 1A INZ(*OFF) D @ERR S 1A INZ(*OFF) D @SKIP S 1A INZ(*OFF) D @MODE S 1A INZ(*BLANKS) D MESSAGEQUE S 10A INZ('*') D MSGDTALEN S 8B 0 INZ(60) D MSGQUENBR S 8B 0 INZ(0) D MESSAGEKEY S 4A D MESSAGEFIL S 20A D MESSAGETYP S 10A INZ('*DIAG') D @RRN# S LIKE(@RRN) INZ(*ZEROS) D @PASS S LIKE(@RRN) INZ(*ZEROS) * DX S 4S 0 INZ(*ZEROS) D @FIELDS S 4S 0 INZ(*ZEROS) DF S 4S 0 INZ(*ZEROS) * D @FLDNAM S 10A DIM(150) INZ(*BLANKS) D @FLDTYP S 1A DIM(150) INZ(*BLANKS) D @FLDLST S 14S 0 DIM(150) INZ(*ZEROS) * D ErrorDs DS INZ D BytesProvd 1 4B 0 D BytesAvail 5 8B 0 D MessageId 9 15 D Reserved 16 16 D MessageDta 17 116 INZ(*BLANKS) * D GENDS DS D OffsetHdr 117 120B 0 D SizeHdr 121 124B 0 D OffsetLst 125 128B 0 D NbrInLst 133 136B 0 D SizeEntry 137 140B 0 * D Input DS D UserSpace 1 20 D SpaceName 1 10 D SpaceLib 11 20 D InpFFilLib 29 48 D InpFilNme 29 38 D InpFilLib 39 48 D InpRcdFmt 49 58 * D HeaderDS DS D OutFilNme 1 10 D OutLibNme 11 20 D OutType 21 25

76

D OutFormat 31 40 D RecordLen 41 44B 0 * D LISTDS DS D SfFld 1 10 D SfType 11 11 D BufferOut 13 16B 0 D FieldLen 21 24B 0 D Digits 25 28B 0 D Decimals 29 32B 0 D FieldDesc 33 82 * D DS D StartPos 1 4B 0 D StartLen 5 8B 0 D SpaceLen 9 12B 0 * D DS D @FLDLST1 1 14S 0 D @STRPOS 1 4S 0 D @FLDLEN 5 8S 0 D @DIGITS 9 12S 0 D @DECIMALS 13 14S 0 * /EJECT * *********************************************** *MAINLINE... *********************************************** * C EVAL *INLR = *ON * C EXSR $GETDBFLDS * C EXSR $POSITION C EXSR $PROCESS * C EXSR $SHUT_DOWN * /EJECT * ************************************************ C $POSITION BEGSR * POSITION SUBFILE RECORDS... ************************************************ * C Z-ADD 1 @PAGE * C CLEAR @RRN * C Z-ADD X @FIELDS * C EVAL *IN50 = *OFF C EVAL *IN51 = *OFF C EVAL *IN52 = *ON * * CLEAR SUBFILE... * C WRITE CNTR01 * C ENDSR

77

* /EJECT * ********************************************* C $PROCESS BEGSR * PROCESS SUBFILE, AND USER FUNCTIONS... ********************************************* * * F3-EXIT... * C DOU *IN03=*ON * C EXSR $FILLSFL * C IF @RRN > *ZEROS C EVAL *IN50 = *ON C EVAL *IN51 = *ON C EVAL *IN52 = *OFF C ELSE C EVAL *IN50 = *ON C EVAL *IN51 = *OFF C EVAL *IN52 = *OFF C ENDIF * C EVAL @RRN# = @RRN * C DOU *IN03=*ON OR *IN25=*ON * C WRITE SCRN01 C EXFMT CNTR01 * C EVAL @REBLD = *OFF * C IF *IN03=*ON C LEAVE C ENDIF * C SELECT * * ROLLUP KEY PRESSED... * C WHEN *IN25 = *ON C EVAL @RRN = @RRN# C EVAL @PAGE = (@PAGE +15) C CLEAR @PASS * * ENTER-PRESSED... * C OTHER * C EXSR $CURSOR * C ENDSL * * REPOSITION/REBUILD SUBFILE WHEN THIS FIELD * C IF @REBLD = *ON C EXSR $POSITION C LEAVE C ENDIF *

* IS ON...

78

C ENDDO C ENDDO * C ENDSR * /EJECT * *********************************************** C $CURSOR BEGSR * CURSOR POSITIONING... *********************************************** * C EVAL @PAGE = @FIRST * C @CURSOR DIV 256 SETROW C MVR SETCOL * C IF GETRRN > *ZEROS C GETRRN CHAIN SUBF01 99 C IF *IN99 = *OFF C MOVE #FLDNAM RETURNNAME C MOVE #DIGITS RETURNLEN C EXSR $SHUT_DOWN C EVAL *INLR = *ON C RETURN C ENDIF C ENDIF * C ENDSR * /EJECT * ********************************************* C $FILLSFL BEGSR * FILL SUBFILE... ********************************************* * C EVAL *IN75 = *OFF C CLEAR F * C 1 DO @FIELDS F * C CLEAR @FLDLST1 C MOVEA @FLDNAM(F) #FLDNAM C MOVEA @FLDTYP(F) #FLDTYP C MOVEA @FLDLST(F) @FLDLST1 C Z-ADD @STRPOS #STRPOS C Z-ADD @DIGITS #DIGITS C Z-ADD @DECIMALS #DECIMALS * C ADD 1 @RRN C ADD 1 @PASS * C WRITE SUBF01 C CLEAR #FLDNAM C CLEAR #FLDTYP C CLEAR #STRPOS C CLEAR #DIGITS C CLEAR #DECIMALS * C IF F = @FIELDS

79

C EVAL *IN75 = *ON C ENDIF * C ENDDO * C ENDSR * /EJECT * ****************************************** C $GETDBFLDS BEGSR ****************************************** * * This is neat!!! * We're using the "QUSFLD" *API to get * the RECORD-FORMAT-LAYOUT for the FILE * that's defined for this app... * ------------------------------------------------------* * First, we're gonna create a USER-SPACE in QTEMP * to store all of the stuff we need... * * Now, we can create it... * C EVAL SpaceName = 'DSUSFD00' C EVAL SpaceLib = 'QTEMP' C MOVE SCRFLDDBFL InpFilNme C MOVE SCRFLDDBLB InpFilLib C EVAL BytesProvd = 116 C CALL 'QUSCRTUS' C PARM UserSpace C PARM *BLANKS SpaceAttr 10 C PARM 4096 SpaceLen C PARM *BLANKS SpaceVal 1 C PARM '*ALL' SpaceAuth 10 C PARM *BLANKS SpaceText 50 C PARM '*YES' SpaceRepl 10 C PARM ErrorDs * C IF BYTESAVAIL <> 0 C EXSR $APIERRORS C ENDIF * * Get the LIST FIELDs data for the FILE name passed... * C CALL 'QUSLFLD' C PARM UserSpace C PARM 'FLDL0100' ListFormat 8 C PARM InpFFilLib C PARM SCRFLDDBRF InpRcdFmt C PARM '1' OverRide 1 * * Now, pull it in from the USER SPACE, with * a RETRIEVE command... * -----------------------------------------* Have to do this a couple of times, since, * everything we need is sittin' in different * parts of the USER-SPACE... * -----------------------------------------* First, let's grab the GENERAL HEADER info from the * and plop-it into the GENDS structure...

*SPACE,

80

* C EVAL StartPos = 1 C EVAL StartLen = 140 C CALL 'QUSRTVUS' C PARM UserSpace C PARM StartPos C PARM StartLen C PARM GenDs * * Then, grab some more, and put it into the HEADER * structure... * C EVAL StartPos = OffsetHdr + 1 C EVAL StartLen = SizeHdr C CALL 'QUSRTVUS' C PARM UserSpace C PARM StartPos C PARM StartLen C PARM HeaderDs * C EVAL SpaceName = 'DSUSFD00' C EVAL SpaceLib = 'QTEMP' * C EVAL StartPos = OffsetLst + 1 C EVAL StartLen = SizeEntry * C CLEAR X C CLEAR @FLDTYP C CLEAR @FLDNAM C CLEAR @FLDLST * * Now that we have some general information about the * fields in the file, we can LOOP thru the next * RETRIEVE, based on the number-of-fields counter, * and grab what we need... * C DO NbrInLst * * Pull field-info and plop-it into the LIST structure... * C CALL 'QUSRTVUS' C PARM UserSpace C PARM StartPos C PARM StartLen C PARM ListDs * * We're gonna dump these values into arrays, because, * we don't want too much I/O processing on FILES... * C EVAL X = (X + 1) C MOVE SFFLD @FLDNAM(X) C MOVE SFTYPE @FLDTYP(X) C CLEAR @FLDLST1 C MOVE BUFFerout @STRPOS C MOVE FIELDLEN @FLDLEN C MOVE DIGITS @DIGITS C MOVE DECIMALS @DECIMALS C IF @DIGITS = *ZEROS C MOVE FIELDLEN @DIGITS C ENDIF C MOVE @FLDLST1 @FLDLST(X)

81

* C EVAL StartPos = StartPos + SizeEntry C ENDDO * C ENDSR * /EJECT * ********************************************** C $SHUT_DOWN BEGSR ********************************************** * C EVAL SpaceName = 'DSUSFD00' C EVAL SpaceLib = 'QTEMP' C EVAL BytesProvd = 116 C CALL 'QUSDLTUS' C PARM UserSpace C PARM ErrorDs * C IF BYTESAVAIL <> 0 C EXSR $APIERRORS C ENDIF * * C ENDSR * /EJECT * ********************************************* C $APIERRORS BEGSR ********************************************** * * Standard API ERROR MESSAGE handling... * * --------------------------------------------------------* C EVAL MESSAGEDTA = (MESSAGEDTA + MESSAGEID) C EVAL MESSAGEFIL = ('QCPFMSG ' + 'QSYS') C EVAL MESSAGEID = 'CPF9898' * C CALL 'QMHSNDPM' PQHSNDPM * C ENDSR * /EJECT * **************************************** C *INZSR BEGSR **************************************** * C *ENTRY PLIST C PARM SCRFLDDBFL C PARM SCRFLDDBLB C PARM SCRFLDDBRF C PARM RETURNNAME C PARM RETURNLEN * C PQHSNDPM PLIST C PARM MESSAGEID

82

C PARM MESSAGEFIL C PARM MESSAGEDTA C PARM MSGDTALEN C PARM MESSAGETYP C PARM MESSAGEQUE C PARM MSGQUENBR C PARM MESSAGEKEY C PARM ERRORDS * * C ENDSR * /EJECT * Sorry, but memory can play tricks on you. A "dummy" format is defined in the display file with the ASSUME keyword. One field is defined in this format with the PUTOVR attribute. This works! If memory serves me correctly, you need to specify OVRDTA or OVRATR on all fields. AS/400 'machine language' - MI Programming Question: Hi as i am often at AS/400 shops that only have an RPG compiler, i'd like to start learning MI to be able to do things i'd otherwise only could do in C/C++. Now i am not afraid of MI, but i need some information to get started. I (nor any of the shops i've ssen so far) dont have the "Machine Interface Functional Reference" which is referred to extensively in the API books. This book is NOT on the Online Reference Library CD's , nor is it on the IBM rochester FTP site (as far as i know). Can anyone tell me where i can obtain the book (in electronic or paper form, any version) or advice other reading? Or does anyone perhaps have a list of operation codes that can help me get started?? tnx very much in advance Answer(s): > Can you tell us more about W-code? W-code is a platform independent intermediate language that is output by most of IBM's more recent compilers on multiple platforms. It is then processed by a platform specific back-end (the bit we refer to as the translator on the AS/400). There are back-ends for Win 95, OS/2, Win 3.1, etc. etc. It is much more of a 'nuts and bolts' assembler type language than the old MI. MI instructions are of the "Move Universe from A to B" type. W-code instructions are more itsy-bitsy. W-code is not a published interface, but it can be obtained by folks who want to write compilers etc. for the platform. In the System API Programming guide chapter 5 contains a very brief introduction to MI programming. Here you also have all the information for a 'do it yourself' MI compiler. However for real programming you need the Functional Reference. The Functional Reference is NOT complete, in the sense that OPCODES reserved for use by IBM (system state programs) are not listed. However the opcodes that are listed are not likely to be changed in the curent millenium. I found that I can sometimes achieve an incredable performance gain by putting SELECTED parts of an application in MI. Especially manipulating Independent Indexes is much faster than using workfiles, of using Indexes with help of the appropriate API, which is incredibly slow. Also MI can be used to retrieve lots of information from a job, or from the system. For instance it can easily be seen how many pagefaults your jobs caused, or how many CPU milliseconds it used. I have used this for performance testing applications that themselves were written in RPG. A very simple MI program can even retrieve information from the system operator panel, like how your key is turned in your machine (if you still have one of those models). QPROCT ("Program Operation Code Table" ??) in QSYS (type x'19DA') contains all valid MI op-codes. There's a lot of the opcodes from the S/38 days that can only be used by system domain programs, and the Create Program API won't recognize them. Database access, create program, etc. is probably considered too "dangerous" (or simply makes it too easy to violate system security or database integrity). Even IBM's compilers doesn't access the DB opcodes directly, but rather through programs QDB*. It's safer to use a HLL like RPG to access files, and use MI for the utlity/system related stuff. > ...... all the compilers generate MI code if they changed MI > a lot, they would need to change all the compilers Just a couple of points. First only the _old_ compilers generate MI. All of current compilers (C, C++, CL, COBOL/400, RPG IV) generate W-code, not MI. Second, the compiler writers are protected from potential changes to MI (or more often to the control blocks etc.) by macros. Even when changes occur, by changing the macro we avoid changes in code. I agree though that MI is not likely to go away any time soon. Jon Paris - Toronto Lab Do you know of

83

any 3rd Party publications that might be useful aside from the IBM book I know of and looking at NEWS/400 articles? Like I said in my previous post, the IBM book (Machine Interface Functional Reference) really gets you nowhere. Well, that struck me. :-) Not that I'm going to dive into MI after that, but it was a nice reading. As some say here : oooole tus huevos! Right, but still, there are things you can do with MI at such a speed you can't do with anything else. We have been doing heavy MI development for years, and the difference is huge. However, we do system tools here, so the investment in manuals, training, and testing pays for itself. Outside the tool development, MI can help you to speed up certain critical functions of your applications... but try other languages first. There is more myth on the "release dependant" issue of MI than reality. Our software run untouched from V1R2 (yeah, I mean Version One Release Two) with the sole exception of two instructions (out of hundreds of thousands lines) that we use for speeding up system internal things, that you are not (definitely) going to use in a business application program (hey, not a surprise here: all the compilers generate MI code - if they changed MI a lot, they would need to change all the compilers - and the beauty of the AS/400 is MI - its machine independant interface). And we achieved subsecond response times in complex Client/Server transactions, for instance, thanks to the MI compiler. But MI *is* complex. You can buy the book (MI Functional Reference) from IBM, but it is not a tutorial. IBM will not help you either. There was a company (Monahon I think) that sold a MI compiler with a handbook, but I don't know if they still are on business (look for them on the NEWS/400 Sourcebook). Good MI examples appeared from time to time on AS/400 magazines as NEWS/400 and MC. You can learn a lot from inspecting the the RPG compiler output in MI. You are going to need a good interactive source debugger as well (the IBM provided debugger will not work). If you need a good one, send me a message, I know a great one. If you, in the end, start a MI programming career, welcome to the club! He *doesn't* have a C compiler at his shops. ;D. That was his question. Anyway, I don't understand why someone wants to write legacy soft *on the AS* in C. Particulary on the AS, whose C compiler is really indecent and whose C++ environment requires aprox 50x (sum soft, CPU & workstations needed) investment as on any other machine. And if it isnt legacy soft, well, why have you to recompile it for every customer? I don't know what you're doing, JimBob19, but I'm sure you have a better solution behind you as rewriting in MI code, which is machine & release dependent. Tell us some more. If you can do it in C then do it in C. You can order the Machine Interface Functional Reference book from your local IBM literature service. I had this book at my previous work place and found it more than lacking. It only describes a few select system mmemonics such as MATMATR (materialize machine attributes) but certainly doesn't give you any information on how to change any settings on your /400 or even something "simple" as to create an object. (Even though they use the CRTS (create space) OP in their sample programs!) There is as certain *DTAARA in the QSYS library which contains the names of all MI mmemonics known to the system MI compiler (It starts with Q ;-) I don't remember the rest though). Just a fraction of these are documented in above mentioned publication. BTW I heard of a 3rd party MI compiler that has aside from the OPS I just mentioned lots of IBM undocumented MI OPS in it. You might want to check for that. For kicks-n-giggles, I wrote an RPG that used most (all the standard) of the OPCODEs to get work done in RPG, then compiled the program with MI output it's on of the options on the compile. I then took a look at the code. And decided to skip it... Too much like JCL for me... 8-) There are other problems too, for instance objects previously available via MI, can no longer be accessed as they're now in the 'System Domain' (I think it's called) and you get object violations if you go near them. contact IBM, I think you can order this book. and as far as I know MI is release-dependent, opcodes may change without notice.! Library lists and performance Question: Is there any performance difference in the following scenario? 1. One library with 1000 objects. 2. 10 libraries with 100 objects. I understand that in (2) if the object were found in a library near the top of the library list the access would be very quick. My question is this, with one big library is there a pointer to the object required or does the OS have to scan all the objects in the library until it finds the one required. If there is a pointer then would it not be faster to access any object in one huge library than to access an object in the second library. The OS would be checking the existence of the object in the first library then the second...until the library list has been scanned. I hope some of you system gurus can help as I am just a lowly application guy Answer(s): Not (right). If not told otherwise (specifying an explicit storage pool for every object), OS/400 itself chooses physicall space for any objetct, usually trying to keep sotrage usage at a level for all disks.

84

Wrong. All data is spread over all disks evenly, to make optimal use of all disk-arms. Also, in a related performance impact: If you seperate the objects into 10 libraries, the as/400 can place them on seperate disk packs, reducing the risk of (entire) loss, and spreading the disk head activity among several physical devices. (right?) :) In each library there is an independent index (similar to the index of a logical file) which the system can use to find 'normal' objects. That means it will take approximately 5 times as long (averagely) to find one object with 10 libraries than with one library, given that they are evenly distributed in the 10 libraries. But a lookup in an index is fast, so you really don't need to bother. I'm afraid that your statement is true if you have qualified the objects ( I mean if you have stated the library/object of the call/open ) if not there is a search. As was stated in a later message that penalty is nowadays not that significant ( I recall having read somewhere in the old S/38 to put the data libraries before the pgms libraries to diminish the search ) The is absolutley no difference between the two. I runs on a pointer system and does not need to search libraries. Once the program is compiled , os400 knows exactly where it is. This is an interesting question, and I'd love to hear what people say. But, I really don't think it's a big performance worry. My guess is this. It searches each library for the object, but uses a good search technique. Kind of like the way we would search through a dictionary for a word. We wouldn't start with the A's and scan until we reach our desired word (in this case let's say "STEAK"). We would go to the Ss and then the STs and so forth. I can't for the life rember the name of the search method. But it's the one where you go to the middle of the list. If the value is equal, your done. If it is greater than the desired value, you go to middle of the top half, and do the same until a value is or is not found. If the value is less than the desired value, you use the bottom half of the list and do the same until a match is or isn't found... Darn mental blocks! Binary search? Someone help me out / How to use ERRSFL Question: Help, I am writing messages to a display file by using the API QMHSNDPM. The messages appear in the message subfile on the display file if the message subfile is defined. I am trying to avoid using the message subfile and use the standard error message subfile created by the file level ERRSFL keyword. Has anybody managed to load this error message subfile via the QMHSNDPM API?

Answer(s): We switched to using this API to send error messages successfully. We decided not to try and fill the subfile, because most users only look at the one being displayed. So, whenever we encounter an error, we loop back to display the screen (and any errors). One thing we had to do was use the overlay keyword on the control record (for a subfile) and the screen format (for just a plain screen) otherwise the error would flash and then dissappear. A simple example of how we use it is this... Cdow(W$KEY <> f3)
CCALL'SNDPM'WPMSG CEXFMTSCR01 CevalWPMSID = 'INV0001' *other code Cenddo the program call to SNDPM displays any error message shown. The WPMSG parameter list includes message id, message data, message file name, and message file library. We created message ID INV0001 as a blank record to clear any messages after they are displayed. Hope this helps.

The ERRSFL can only be used ERRMSG, ERRMSGID, SFLMSG, SFLMSGID, and the display file validity checking keywords. If I were you, I would continue to use the Message subfile and QMHSNDPM. It gives your program far better control over error handling. It is a very telling example that message subfiles are what IBM uses for all its displays. Updating in CL

85

Question: Is there ANY way to update a file in CL? Looked for an API and didn't find one. Any Help would be greatly apreciated. Answer(s): If you have TAATOOLS, there is a cmd called something like UPDFLD that allows this. Record lock wait time Question: Does anyone know how to change the default time that an RPG update program will wait on a record lock? Answer(s): Set the WAITRCD parameter on the file whith CRTPF or CHGPF commands. This is an atribute of the file object, rather than the programmer's choice. use the setting on the CRTPF, or override on the OVRDBF Do an OVRDBF cmd in a CL pgm before calling the RPG or do the OVRDBF cmd in the PRG using QCMDEXC. The parameter is I believe is WAITRCD. From OS/400 CL Reference, V3R2, with respect to CHGJOB DFTWAIT(): "The wait time specified for this parameter is ignored for read operations to database files; to specify that attribute, use the WAITRCD parameter of the appropriate database command (create, change, or override) for a physical, logical, or database file command." I'm not at a AS/400 now, so I can't provide exact details. The process default wait time is used, and I believe that is a job attribute. Check out the CHGJOB command, or the JOB command for a batch job. It might even be a job CLASS attribute. CHGPF or CHGLF? Message subfile problem Question: a message subfile(first time I tried this) using API's to write and clear the message subfile. The messages are being properly retrieved - they are written to the joblog - but they dont display when the screen with the input capable subfile reappears for correction. I was pondering this over the weekend and one possibility is that I could try the OVERLAY keyword on the control record of the message subfile. Any suggestions appreciated.

Answer(s): If you have OVERLAY on the main screen already, how are you displaying the message subfile? Are you writing it before the exfmt? for example C C WRITEMSGCTL EXFMTSCREEN

The OVERLAY on the message subfile is not requried but the OVERLAY on your main display IS required so that it does not erase your messages. Thanks for the response. The OVERLAY on the message subfile didn't work(I already had one on the main subfile). Your message subfile control record looks the same except that I did not use the last 3 keywords. The set of '*' for initializing the program message Q also looks the same. I will check on the joblog F9 tip tomorrow. This is for a file maintenance program that the IS dept will be using so the message subfile is not critical but It will really bug me until I get it to work. Check OVERLAY first. If this brings you no success: maybe your message subfile is empty because you sent the messages to another program message queue. Check the joblog, press F9 for message details, you'll see which program sends the message and in which program's message queue it was placed. If this is correct then check if you fill the name of the message queue correct. I prefer the following:

86

A R SMSFL SFL A SFLMSGRCD(24) A SMMSGK SFLMSGKEY A SMPGMQ SFLPGMQ A R SMSFLC SFLCTL(SMSFL) A SFLSIZ(2) SFLPAG(1) A OVERLAY A SFLDSP A SFLDSPCTL A SFLINZ A N01 SFLEND A SMPGMQ SFLPGMQ In my RPG-programs, i use the follwing line to set the program name: C MOVEL '*' SMPGMQ Hope this information is the one you needed. CL: Copying User Profiles Question: I have a question regarding the creation of user profiles. I want to create a simple screen where user profiles can be created for a piece of application software. This application has a base user profile in which all of the new user profiles should be copied after. My problem is this... I'm trying to figure out how to create a user profile within a CL that is exactly like a base user profile. Unfortunately, the command CPYUSRPRF does not exist and RTVUSRPRF could be used, but it would be way too cumbersome. Does anyone know of any user API's that could handle this? I would greatly appreciate it. Answer(s): I thought that the CRTDUPBJ command would copy a *USRPRF object, but I could be wrong. It's at least worth trying. You might also want to consider group profiles. Here's a technique we use to accomplish something similar to what you are describing. We create a message id in a user message file ADDMSGD MSGID(USR0001) MSGF(MYMSGF) MSG('The second level contains CRTUSRPRF defaults') SECLVL('CRTUSRPRF USRPRF(&1) PASSWORD(&1) PWDEXP(*YES) USRCLS(&2) INLPGM(&3)....') FMT((*CHAR 10) (*CHAR 10) (*CHAR 10)) You can specify whatever message variables and defaults that you like. Anyway you get the picture. A program can use the RTVMSG command or the QMHRTVM api to retrieve the second level text with the message variables replaced with values and run the command with QCMDEXC. ILE RPG, RPG IV, vs. RPG/400 Question: We are primarily RPG 400 site. We are contemplating on moving to RPG IV (ILE RPG). Advice from those who have done this will be appreciated Thanks Answer(s): Just take a look at all the limitations that have disappeared, think about the easier coding for string and arithmetic expressions and the choice should be clear. Whether or not you'll be using the ILE capabilities (service programs, modules, ...) is another question, but I would at least start with RPG IV (or whatever it might be called) and use the CRTBNDRPG option. So far I haven't encountered any disadvantage except that compilation times (including the CRTPGM) are a bit longer compared to before. Take your migration in two steps. 1) Focus on the Language (i.e. RPG-IV). The features such as date/time arithmetic and many more are more than worth the effort which is relatively minor. As Paul indicated, the freedom from limits is worth the move all by itself. 2) Learn the ILE piece (they are separate - one does not require the other) separately. ILE tends to be difficult for many AS/400 people. If you have any old mainframe folks in your shop, they'll tell you that IBM finally brought the linkage

87

editor to the 400. Approcah ILE with caution using modular programming only where necessary and major benefit can be gained. In most cases we've found that the complexity of modular programming isn't worth the gain, but RPG-IV is. We converted all of our old RPG/400 (actually RPG-III and some RPG-II code) to RPG-IV via the converter that IBM provided without incident one weekend. All of our 5,000 plus programs compiled and ran without incident. They did a real good job on the converter, just create new source files with the proper record lenght, set up a CL and let her rip. You may want to check NEWS/400's or Midrange Computing's list of books and buy some of. I can't remember the title, but there is on book (small green book) that explains the differences and introduces the new op codes to an experienced RPG-III programmer. That was all we needed to get the migration done. We did conduct a seminar where we used overhead foils to present the new features to the programmers and spend 1/2 day talking about them (including ILE). We did no other training. There is another "problem", the compiled objects are about double size than RPG400 programs. I have a shareware tool that might help you out too. It is call Convert ILE Format (CVTILEFMT). It is run after you convert the source from RPG to RPG/IV using IBMs CVTRPGSRC command. It formats the source into a free-format style. It is very similar to Connection 2000's convert program (but doesn't cost anything). You can view examples and download the program at my web site: http://www.bvstools.com Feel free to ask questions, etc.

RPG and subfiles Question: Hi, I've been attempting to program a sub file and use it with RPG/400. I've got most of what needs to be done, complete, but I've been trying something which doesn't seem to work. I have a field called OPT in my sub file record SFLRCD, and when a certain number, 4, is placed in OPT I want to delete the record which is currently displayed. I've got the READC and the DOWEQ loop done, so it will read through all the records until it finds this one and I'm also subtracting one from the RRN. The error I receive is 5198, the UPDAT or DELET entry is not valid for the file or record type. Yet, in a text book I have it notes that you can delete from a sub file, but doesn't have an examples. Answer(s): You can't delete an individual subfile record. You can delete a whole subfile with the SFLDLT keyword. (You use this only if you already have 24 subfiles (!) in use and you want to put another into use) At my company, when a record is deleted, we update the subfile record with the text "*DELETED*" and protect the option field. Another thing you might try is to rebuild the subfile after a delete is performed, and position the subfile to the record that was before the one that was deleted. Why not load the subfile from the original file as a page at a time subfile? Sure the subfile will start again at the first page, but a good practice is to have a "position to" field at the top as to where to start loading from the file. Thank you anyone who has responded I realized I was in error late last night, the book actually does not say you can delete a record in a sub file. damn. > Yet, in a text book I have it notes that you > can delete from a sub file, but doesn't have an examples. So I have devised a method of storing the records in a temp file because the program is actually adding records as it runs, then if the user places a 4 in the OPT field then it will clear the sub file delete the record in the temp file and then reload the sub file with the records in the temp file. I was going to do it with a run-time array, but someone said that may use up to much resources. You can't delete a subfile record. I always use a READC loop, delete my record from the file and "mark" the subfile record as deleted. You could do this by putting a text "** deleted **" in a text field and/or by protecting your option field so the user can't delete the same record twice. Make a F5=Refesh key to reload your subfile. You can do it. To do a Delete or update the file you are working on has to be set in the file specs as "update" E.G. (FSPECS) FDATAFILE UF A E DISK To do the delete use this sample code: *****************************************************************

88

* $PFDLT Primary File record deletion subroutine ***************************************************************** C $PFDLT BEGSR C SVKR CHAIN RUPDAT 47 C *IN47 IFEQ *OFF * If rcd exists then delete it C DELETE RUPDAT C MOVE 'Y' DELF C ENDIF C ENDSR after you do a deletion or a change or add. You need to do a READC on the subfile and then reload the subfile so it will reflec the change. The basic method is that you delete the record from the file from which the subfile is build, and then reload the subfile. I know that you can update a subfile record but I've never tried to delete a record on it, and I've my doubts that it can be done. It has been a couple of years since did what you want to do, but, all you need is the READC and then check for the correct value in the OPT1 field. if it is correct then - DELET SFLRCD (DELETE for RPG / ILE). this should not pose any problems. if you can fill the sub-file then you can delete from it. you do not need to subtract anything from RRN either. hope this helps. Multiple subfiles Question: Hi, I need to write an RPG program that will display and allow input for more than one subfile at the same time. Has anyone done this? If so would you please enlighten me on the technique? Answer(s): Did it some time ago. Something like SEU split screen (F15). Two things to remembrer: 1. Have at least one EXFMT but use writes and reads in other situations, especially, 2. Get the cursor location to determine which subfile the user is on. 3. You will have to use ASSUME to keep both on screen I've done this also, with up to 4 subfile windows. Works slick except for one thing. Transfering control from one subfile to the next isn't as easy as positioning your cursor on the next subfile and pressing enter. It seems the last control record written is the one that is "active". I added a function key to change control through each of the subfile windows. Hi, you can display more than one subfiles in two ways: Either ------------------------------------- + + + subfile1 + + + ------------------------------------- + + + subfile2 + + + ------------------------------------ or side by side (or anything else) with subfile-windows. Note: When you read a record from one subfile (f.e. READC) and then you read a record from the next subfile, the system "forgets" the rcdnbr of the first read. If you need it for an update of the subfilke you must save the rcdnbr and then read the record (with CHAIN) for the update again. Field masking for passwords Question: Hello all, I do not know if this is going to be dumb question but I need to ask it. I need to encrypt a password that will be on a subfile. I do not want the actual password to appear but instead, like in MS Access you can substitute the text for some special characters while maintaining the password on the physical file. if anyone has done something similar, or has any suggestions please e-mail me and share. Otherwise I appreciate your attention to my little request and thank you. Answer(s): I have an encoded security file on the main system. I did it by creating a table within an RPG program that would translate the characters from what was entered on the screen to what was stored in the file. Of course, once the program was put in place, I removed the source from the system. Saved it to diskette, printed it out and took it all home. If I ever leave the company, I hope I remember to give them the diskette. That could be a problem. Poor memory. :-) I'm sure

89

their are better ways to do this, but this is what I did. The AS/400 alternative for this is setting a field as non-display. Having the "*" as a placeholder like in most PC applications is however not possible. SFLMODE keyword in an ILE program Question: I am trying to use the SFLMODE keyword in an ILE program to keep my subfile in Fold/Truncate mode while I page down through my subfile. Does anyone have any examples of how to use this in RPG ? Answer(s): Here is a short example: DSPF: SFLMODE(&FIELD1) N59 SFLDROP(CF14) 59 SFLFOLD(CF14) RPG: In your *INZSR (or where else you want) set the FIELD1 to *ON. In your dspf-routine: EVAL *IN59 = (FIELD1=*OFF) EXFMT xxx ... ... ... Note: CF14 is the key for fold/trunc. The field FIELD1 (or any other name) must be 1 alpha. You can replace the indicator 59 with any other ind. Subfile size Question: Does anyone know if it is possible to retrieve the subfile size or subfile page size in an RPG program without filling the subfile with blank records and using a counter? Answer(s): I believe that the INFDS data structure holds that sort of information. You will need to look at the IBM infoseeker for the breakdown on the data structure. Color coding records in a subfile Question: Can anyone instruct me on how to color condition fields in a subfile. For example, I would like to conition a balance field to be displayed in red if it is debit figure. Only the subfile record with debit balances should be red all other records on the subfile display should not be red. Answer(s): Now days there is an option to do all of this with DSPATR(&field) see http://AS400BKS.rochester.ibm.com:80/cgibin/bookmgr/bookmgr.cmd/BOOKS/QBJAUI00/ 3.3.39? ACTION=MATCHES&REQUEST=dspatr&TYPE=FUZZY&searchTopic=TOPIC&searchText=TEXT &searchIndex=INDEX&rank=RANK#FIRSTHIT Greetings A neat way to do this is to make the SFL field one character longer than normal and move a hex attribute for the required color into the furst position of the field Now this is obviously not feasible with numeric fields but works very well for character fields and removes the need to track the color on the field (indicators are always reset when reading 'C' to a subfile) If you really need to use this technique with a numeric field, make the SFL field character and then use the API

90

to edit the field into a 'numeric' format Let me know if you want to receive a table of hex attributes and the color/attribute that they represent MSGLINE in windows Question: Hello , I have a problem with message handling for WINDOW-diplay formats. For usual formats (RECORD) I use a messageformat (MSGSFL). The messages I give out with an API or with the SNDPGMMSG command. Now my problem: What's with the message line in WINDOW-Formats? There is the parameter *MSGLIN / *NOMSGLIN. But how it works? It's not working with the API or the SNDPGMMSG command. Note : I don't want to give the message outside the window (in line 24). I want to use the messageline IN the window (Where you can e.g. see the message "pressed funktion-key not valid" or sonething like this; I don't know the exact english errortext) Can anyone help me ?

Answer(s): If you define the window parameters in a separate record format e.g.
R WINDOW wINDOW(*DFT 16 31 *NOMSGLIN) and then all your normal formats R DSP01 WINDOW (WINDOW) R ERRSFL SFLMSGRCD(16) R ERRCTL SFLCTL(ERRSFL) WINDOW(WINDOW) OVERLAY You can use the normal way to get your error messages in the message subfile. Controlling cursor movement on a display file Question: Anyone know of an easy way to have the cursor move top to bottom through input capable fields instead of left to right within a display file, NOT a subfile. Answer(s): Use CSRLOC. This allows you to set the value of the row and column of the cursor before each write to the screen. You could set up a loop to increment the value of the row before each WRITE or EXFMT. The column value would remain constant. The CSRLOC does NOT work with subfiles. Your DDS manual should have the syntax and format of the keyword. You can use the field level keyword FLDCSRPRG to specify the next field to go to when exiting that field. You can go to any other field on the screen which makes for some real interesting possibilities. API returns error! Why? Question: Greetings, I have a QAD (quick and dirty) program that dumps objects to a user space using the QUSLOBJ API. I pass parms of the object name, library name and object type The problem is that when I run it, I get an error on the call to QUSLOBJ that says the object type is not valid We are talking *ALL or *FILE which *IS* support by the API If anyone would like to take a look and see if they can see the problem then I will attach the source (DSPF & RPG) to this posting (I think I can attach) so that it can be uploaded using CPYTOPCD from a folder I will also embed the code at the end in case that does not work I have used the Retrieve from USer Space API but the same thing happens if I use memory pointers Also, the program has worked a couple of times and then stopped without any program changes (I promise !!) This problem is one I have been having recurrent problems with API's that they will work one time and then not another !!! I am running V3R7 on a AS/436 on fairly current CUM level (6 mo)

91

Answer(s): You need to define SPACELEN & SPACEPOS as 9B 0 to be 4-byte integers. Just like you have done with SPACESIZE. Usually when things change mysterically, one ore more fields, used as parameters, have the wrong length, and the following variables (in memory) may be overwritten. Put an RPG Program on the Web Question: I need to share an RPG application via Internet using AS/400 as server. Is it possible? And what do I need ? Is it possible to do the same thing with a Client/Server application (Win95->AS/400) ? Many thanks for your help. Answer(s): A simple and cheap solution is to use TN5250. MochaSoft in Denmark has a good inexpensive one for Windows 95. Visit their site at http://www.mochasoft.dk/ They are also working on a java applet that runs from a browser. If you are at the right release level of OS/400 you could try WSG(workstation gateway under tcp/ip) or buy their HOD(host on demand for TN5250); it runs on/with a browser. All of the above assumes that a simple 5250 black and green interface is acceptable. Hope this helps. The quickest, easyiest, and cheapest way to accomplish your objective is via IBM's WSG (workstation gateway) that ships free with OS/400 and has since V3R2 and V3R6. You will have to configure and install IBM's HTTP (ICS) server (see the TCP/IP configuration guide) and WSG (same guide). WSG is a server based terminal emulator allowing you to access 5250 looking screens via your web browser. The downside is that WSG does not map the keyboard. The freebees, WSG and ICS lack secure sockets and user authentication. WSG does use standard AS/400 security. If you want stronger software you can use IBM's ICSS on V4R1 or I/Net's Commerce Server/400 and Webulator products which run V3R1 and up. An even quicker and easier method is to use CA/400 or Reflection as a TELNET client. You must have TCP/IP configured on your AS/400 and your client must have CA/400 or Reflection on their PC. They can then connect to your AS/400 and access your application via TELNET (also in the TCP/IP configuration guide). See the COOL TITLE ABOUT THE INTERNET book for a starting point. Try out ANDAR Internet Suite/400 for 30 days free. It allows you to write standard RPG programs to publish web pages. With just a simple knowledge of HTML, you can write RPG programs to retrieve data from your application database and "print" HTML using standard O-Spec. ANDAR Internet Suite/400 will automatically take your "printed" output HTML code and send it to the client browser. Data from the HTML form fields is passed to your program as an externally described data structure, no need to learn complex APIs. ANDAR Internet Suite/400 can be viewed and downloaded from http://www.helixdp.com Follow the links in a real live demo of the product as we show you a "Customer Inquiry" application implemented over the web. The suite also includes security software for your AS/400 servers and a log analysis tools. Variable length records in RPG Question: We receive lots of name and address files each with different layouts and varying attached data and we would like to set up a system where they are all stored in the same Variable Length file. I understand that these can be read in RPG easily enough but I am yet to see a method for writing to a VL file. Can it be done? if so got any code? Answer(s): I too did this a while back. I am writing from memory, so be prepared to do some "empirical validation". Here are just a couple of points that come to mind. (1) In the old RPG/400, at least, you must compile with a special option; I think it is CVTOPT(*VARCHAR), for the program to see the variable-length fields. Here's a trap that really got me: after compiling on a CISC box and restoring on a RISC box (at least, that's what my customer *said* they did) the program no longer accessed the variable-length fields. (2) An easy way to calculate the length of the actual data is to use the CHEKR operation with a blank in factor 1. I did this a while back. I used a data structure to redefine the database field as two subfields. The first (LEN) contained the actual length of the field's contents, the second was the field contents. When writing to the database field (the DS), you need to store the length of it's new contents in the LEN field. Differences Between RPG400, RPG IV, and ILE RPG Question:

92

Please excuse my ignorance on this subject, but could someone enlighten me on the main differences between RPGIV, RPG400 and the newer ILE version? Answer(s): In a nutshell, ILE is an architecture. RPG400 is the current version of the RPG language and is available on all AS400's. RPG IV has a lot of changes to the whole structure of the RPG language and is only available starting with V3R7 (I think!) Both RPG400 and RPG IV can use ILE architecture. >>In a nutshell, ILE is an architecture. 100% correct >>RPG400 is the current version of the RPG language and is available on all AS400's. Wrong. It is certainly available on all AS/400s, but it is not current - hell it hasn't been updated since V2R2 (or was there a tiny change in V2R3?) The current version is RPG IV (otherwise known as ILE RPG). >>RPG IV has a lot of changes to the whole structure of the RPG language and is only available starting with V3R7 (I think!) Wrong. RPG IV is available starting from V3R1 and is the _only_ RPG compiler (not counting VARPG on the PC) being enhanced by IBM. >>Both RPG400 and RPG IV can use ILE architecture. Wrong. Only RPG IV can use ILE. 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 and 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. More: There is of course much more that is new in ILE RPG. For performance reasons, you should have a good understanding of ILE. Bryan Meyers has written several very good articles in NEWS/400 that can help you avoid some ILE traps. He is also one of the moderators of NEWS/400's RPG Programmers Community. Undeleting records Question: When I display the file attributes on a particular PF, I see that it has 19675 records and 191 deleted records. Is there anyway to recover (undelete) them? Unfortunately the file is not journaled. Thanks.

93

Answer(s): I think it's possible - i've found the pgm undelete written in MI - on www.news400.com resources, code. You probably already have the answer, but here's what I know: In news/400 a very, very long time ago there was a utility (I use it, too, but not on the production machine) based on the following: Deleted records aren't really deleted, just marked with a specific hex code. Normal operating functions will just skip these, but you can save the file to a savefile (*SAVF). This savefile can be read as a physical file, with records of 512 positions. If you know what hex code means "beginning of record", "end of record" etc, you can identify also the deleted records and write them to a file again. Hope this makes it clearer,

Logical files
Logical files allow a user to access data in a format that is different from the way it is stored in one or more physical files. The logical file contains no data records. It contains the corresponding record number of the data record in the physical file. The logical file will contain the index to the physical file. Logical files provide the path to the physical file. There are four types of logical files. The first is the simple logical file. This maps data from a single physical file to another logical record definition. The second is the multipleformat logical file. This logical file allows access to several physical files. The third type of logical file is the join logical file. The join logical file defines a single record definition that is built from two or more physical files, tables, logical files, or views. The total number of physical files and tables cannot exceed 32. The forth type of logical file is the SQL view. These are similar to join logical files but SQL view logical files locate the access path at run time by use of the Query Definition Template and are not maintained by each join.

94