RPG Tricks and Techniques Tuohy/RPG Tricks and... · 2015. 5. 27. · If *DEBUGIO is specified,...

34
Paul Tuohy ComCon System i Developer 5, Oakton Court, Ballybrack Co. Dublin Ireland ComCon Phone: +353 1 282 6230 e-Mail: [email protected] Web: www.systemideveloper.com www.ComConAdvisor.com RPG Tricks and Techniques ComCon Paul Tuohy Paul Tuohy, author of "Re-engineering RPG Legacy Applications" and "The Programmer's Guide to iSeries Navigator", is one of the most prominent consultants and trainer/educators for application modernization and development technologies on the IBM Midrange. He currently holds positions as CEO of ComCon, a consultancy firm based in Dublin, Ireland, and founding partner of System i Developer, the consortium of top educators who produce the acclaimed RPG & DB2 Summit conference. Previously, he worked as IT Manager for Kodak Ireland Ltd. and Technical Director of Precision Software Ltd. In addition to hosting and speaking at the RPG & DB2 Summit, Paul is an award-winning speaker at COMMON, COMMON Europe Congress and other conferences throughout the world. His articles frequently appear in iProDeveloper, The Four Hundred Guru, RPG Developer and other leading publications. Paul also hosts the popular iTalk with Tuohy podcast interviews. This presentation may contain small code examples that are furnished as simple examples to provide an illustration. These examples have not been thoroughly tested under all conditions. We therefore, cannot guarantee or imply reliability, serviceability, or function of these programs. All code examples contained herein are provided to you "as is". THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED. © ComCon & System i Developer, LLC 2005-2015 1

Transcript of RPG Tricks and Techniques Tuohy/RPG Tricks and... · 2015. 5. 27. · If *DEBUGIO is specified,...

  • Paul Tuohy ComCon System i Developer 5, Oakton Court, Ballybrack Co. Dublin Ireland ComCon

    Phone: +353 1 282 6230 e-Mail: [email protected] Web: www.systemideveloper.com www.ComConAdvisor.com

    RPG Tricks and Techniques

    ComConPaul Tuohy

    Paul Tuohy, author of "Re-engineering RPG Legacy Applications" and "The Programmer's Guide to iSeries Navigator", is one of the most prominent consultants and trainer/educators for application modernization and development technologies on the IBM Midrange. He currently holds positions as CEO of ComCon, a consultancy firm based in Dublin, Ireland, and founding partner of System i Developer, the consortium of top educators who produce the acclaimed RPG & DB2 Summit conference. Previously, he worked as IT Manager for Kodak Ireland Ltd. and Technical Director of Precision Software Ltd. In addition to hosting and speaking at the RPG & DB2 Summit, Paul is an award-winning speaker at COMMON, COMMON Europe Congress and other conferences throughout the world. His articles frequently appear in iProDeveloper, The Four Hundred Guru, RPG Developer and other leading publications. Paul also hosts the popular iTalk with Tuohy podcast interviews. !This presentation may contain small code examples that are furnished as simple examples to provide an illustration. These examples have not been thoroughly tested under all conditions. We therefore, cannot guarantee or imply reliability, serviceability, or function of these programs. All code examples contained herein are provided to you "as is". THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED.

    © ComCon & System i Developer, LLC 2005-2015 1

  • ComConAgenda

    Basic Guidelines H Spec Compiler Directives Managing Prototypes Managing Service Programs Integers Varying Length Fields Indicators Sorting Arrays Messaging Call Back Keys in Free Form MOVE/MOVEL/MOVEA equivalents in free form Static Use offsets when told to Dynamic memory Record Locking

    ComCon

    if (hours

  • ComConBasic Guidelines

    Be Free Code EVERYTHING in free form Even if you are adding two lines of code to an old RPG II style program

    Mind your language Use proper names - customerName not custNam not cusNo not wkCsNo - messageType not msgType Speak the same language as the rest of the world - Table instead of Physical File - Index instead of Logical File

    Learn and adopt from other languages /INCLUDE instead of /COPY Constants in capitals

    Use the modern tool set (WDSC/RDi) The tools will help with everything new

    Embrace what is new Learn new habits

    ComConH Spec

    The H Spec has a new lease of life in RPG IV Defaults for program Debugging Compiler options

    The Compiler will search for the H spec in the following order: An H spec included in your source A data area named RPGLEHSPEC in *LIBL A data area named DFTLEHSPEC in QRPGLE

    The search stops when the first of these is found Use a copy member for any standard H-specs, not a Data Area

    © ComCon & System i Developer, LLC 2005-2015 3

  • ComCon

    H option(*srcStmt :*noDebugIO) H datFmt(*ISO) timFmt(*ISO) datEdit(*YMD-) H decPrec(63) H copyRight('This is mine, all mine!')

    Compiler Options on H-spec

    Many CRTBNDRPG/CRTRPGMOD keywords can now be specified on the H The compile options specified will override the ones specified on the CRTxxxxxx command

    Unsupported keywords: DBGVIEW, OUTPUT, REPLACE, DEFINE, PGM, SRCFILE, SRCMBR, TGTRLS

    Use Option(*SrcStmt:*NoDebugIO) Match program statement numbers with source line numbers !!

    Easier for debugging and end user support Skip the individual field steps for Input and Output specs Faster step function during debugging

    On the H spec, separate multiple options with a colon (:) – e.g OPTIONS( *SRCSTMT : *NODEBUGIO) With *SRCSTMT specified, the statement number reported when an error occurs during run time will correspond directly to the SEU sequence number. Without this support, the statement number reported did not correlate directly to the source statement numbers. Therefore, support of end user problems was much more difficult. Many support desks kept compiler listings of all programs just to be able to match the program statement numbers to SEU statement numbers. *NOSRCSTMT indicates that line numbers are assigned sequentially. If *SRCSTMT is specified, statement numbers for the listing are generated from the source ID and SEU sequence numbers as follows: stmt_num = source_ID * 1000000 + source_SEU_sequence_number For example, the main source member has a source ID of 0. If the first line in the source file has sequence number 000100, then the statement number for this specification would be 100. A line from a /COPY file member with source ID 27 and source sequence number 000100 would have statement number 27000100. Note: When OPTION(*SRCSTMT) is specified, all sequence numbers in the source files must contain valid numeric values. If there are duplicate sequence numbers in the same source file, the behavior of the debugger may be unpredictable and statement numbers for diagnostic messages or cross reference entries may not be meaningful. If *DEBUGIO is specified, breakpoints are generated for all input and output specifications. *NODEBUGIO indicates that no breakpoints are to be generated for these specifications. This means that during debug sessions, doing a Step function on an IO statement required many steps (one for each field in the format).

    © ComCon & System i Developer, LLC 2005-2015 4

  • ComCon

    New compiler directives Control which statements in your source are to be used

    Set (/DEFINE) and clear (/UNDEFINE) a condition-name Condition names may also be set on the CRTBNDRPG and CRTRPGMOD commands

    Include source based on the status of a condition-name /IF {NOT} DEFINED(condition-name) /ELSEIF {NOT} DEFINED(condition-name) /ENDIF

    Skip to the end of the current source /EOF

    Many uses including maintaining a common source for different versions of an application

    Copy Members may be nested

    Compiler Directives

    ComCon

    /if defined(DOCSPEC) /include QCPYSRC,SPSSRON /elseIf defined(DODSPEC) /include QCPYSRC,FORMATS /include QCPYSRC,PROTOTYPES /if defined(CGIPGM) /include QCPYSRC,PROTOTYPEB /include QCPYSRC,USEC /endIf /unDefine DODSPEC /define DOCSPEC /else /include STDHSPEC /define DODSPEC /endIf

    Conditional Compilation (1 of 2)

    A technique to allow multiple spec types in a single copy member Uses nested copies - SPSSRON contains free form calculations - FORMATS and PROTOTYPES contain D specs - STDHSPEC contains H specs

    The copy/include member STANDARD contains this

    © ComCon & System i Developer, LLC 2005-2015 5

  • ComCon

    /include QCPYSRC,STANDARD < Only the H spec code will be copied here > F F /include QCPYSRC,STANDARD < Only the D spec code will be copied here > D D /free (Lots of most excellent Code) /include QCPYSRC,STANDARD < and only the C spec code will be copied here >

    Conditional Compilation (2 of 2)

    The main program includes multiple copy directives for the same copy member STANDARD – on the previous slide

    Then the copy member can be used like this

    ComCon

    /if defined(*CRTBNDRPG) H dftActGrp(*NO) actGrp('MYSYS') /endIf ! H bndDir('MYBNDDIR') H option(*srcStmt :*noDebugIO) H datFmt(*ISO) timFmt(*ISO) datEdit(*YMD-) H decPrec(63) H copyRight('This is mine, all mine!')

    Predefined Definition Names

    Compiler has a few predefined definition names *CRTBNDRPG – if compiling using the CRTBNDRPG command *CRTRPGMOD – if compiling using the CRTRPGMOD command *ILERPG – if compiling using the ILE RPG IV compiler - as opposed to the Visualage for RPG compiler *VxRxMx – the target release version (or later) of OS/400 - Starting with a target of V4R4M0

    © ComCon & System i Developer, LLC 2005-2015 6

  • ComCon

    d myDSArray Ds qualified dim(4) d a 2a d b 2a ! /IF NOT DEFINED(*V7R1M0) d myDSMap_p s * inz(%addr(myDSArray)) d myDSMap Ds qualified based(myDSMap_p) d allData 4a dim(%elem(myDSArray)) d allA 2a overlay(allData) d allB 2a overlay(allData: *next) /ENDIF ! d i s 10i 0 /free // Assign values to array here ! /IF DEFINED(*V7R1M0) DANGER!!! THE V7R1M0 CODE HAS NOT BEEN TESTED. IT MUST BE TESTED BEFORE COMPILING UNDER V7R1M0 OR LATER FOR THE FIRST TIME. ! i = %lookup('ee' : myDSArray(*).a); /ELSE i = %lookup('ee' : myDSMap.allA); /ENDIF dsply i; *inLR = *on; /end-Free

    Future Coding

    You don't have to wait for a future release to code the latest functionality You just cannot compile or test it!

    Future coding is where, even though you are on an earlier release of the operating system, you want to include the code that should be used in a future release of the operating system. In other words, you want to write a program that contains both the coding solutions for both the current release and a future release of the OS – the solution to be used to be determined by the compiler. The important point to bear in mind is that you cannot yet test the "future code". To achieve our goal, we make use of the predefined Target Release condition in conditional compiler directives. The Target Release condition is in the format *VnRnMn. On the previous slide:-

    - The definition of the pointer and the overlaying data structure is only included if compiling for a version of the OS prior to V7R1M0.

    - The code to perform the %LOOKUP() function will be performed on the data structure array sub field or on the overlaying sub array, conditioned (again) by which version of the OS we are targeting on the compile.

    - The big warning comment is not a typo - it is indeed some spurious text that will cause the compile to fail if compiled at V7R1 or later (I did not forget the slashes for comments!). Remember, this V7R1+ code has not yet been tested so we want to be “reminded” to test when we first compile at V7R1 – at which stage we can remove the reminder.

    This technique is best used if all program sources are scanned/re-compiled as part of the upgrade process.

    © ComCon & System i Developer, LLC 2005-2015 7

  • ComConManaging Prototypes

    Bearing in mind that an application may end up having thousands of prototypes, just one source member containing all prototypes for the application will very quickly become unmanageable. The important point is that all programs should only have one copy directive to include all prototypes. The ability to have nested copy directives makes the maintenance of prototypes very straight forward. That copy member included in every source contains multiple copy directives to include other members containing prototype definitions. These other members would be:-

    - A copy member containing prototypes for all dynamic calls - A copy member per service program containing prototypes for all procedures exported from the service program - Or, for very large service programs, have a copy member per module in the service program. Then have a copy

    member for the service program that contains copy directives for all the module prototype members. You want to achieve the best of both worlds in that only one copy directive is required to have all prototypes included but the actual prototypes themselves are stored in multiple easy to manage members. But why stop with just prototypes? Why not include copy members containing standard definitions of data structures and named constants? All of these can be included with just one copy directive.

    © ComCon & System i Developer, LLC 2005-2015 8

  • ComCon

    Source Physical File with the same name as Service Program It will contain ALL sources for the Service Program

    Binding Directory with the same name as Service Program Other service programs that this service program binds to Does NOT contain module list

    “Normal” source members All module names start with the same characters (e.g. UTIL001, UTIL002 etc.) - Allows for generic name for modules in CRTSRVPGM For each module - Code module (RPGLE, SQLRPGLE, CLLE, one or more procedures) - Prototype member, with prototypes/base definitions relating to code module

    “Special” source members Binder language – member has same name as Service Program Service Program Prototype member - Contains /Include directives for the module prototype members - Programs include this member to get prototypes for the service program A standard H spec

    Managing Service Programs

    ComConManaging Service Programs – An Example

    The Service Program UTILITY Lets say it requires 3 modules

    We need... A binding directory names UTILITY - Add directives for any other service programs we need to bind to A source physical file named UTILITY, which contains members - UTILITY – binder language for service program

    • Must have an assigned signature - PUTILITY – Prototype member for service program

    • Contains /Include directives for PUTIL001, PUTIL002, PUTIL003 - STDHSPEC – H spec to be included in all code members - Code members UTIL001, UTIL002, UTIL003 - Prototype members PUTIL001, PUTIL002, PUTIL003

    To create the service program CRTSRVPGM SRVPGM(UTILITY) MODULE(UTIL*) SRCFILE(UTILITY) 
 SRCMBR(UTILITY) BNDDIR(UTILITY)

    More details “Development Environments”
http://www.itjungle.com/fhg/fhg051210-story01.html Contains a couple of handy user actions

    © ComCon & System i Developer, LLC 2005-2015 9

  • ComConManaging Service Programs – Adding

    Adding a module (UTIL004) Code the code member – UTIL004 Code the prototype member – PUTIL004 Compile the module - I usually write a “test”program for the module Add an /Include directive to PUTILITY for PUTIL004 Use the RTVBNDSRC command (with the *ADD option) to add export directives for UTIL004 to binder language member UTILITY Edit UTILITY and remove the ENDPGMEXP/STRPGMEXP inserted by RTVBNDSRC Create the service program as before

    Adding a new procedure to UTIL004 Exactly as above except... - No need to add an /Include directive to PUTILITY - When editing UTILITY, also remove the duplicated procedure names

    • Alternatively, do not use RTVBNDSRC and add export manually

    ComConUse Integers

    A Binary representation of a number Integers have a data type of I or U

    I = Signed U = Unsigned

    Replaces the old B (Binary) data type Has a broader range then binary (for the same storage) No conversion to/from packed (as with binary) Use instead of B data type with APIs

    Use for compatibility with other languages C, Java and MI (and CL in V5R3) Used extensively with C functions and APIs

    Better performance for math operations Faster then packed (or zoned or binary) - But would you notice the difference?

    Use 10I 0 as your default work field size If you do not need decimals/fractions For numeric parameters

    © ComCon & System i Developer, LLC 2005-2015 10

  • ComConInteger Details

    Integers are identified in different ways in different places RPG API documentation C and Java - The same

    RPG Bytes Range API C and Java3 i 0 1 -128 to 127 BIN1 char5 i 0 2 -32,768 to 32,767 BIN2 short

    10 i 0 4 -2,147,483,648 to 2,147,483,647 BIN4 long or int

    20 i 0 8 -9,223,372,036,854,775,808 to
9,223,372,036,854,775,807

    BIN8 long

    3 u 0 1 0 to 255 UBIN1 Unsigned char

    5 u 0 2 0 to 65535 UBIN2 short

    10 u 0 4 0 to 4,294,967,295 UBIN4 long20 u 0 8 0 to 18,446,744,073,709,551,615 UBIN8 long long

    ComCon

    D firstName s 10a inz('Paul') D lastName s 10a inz('Tuohy') D v_firstName s 10a varying inz('Paul') D v_lastName s 10a varying inz('Tuohy') D fullName s 21a varying fullName = %trimR(firstName) + ' ' + %trimR(lastName); fullName = v_firstName + ' ' + v_lastName; fullName = ''; %len(fullName) = 0; fullName = *blanks; // fullName now has 21 spaces

    Varying Length Fields

    Excellent for string handling Reduces requirement for %TRIMx functions The length is set when data is moved into it

    The %LEN function can also be used to reset the length Be careful using *blanks

    © ComCon & System i Developer, LLC 2005-2015 11

  • ComCon

    D firstName s 10a inz('Paul') D lastName s 10a inz('Tuohy') D padding s 500a varying inz(*blanks) ! %len(padding) = 10; fullName = %trimR(firstName) + padding + %trimR(lastName);

    CAT Functionality with Varying Fields

    The fixed format CAT operation allowed you to specify trailing blanks F1 field is right trimmed and trailing blanks added Trailing blanks specified after F2 - As a literal or constant - As a numeric field

    Define varying field for padding Length should be maximum length required Initialize to all blanks

    Set length of padding field to required trailing blanks and concatenate

    D firstName s 10a inz('Paul') D lastName s 10a inz('Tuohy') C firstName Cat lastName:10 fullName

    ComCon

    D DS D webOut 2048a varying D webDataLen 5i 0 overlay(webOut) D webData 2048a overlay(webOut:*next) ! webOut = 'THIS IS THE DATA';

    A quick word about Varying Length fields

    Varying length fields have two components The current length of the field - a two byte binary (5i 0) or a four byte binary (10i 0) - Depending on the defined length of the field The actual data

    A varying length field requires two (or four) more bytes of storage then the defined length Be careful when using varying length fields in data structures

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

    0 
0

    1 
6

    T H I S T H E D A T A

    © ComCon & System i Developer, LLC 2005-2015 12

  • ComCon

    Fdisplay CF E workstn indDS(dsp) D dsp DS qualified D F3_Exit n overLay(dsp:3) D F12_Cancel n overLay(dsp:12) D resetErrorIndicators... D 3a overLay(dsp:31) D error n overLay(dsp:31) D startDateError... D n overLay(dsp:32) D endDateError... D n overLay(dsp:33) dsp.resetErrorIndicators = *zeros; if (startDate < today); dsp.startDateError = *on ; endIf; dsp.endDateError = (endDate < startDate); dsp.error = (dsp.startDateError or dsp.endDateError); exFmt testRec; if (dsp.F3Exit or dsp.F12_Cancel);

    Use Named Indicators

    Use real names for your display and print file indicators !

    The display/printer file MUST specify the INDARA keyword

    Logical Expression

    This example illustrates the use of the INDDS (indicator Data Structure). Note the definition of the DSPIND data structure. When this is specified on the F spec, the programmer MUST use the indicators in the data structure and NOT the numbered indicators to control this file. For example, in this program, if the programmer turned on indicator *IN31 directly in the program logic, it would have zero impact on the display file. The program logic must refer to the indicator as "Error" to turn it on or off in order for it to have an impact on the display file. The keyword INDARA must be specified for the display/printer file. This does not normally have any impact on the RPG program. It specifies that indicators should "travel" in their own area, separate from the normal data stream. As a result any CLEAR or RESET operations applied to a record format will not affect indicators in an INDARA file, but would have done so without this keyword. If you use CLEAR and RESET you may need to add aditional code to handel the indicators. This is the DDS for the display file used in the RPG example: ! INDARA

    R TESTREC CF03(03) CF12(12)

    5 11'Start Date: . .'

    STARTDATE L 5 27DATFMT(*USA)

    32 DSPATR(RI PC)

    32 5 39'Date cannot be earlier than today'

    7 11'End Date: . . .'

    ENDDATE L 7 27DATFMT(*USA)

    33 DSPATR(RI PC)

    33 7 39'Date must be later than Start Date'

    31 10 21'Please correct above error'

    © ComCon & System i Developer, LLC 2005-2015 13

  • ComCon

    D baseAddress DS based(baseptr) qualified D street1 30 D street2 30 D city 20 varying D state 2 D zip 5 D zipPlus 4 ! D invoiceInfo DS qualified D mailAddr likeDS(baseAddress) D shipAddr likeDS(baseAddress)

    A Based DS can be used to define “standard” data A based DS does not take up memory Great for API formats Great for prototypes Leads to the use of qualified data structures

    But there are "issues" Compiler allows reference to the DS and subfields Cannot initialize subfields

    Based Data Structures (to V5R4)

    ComCon

    D baseAddress DS template qualified D street1 30 D street2 30 D city 20 varying D state 2 inz('TX') D zip 5 D zipPlus 4 ! D invoiceInfo DS qualified D mailAddr likeDS(BaseAddress) inz(*likeDS) D shipAddr likeDS(BaseAddress) inz(*likeDS)

    Based Data Structures – Better in V6R1

    A Based DS can be defined using TEMPLATE keyword TEMPLATE allows subfields to be initialized Cannont “accidently” reference subfields in template

    © ComCon & System i Developer, LLC 2005-2015 14

  • ComCon

    /include QINCLUDES,BASEINFO ! D invoiceInfo DS qualified D mailAddr likeDS(BaseAddress) inz(*likeDS) D shipAddr likeDS(BaseAddress) inz(*likeDS)

    Referencing Based Structures

    Where to store based structured In same member as prototypes - If structure relates to a program or procedure In “standard” copy members

    Should be included in ALL programs/procedures Same as prototypes

    ComCon

    D compileData DS D 27a inz('January February March ') D 27a inz('April May June ') D 27a inz('July August September') D 27a inz('October November December ') ! D monthNames 9a overlay(compileData) dim(12)

    Overlaying a DS & Unnamed Fields

    The Overlay keyword can overlay the DS itself Makes a great alternative to using compile-time data

    Initialize the data near the array definition itself

    No need to chase to the end of the source member Note that the DS subfields do not need to be named But can still have INZ values!

    © ComCon & System i Developer, LLC 2005-2015 15

  • ComCon

    D addressInfo DS // Note that Dim is specified at the group field level D addressData 52a dim(1000) D streetA 30a overlay(addressData) D cityA 20a overlay(addressData: *next) D stateA 2a overlay(addressData: *next) /free ! sortA %subArr(cityA:1:noLoaded); // Sort into City sequence ! sortA %subArr(stateA:1:noLoaded); // Sort in State sequence

    Using SORTA with Group Fields

    Want to sort an array with different keys? Group fields can provide an answer

    Be sure to specify the DIM at the group field level Then the array can be sorted on any of the subfields

    Make use of the %SUBARR() BIF Subset of the elements of the array

    Note that when using this technique all of the other fields in the array (i.e. those that are part of the group) will be "pulled along" with their associated values. !ASCEND or DESCEND can be specified as normal along with the DIM keyword. So, while you can sort on any of the fields in the group, you can only sort ascending OR descending sequence on any given array. !The %SUBARR BIF was introduced in V5R3. In this example NoLoaded contains the number of elements loaded to the array.

    © ComCon & System i Developer, LLC 2005-2015 16

  • ComCon

    Load Once – Reference Often Subfiles

    Load the data to an array Load the subfile from the array Users can select “sort by” columns - An example in a moment

    Tables Instead of constantly chaining to tables Load the table to an array Use %Lookup instead of CHAIN

    Not a good option for “volatile” data

    Why Use Large Arrays to Store Data?

    ComCon

    d subRecData E Ds extName(SUBSORTD:SUBREC) ! d Ds d allSubRec like(subRecData) d dim(9999) ascend d arrOption like(option) overLay(allSubRec) d arrCode like(code) overLay(allSubRec:*next) d arrName like(name) overLay(allSubRec:*next)

    Use an Array to Store Subfile Data

    Subfiles Load the data (usually from databases) to an array - Each element is a subfile record and each subfield is an “array mapping” Load the subfile from the array Users can select “sort by” columns - Program uses SORTA and reloads the subfile

    A REF(*LIBL/CUST) A R SUBREC SFL A OPTION 1A B 7 3 A 31 DSPATR(RI PC) A CODE R O 7 11 A NAME R O 7 22 **** In the SFLCTL record A CA06(06) A RTNCSRLOC(*RECNAME &CURSORREC &CURSORFLD) A CURSORREC 10 H A CURSORFL 10 H

    © ComCon & System i Developer, LLC 2005-2015 17

  • ComConSorting a Subfile Array

    d subRecData E Ds extName(SUBSORTD:SUBREC) ! d Ds d allSubRec like(subRecData) d dim(9999) ascend d arrOption like(option) overLay(allSubRec) d arrCode like(code) overLay(allSubRec:*next) d arrName like(name) overLay(allSubRec:*next)

    numRows = 0; read dataBase; dow not %EOF(dataBase); numRows += 1; allSubRec(numRows) = subRecData; read dataBase; endDo;

    for RRN = 1 to numRows; subRecData = allSubRec(RRN); write subRec; endFor;

    select; when cursorFld = 'CCODE'; sortA %subArr(arrCode: 1: numRows); when cursorFld = 'ANAME'; sortA %subArr(arrName: 1: numRows); endSl;

    Load data to array Load subfile from array

    Sort relevant array based on RTNCSRLOC field -

    then Load from array!

    ComCon

    D sortIt PR extProc('qsort') D dataStart * value D elements 10u 0 value D size 10u 0 value D function * procPtr value ! D findIt PR * extProc('bsearch') D lookFor * value D dataStart * value D elements 10u 0 value D size 10u 0 value D function * procPtr value

    Using Large Arrays

    But what about sorting and scanning large arrays? Ones that go beyond the 64K or 16M maximum

    Use the C functions qsort and bsearch More powerful versions of SORTA and LOOKUP Refer to the Red Book "Who Knew You Could Do That With RPG IV?" for details and examples

    © ComCon & System i Developer, LLC 2005-2015 18

  • ComConAn Approach to Handling Error Messages

    Traditional Approach ERRMSGID in DDS Use a Message Subfile - Send messages to program message queue - Message subfile displays messages in program message queue

    Unfortunately, this approach will not work in non green screen environments Not every client will know what a program message queue is!

    An alternative approach A module that stores messages

    Stores an array of messages Contains subprocedures to - Add a message - Clear stored messages - Retrieve a message - Return the number of stored messages

    The full details in "Getting the Message - Parts 1 and 2" http://www.itjungle.com/fhg/fhg101409-story01.html http://www.itjungle.com/fhg/fhg102109-story01.html

    ComCon

    ! d message DS likeDS(def_MsgFormat) /free ! clearMessages(); addMessage('ERR0001': employeeID: %char(salary)); addMessage(MS_END_RUN); addMessage('ERR0002': employeeID); addMessage('ERR9001': *omit: % employeeID); addMessageText('Bad things happened!!!'); if (messageCount() > 0); for i = 1 to messageCount(); getMessage( i: message); // Do what you will with the message endFor; endIf;

    Using Message Procedures

    A quick idea of how it works

    ! d def_MsgFormat Ds qualified d based(dummy_Ptr) d msgId 7a d msgText 80a d severity 10i 0 d help 500a d forField 10a

    © ComCon & System i Developer, LLC 2005-2015 19

  • ComConCall Backs

    Call Back Where a call is made back to a subprocedure in the calling program Not an alien concept - %HANDLER() BIF used with XML-INTO - qsort() and bsearch()

    Scenario Program calls a subprocedure to calculate tax (yech!)

    The Tax Calculation Certain amount of tax calculation is "standard" Additional tax calculations, depending on country Sub procedure written for each country tax calculation

    The Tax Calculation Subprocedure Performs some standard calculations Calls back to a subprocedure in the calling program - Does not have to be in the calling program, as long as it is in a bound module Does some more calculations Returns

    How do we identify the subprocedure to be called A procedure pointer

    ComCon

    ! if (country = 'IE'); tax_Ptr = %paddr(calculateTaxIE); elseIf (country = 'US'); tax_Ptr = %paddr(calculateTaxUS); endIf; ! calculateTax(data: tax_Ptr);

    Calling Procedure Sets Required Procedure Pointer

    In the calling procedure Set the procedure pointer for the relevant country tax calculation Imagine a lot more country procedures - Note that the interfaces are the same for each subprocedure

    ! d calculateTaxIE pr 15p 2 d gross 15p 2 const d taxCode 2a const d calculateTaxUS pr 15p 2 d gross 15p 2 const d taxCode 2a const ! d tax_Ptr s * procPtr

    © ComCon & System i Developer, LLC 2005-2015 20

  • ComCon

    ! // Lots of nasty tax calculations // Perform call back p_calculateTax = tax_Ptr; calculateCountryTax(b_tax_data.gross: b_tax_data.taxCode); // More nasty tax calculations

    The Tax Calculation Subprocedure

    Generic prototype to call back to country tax subprocedure EXTPROC identifies a procedure pointer Prototype definition of all subprocedures must be the same Set the procedure pointer Call the subprocedure

    ! d p_calculateTax s * procPtr d calculateCountryTax... d pr 15p 2 extProc(p_calculateTax) d gross 15p 2 const d taxCode 2a const

    ! d calculateTax pi d data likeDs(b_tax_data) d tax_Ptr * procPtr const

    ComCon

    ! exec SQL insert into empa values(1, 'Paul'); check_SQLState(); ! exec SQL insert into empa values(1, 'Fred'); check_SQLState(); ! exec sql fetch next from C001 into :data ; ! if (check_SQLState()); // It was EOF endIf;

    SQL Never “Fails”

    All errors are trapped As if an (E) extender was being used

    Can use a procedure to have “unexpected” errors make the program fail Procedure doubles in checking EOF

    © ComCon & System i Developer, LLC 2005-2015 21

  • ComCon

    ! // Get last state exec SQL get diagnostics condition 1 :lastState = RETURNED_SQLSTATE; ! // All OK - just return if (status_SQL = W_SUCCESS); ! // EOF - return true - but no message elseIf (status = W_EOF); status = *on; ! // Warning - send Diagnostic message elseIf (status_SQL = W_WARNING); messageType = W_DIAGNOSTIC; exec SQL get diagnostics condition 1 :messageText = MESSAGE_TEXT; ! // Anything else - send an Escape message else; messageType = W_ESCAPE; exec SQL get diagnostics condition 1 :messageText = MESSAGE_TEXT; status = *on; ! endIf;

    SQL Exception Handler (1 of 2)

    Use GET Diagnostics to determine the “success” of the last SQL statement Set conditions for a message being sent

    ComCon

    ! if (messageType *blanks); messageText = lastState + ' ' + messageText; sendProgramMessage( W_MSGID : W_MSGF : messageText : %len(%trimr(messageText)) : messageType : W_STACK_ENTRY : W_STACK_COUNT1 : messageKey : APIError ); endif; ! return status;

    SQL Exception Handler (2 of 2)

    Sending an Escape message call the procedure to fail Which will cause an error in the calling program

    Full code in notes Article Embedded SQL Exception/Error Handling
http://www.itjungle.com/fhg/fhg040214-story01.html

    © ComCon & System i Developer, LLC 2005-2015 22

  • Prototype

    ! //**/ @desc Check SQL status code.

    // Checks the status code of the previoiusly executed SQL

    // statement and, depending on the status, will send a message

    // to the caller.

    // if the status is a warning (SQLSTATE='01nnn'/SQLCODE>0), a

    // diagnostic message is sent to the caller

    // if the status is an error(SQLSTATE='03nnn'/SQLCODE

  • // Work fields

    D messageKey s 4a

    D messageType s 10a

    D messageText s 1024a

    D status s n

    ! D DS

    D lastState 5a

    D status_SQL 2a overLay(lastState)

    ! // Constants

    D W_DIAGNOSTIC C '*DIAG'

    D W_EOF C '02'

    D W_ESCAPE C '*ESCAPE'

    D W_MSGF C 'QCPFMSG *LIBL'

    D W_MSGID C 'CPF9897'

    D W_STACK_ENTRY C '*'

    D W_STACK_COUNT1 C 1

    D W_SUCCESS C '00'

    D W_WARNING C ’01'

    /free

    // Get last state

    exec SQL

    get diagnostics condition 1 :lastState = RETURNED_SQLSTATE;

    !

    // All OK - just return

    if (status_SQL = W_SUCCESS);

    ! // EOF - return true - but no message

    elseIf (status = W_EOF);

    status = *on;

    ! // Warning - send Diagnostic message

    elseIf (status_SQL = W_WARNING);

    messageType = W_DIAGNOSTIC;

    exec SQL

    get diagnostics condition 1 :messageText = MESSAGE_TEXT;

    ! // Anything else - send an Escape message

    else;

    messageType = W_ESCAPE;

    exec SQL

    get diagnostics condition 1 :messageText = MESSAGE_TEXT;

    status = *on;

    ! endIf;

    © ComCon & System i Developer, LLC 2005-2015 24

  • if (messageType *blanks);

    messageText = lastState + ' ' + messageText;

    sendProgramMessage( W_MSGID

    : W_MSGF

    : messageText

    : %len(%trimr(messageText))

    : messageType

    : W_STACK_ENTRY

    : W_STACK_COUNT1

    : messageKey

    : APIError );

    endif;

    ! return status;

    /end-free

    P E

    ComCon

    monitor; value = value/rate; on-error; value = 0; endMon;

    Code the norm...

    Program the exception

    if (rate 0); value = value/rate; else; value = 0; endIf;

    Old RPG habits die hard

    © ComCon & System i Developer, LLC 2005-2015 25

  • ComCon

    FSalesHist IF E K Disk D salesData Ds likeRec(salesHistR) ! chain (custCode: prodCode: 'AB' + Input) salesHist salesData;

    Keys in Free Form

    Keys are easier to use in Free Form

    FSalesHist IF E K Disk D salesKey E Ds extname(salesHist:*Key) D qualified D salesData Ds likeRec(salesHistR) ! chain %KDS(salesKey) salesHist salesData;

    FSalesHist IF E K Disk D salesData Ds likeRec(salesHistR) ! C keyList KList C KFld custCode C KFld prodCode C KFld invoice C keyList chain salesHist salesData

    Regardless of whether you use fixed form or free form, file operation codes may now specify a data structure to be used with the operation. In these examples the record is placed in the SalesData data structure: the data structure must be defined using the LIKEREC keyword and it is implicitly qualified. !The first example shows the traditional fixed form method of defining a key list to be used with a file where the key consists of a number of fields. !The second example shows how a key list may be emulated in the D specs. An externally defined data structure is defined using the optional parameter of *KEY on the EXTNAME key word to indicate that only key fields from the external file are to be included in the data structure. This data structure is then used as an argument for the %KDS BIF on the CHAIN operation. This is slightly better than a key list in that the %KDS keyword makes it clear that a data structure is being used to define the key. !But the third example is the preferred way to do it. Simply provide the list of key fields to be used. You can even use a literal and/or an expression as one of the key fields!

    © ComCon & System i Developer, LLC 2005-2015 26

  • ComCon

    C MOVE '3' BARTYP 3 C MOVEL BARTYP LASTYP 3 0 C MOVE 1 LASTYP C MOVE '1' *INLR

    The “problem” with MOVE/MOVEL

    There is a problem with MOVE/MOVEL? You must know what the fields are You must know the definition of the fields

    C MOVE '3' BARTYP C MOVEL BARTYP LASTYP C MOVE 1 LASTYP C MOVE '1' *INLR

    ComConMove in Free Form Guidelines

    Character to character:- EVAL is the same as MOVEL with padding EVALR is the same as MOVE with padding Use %SUBSTR if different lengths and no padding - %substr(char10: 1: 6) = char6; // MOVEL - %substr(char10: 5: 6) = char6; // MOVE

    Number to number Use EVAL Watch out for numeric overflow - Use MONITOR if in doubt

    Number to character Use %CHAR, %EDITC, %EDITW and, if necessary, %SUBSTR - char10 = %char(number); - %substr(char10: 1: 6) = %char(number)

    Character to number Use %INT, %DEC, %DECH

    © ComCon & System i Developer, LLC 2005-2015 27

  • ComCon

    D charField 3 D numberField 3 0 inz(5) D integerField 5i 0 D dateField d D charDate 6 inz('021408') D numberDate 6 0 ! charField = %char(numberField); // charField = '5 ' charField = %editc(numberField:'3'); // charField = ' 5' charField = %editc(numberField:'X'); // charField = '005' ! integerField = %int(charField); // integerField = 5 numberField = %dec(charField: 3: 0); // numberField = 5 dateField = %date(charDate:*MDY0) ! charDate = %char(%date():*ISO0); numberDate = %dec(%date():*ISO);

    MOVE in Free Form

    Self Explanatory Don’t need to know field definitions But might take some reading!

    ComConWhat About MOVEA?

    Why was MOVEA array being used? To emulate string handling - Use string BIFs instead Multi dimensional arrays - Use data structure arrays Storing copies of data - Use %subArr() BIF

    But some MOVEA operations are more complex Make use of data structures - Overlay arrays onto character fields Write a subprocedure - Which would also make it easier to understand

    © ComCon & System i Developer, LLC 2005-2015 28

  • ComCon

    P putSource2... P B d PI ! d nextSeq s 10i 0 static /free nextSeq += 1; s2.srcSeq = nextSeq; write source2 s2; return; /end-Free P E

    Static

    The oft ignored STATIC keyword Used in subprocedure D specs

    A subprocedures memory is reallocated on every call Except for data defined with the STATIC keyword

    STATIC mean that fields... That are specific to the subprocedure And whose state needs to be maintained between calls Do NOT have to be defined globally

    ComConAlways Use Offsets!

    Many IBM supplied programs have special “buffers” APIs Triggers Usually use Data Structures in RPG

    If documentation mentions an offset – USE IT! Do NOT hard code positions

    Bad things can happen

    An example Prior to V5R1 many programmers hard coded from and to positions for before/after image fields in the trigger buffer In V5R1, IBM changed the rules for the buffer - Each image now starts on a 16 byte boundary The “lucky” ones got a decimal data error

    © ComCon & System i Developer, LLC 2005-2015 29

  • ComCon“Walking” a Large Array

    On a V5R4 system (64K maximum size) Require array of 90,000 * 10 Array dataArray()

    Each element 10 long 32767 elements Based on pointer pDataArray

    dataArray

    1...................................................................................................90,000

    1.............................327,670 327,671......................655,340655,341......................983,010

    1. numberInList = 90,000;

    2. pDataArray= %alloc((%size(dataArray)*numberInList)); ! %alloc assigns 900,000 bytes

    pDataArray = address of allocated memory

    3. Program loops through 32767 elements of dataArray

    4. “Move” the position of the array pDataArray = pDataArray + (%Size(dataArray(*)) ;

    5. etc. - but do NOT go beyond end of allocated memory

    ComCon

    D dataArray S 10a dim(32767) D based(pDataArray) ! D i S 10i 0 D j S 10i 0 D numberInList S 10i 0 inz(90000) // Allocate memory for the array pDataArray = %alloc((%size(pDataPtr) * numberInList)); // Build the array of values for i = 1 to numberInList; j = j + 1; if (j > %elem(dataArray)); pDataArray = pDataArray + (%Size(dataArray) * %Elem(dataArray)); j = 1; endIf; dataArray(j) = %char(i); endFor;

    Example of Dynamic Memory Allocation

    On a V5R4 system Limited to 32,767 elements in an array Allocate required memory The array is “repositioned” every 32767 elements

    © ComCon & System i Developer, LLC 2005-2015 30

  • ComCon

    How do you handle record locking in Interactive programs? Just lock the records without error recovery - A wealth of possible problems. Batch jobs crashing etc. Lock the records with error recovery - A better solution but do all programs have error recovery? - Bottom line with error recovery: You ain’t getting the record Do not lock the records and hope for the best - If you track the fields with have been changed by the current user and output only those fields you MIGHT be

    ok. How do you track those changes? - Your user will not be aware of concurrent changes - If you do not track changes you may overwrite data changed by another user with older screen information. Do not lock the records and program for concurrent maintenance - Best solution yet!

    Record Locking

    © ComCon & System i Developer, LLC 2005-2015 31

  • ComCon

    FmyFile UF A E K Disk FmyScreen CF E WorkStn D IOData E Ds extName(myFile) D recordImage Ds likeRec(myFileR) dim(2) chain(N) key myFile; recordImage(1) = IOData; exFmt screen; chain key myFile recordImage(2); if recordImage(1) = recordImage(2); // Or compare individual fields update myFileR; else; unLock myFile; // And do any error handling endIf;

    Logic for Record Locking

    This technique allows for Same field names on display file and database file Concurrent maintenance

    Instead of the complete row comparison Increment a counter field in the record Use a timestamp in the record

    Same Field Names

    The basic logic is as follows: - chain(N) key myFile; - the initial row is retrieved. It is automatically read into the IOData DS - recordImage(1) = IOData; - store the original image for later comparison - exFmt screen; display and read the screen. Data in IOData will be overwritten with data from the screen - chain key myFile recordImage(2); get the record again. But this time lock the record and specify a target for the

    read so the data in IOData is not overwritten - if recordImage(1) = recordImage(2); check that the row has not been changed since the program originally

    retrieved it. This can be a comple row comparison, individual columns etc. - update myFileR; or unLock myFile; depending on the results of the comparison

    © ComCon & System i Developer, LLC 2005-2015 32

  • ComCon

    There you have it! !I hope you find a few of the tips and techniques useful and applicable

    Summary

    ComConiTalk with Tuohy

    Check it out at http://www.ibmsystemsmag.com/ibmi/trends/iTALK-WITH-TUOHY/

    © ComCon & System i Developer, LLC 2005-2015 33

  • ComConBy the Speaker

    “Re-Engineering RPG Legacy Applications” ISBN 1-58347-006-9

    “The Programmers Guide to iSeries Navigator” ISBN 1-58347-047-6 www.mcpressonline.com www.midrange.com www.amazon.com etc.

    iSeries Navigator for Programmers A self teach course www.lab400.com

    Article links at www.comconadvisor.com

    © ComCon & System i Developer, LLC 2005-2015 34