Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Susan M. Gantner
Partner400
susan.gantner @ partner400.com
www.partner400.com Your partner in AS/400 and iSeries Education
(Prototypes, Compiler Directives and other features and functions you may have missed) As I travel around the
world talking to RPGers, I often find that even experienced RPG IV programmers have "missed" a few gems along
the way. This session is designed to fill those gaps. Examples? How about Prototypes? Most people know that you
need prototypes when defining and using Subprocedures, but many fail to appreciate their other uses. For
example, did you know that you can use prototypes to call PGM objects? or that a prototype will defend against
passing incorrect parameters? Or that in some cases they can take care of the differences for you? Did you know
that you can conditionally include source lines based on a parameter for the RPG compiler? A great way to
include/exclude additional code needed for testing without having to delete or comment it all out. There are many
other uses for this great feature too. How about mail-merge function? Did you know that through the combination
of two of the latest features of RPG IV that you now have an easy way of blending customer names and other
details into text templates? Additional tidbits include new performance optimization options and effective use of
pointers in RPG trigger programs. Even if you have been programming in RPG IV for some time, you will likely
learn something new in this session!
The author, Susan Gantner, is co-founder of Partner400, a firm specializing in customized education and
mentoring services for AS/400 and iSeries developers. After a 15 year career with IBM, including several years at
the Rochester and Toronto laboratories, Susan is now devoted to educating developers on techniques and
technologies to extend and modernize their applications and development environments. This is done via on-site
custom classes as well as conferences and user group events.
Together with her partner, Jon Paris, Susan authors regular technical articles for the IBM publication, eServer
Magazine, iSeries edition, and the companion electronic newsletter, iSeries Extra. You may view articles in current
and past issues and/or subscribe to the free newsletter or the magazine at: http://eservercomputing.com/iseries/.
Feel free to contact the author at: susan.gantner @ partner400.com and visit the Partner400 web site at
www.partner400.com.
We will be covering a lot of ground in this session, from prototyping C functions to Triggers to.... well
a whole bunch of stuff. Some of it will (hopefully) be new to you and some may be familiar territory.
If you have questions or comments about this presentation, please feel free to contact the authors at
the e-mail address on the front cover.
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.
All of which is the boring legal way of saying that any outbreak of plague, pestilence, flood or other
disasters apparently resulting from the use of these techniques is completely coincidental !!
The search stops when the first of these is found. Therefore, if you have specified H specs in the
data areas AND in your program, the specifications in the data area will NOT be used.
If you currently use one of the data areas to supply default H spec parameters, we strongly suggest
that you code them instead in a /COPY member and include this at the beginning of every source
member. Any additional parameters over and above the normal set can then be simply coded on H
specs following the /COPY directive.
On the H spec, if you specify multiple options, you should separate the options with a colon (:).
For example: 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).
Note: These options were made available as H spec options only via a PTF on some previous releases of the RPG
compiler. NOTE: NEITHER of these new options is the default! Default behavior is as in prior releases.
D Text S 50
The same edit codes available for use on the O specs in RPG are available via the BIF.
D Text S 50
D DueDate S D DATFMT(*MDY)
D Balance S 9 2
C Eval Text = 'Your balance of ' +
C %Char(Balance)
C + ' is due on '
C + %CHAR(DueDate)
In V4R2, %CHAR was added but it only supported date, time and timestamp data types. In V4R4, it
was enhanced to work with numeric fields as well. This makes it much easier string together
numbers with character fields using the concatenate support.
Note that if you wanted to add the dollar sign to the Balance field, you could use the %EDITC BIF
discussed on the previous chart.
Varying length fields make it easier and more efficient to concatenate character fields.
C Read Customer
C If Not %EOF(Customer)
C Eval MsgText = %Replace( %TrimR(Salutation)
C : MsgText
C : %Scan('&S':MsgText)
C : 2 )
C EndIf
To allow the program optimizer to more specifically optimize a module based on the way the users
actually use the program, you can enable application profiling. This can only be done on programs
that are Optimize(*Full) and Target Release(*Current). It can be useful in some situations,
particularly if you have many subprocedures or if you have large and complicated logic in the form of
statements such as IF/ELSE or SELECT. Based on the most popular options taken by the users
during the data collection process, the optimizer may actually choose to re-order some of the logic
blocks or re-order the subprocedures in the module to improve locality of reference.
Note that, of course, when this level of optimization is done, it can cause some interesting side
effects, especially during a source debug session if the code has been moved around in the
compiled code! Any modules with *FULL optimization can exhibit these kinds of side effects, but
those optimized with profiling data are more likely to exhibit them.
Note that profiling data can only be collected on modules where Optimize(*Full) and Target release
(*Current) have been selected.
ENBPFRCOL - This parameter tells the system to include the necessary information to allow this
module (or the modules in this program or service program) to be able to collect profiling data. It
does NOT ensure that profiling data will be collected when collection is started. To do that, you must
use the PRFDTA parameter. If you select *PEP here, statistics are kept only for entry and exit of the
PEP (program entry procedure) itself. Note that this does NOT include any RPG code -- not even
the main procedure. *ENTRYEXIT collects data on the entry and exit of all procedures in the
module (the main and any subprocedures). *FULL collects all the *ENTRYEXIT did, plus statistics
on calls to external procedures.
PRFDTA - This parameter causes data to be collected for this module (or the modules in this
program or service program) the next time profiling data collection is started. Changing this attribute
does not cause data to be collected immediately. After STRPGMPRF and ENDPGMPRF
commands have been run, this parameter on CHGPGM / CHGSRVPGM is used to apply the
profiling data to the modules for potential optimization.
STRPGMPRF - This is the command that actually begins the profiling data collection. Profiling data
will be collected for ALL programs and service programs on the system for which PRFDTA is
currently set to *COL. Note that the profiling data process puts extra workload on the system. It is
advised to collect profiling data for limited periods of time and for specific groups of programs at a
time.
In the example below, what does Part2 contain after the call ?
D TestData DS Program
D Part1 10 Inz('Test Data1') TEST1
D Part2 10 Inz('Test Data2')
C Call 'TEST2'
C Parm Part1
D Part1 S 15 Program
C *Entry PList TEST2
C Parm Part1
C Eval Part1 = *Blanks
C Return
These programs demonstrate the danger of mis-specifying a parameter in the called program.
This problem can also occur with numeric fields, but it is more likely to be obvious at the time of the
call.
With mis-sized character fields the effect may not be noticed until much later. The writer has encountered at
least one program where such an error went undetected for over 7 years!
If you have difficulty believing this, try compiling a running the two programs below.
* Program TEST1
D TestData DS
D Part1 10 Inz('Test Data1')
D Part2 10 Inz('Test Data2')
C 'In TEST1' Dsply
C TestData Dsply
C Call 'TEST2'
C Parm Part1
C 'In TEST1' Dsply
C TestData Dsply
C Eval *InLR = *On
One of the really annoying problems with RPG III is that errors with parameter lists are not
discovered until run-time. It would be much better if we could have the compiler validate the
parameter lists for us.
Prototypes were added to the RPG IV language in the V3R2 and V3R6 releases. Although their
initial purpose was to support Prototyping of Subprocedures (more on these later) the RPG
developers realized that they could be used to provide this support. As you'll see they also provided
a number of other capabilities as well.
The type of error that can result from the incorrect use of parameters can often cause RPG
programmers sleepless nights and a difficult debugging tasks.
For example:
A 20 character field was expected but only a 10 character field was passed.
The called program is modified and now moves blanks to the field instead of the 10 character field it used to
move in. This results in a corruption of data in the calling program.
A character field is expected but a signed numeric is passed.
A situation like this can continue for years since the internal representation of a character "1" is identical to
the representation of the value 1 in a signed numeric field. A programmer makes a "simple change" and
passes a different numeric parameter. Suddenly the called program starts producing incorrect output. Why?
Because the new parameter while numeric is a packed field and the representation of the digit 1 is now completely
different causing the called program to fail to recognize it correctly.
In this section we will look at some of the powerful options that prototypes give us to deal with
situations such as these.
The important thing to note is that because the compiler is now able to validate parameters, errors of
the kind described above cannot occur
D GrossPay 8S 2
D Allowances 8S 2
D TaxCalc PR ExtPgm('PX027C')
D Gross 8S 2
D Allow 8S 2
C CallP TaxCalc( GrossPay : Allowances )
In our example the prototype has been hard coded in the calling program.
Normally we would expect to see it being /COPY'd in from a source file supplied by the programmer
who wrote PX027C. It is good practice to always produce a prototype when coding a new program.
Who knows better what parameters the program is expecting? For existing programs, code the
prototype when you next do maintenance.
While it may seem like more work at first glance, it is important to remember that one of our aims is
to reduce the opportunity for errors to be introduced during maintenance.
With a prototype in place, any change in the size and/or type parameters which in a conventional
CALL/PARM situation would produce an error, with CALLP will be detected by the compiler and will
therefore never get into production.
Note that the names used for the parameters do not match the names of the fields in the CALLP
In fact they could be completely blank and the compiler would be quite happy
It only cares about the number and type of parameters - the names used are irrelevant
Some people use a standard whereby the name used in the prototype identifies the type of field
e.g. Currency, Integer, Name, etc.
D TaxCalc PR ExtPgm('PX027C')
D GrossAmount 8S 2 CONST
D AllowanceAmount 8S 2 CONST
: : :
D Gross S 7P 2
D Base S 6P 2
D PersAllow S 5P 4
: : :
C CallP TaxCalc( Gross : Base + PersAllow )
C* Eval Temp1 = Gross
C* Eval Temp2 = Base + PersAllow
C* CallP TaxCalc( Temp1 : Temp2 )
The use of the CONST keyword allows the compiler to accommodate mismatches in the definition of
the parameters between the callee and the caller.
For example, the program we are calling expects a packed decimal value of five digits with no
decimal places, but the field we would like to use as a parameter is a three digit signed numeric.
Normally we would have to create a temporary variable (packed - five digits) and move the three
digit number to it. We would then pass the temporary field as the parameter. In fact the PARM
op-code provides this support though the use of Factor 2.
When you use the CONST keyword, you are telling the compiler that it is OK for it to make a copy of
the data prior to passing it to accommodate such mismatches. This avoids the need for us to
explicitly define a working variable of the correct size and type.
Another benefit of using CONST is that it also allows an expression to be passed as a parameter.
Prototypes can also be used to call procedures in other languages, most notably C. This means that
RPG IV programs now have access to all the functions in the C function library, which is shipped as
part of OS/400 on all systems. Other system APIs, previously available only to C programmers,
such as TCP/IP sockets and direct program access to the IFS, are also enabled by the prototyping
support associated with subprocedures.
Referring back to the QCMDEXC example, you might be interested to know that through the power
of prototyping the C "system" function is available. Some people prefer this to using QCMDEXC in
that it allows a more direct determination of the sucess/failure of a command. More on this later.
D Sine PR 8F ExtProc('sin')
D Double 8F Value
D Cosine PR 8F ExtProc('cos')
D Double 8F Value
* Once the prototypes are defined, the functions can be used just
* as if they were built into RPG IV. Like this:
C Eval SineX = Sine(X)
C Eval CosineY = Cosine(Y)
C math functions include acos, asin, atan, cos, cosh, sin, sinh, tan, tanh. All accept double floating
point parameters (8F in RPG IV) passed by value, and all return a double floating point value.
It may seem to you that these kinds of math functions are rarely used in RPG programs, and you are
right. However, on those occasions when they are needed then you will really appreciate the fact
that you don't have to try and "fake" the function out in RPG.
The new RPG Redbook (referenced later in this handout) includes an example of these C functions
in action.
H BndDir('QC2LE') DftActGrp(*No)
C function calls are bound calls and can only be used from "real" ILE programs, as opposed to the
OPM compatible programs produced by DFTACTGRP(*YES). So we have to specify
DFTACTGRP(*NO)
Note that if you are compiling the program with the CRTBNDRPG command, when you first prompt
the command you will not see the Binding Directory parameter. This will not appear until you have
entered *NO for the DFTACTGRP parameter - not even if you press F10.
IBM supplies a Binding Directory for each compiler. These are used to allow the Binder to locate the
required run-time routines. The compiler automatically passes the name of this Binding Directory to
the Binder, there is no need for the programmer to specify it.
The C compiler operates in a similar fashion. The name of its Binding Directory is (you guessed it!)
QC2LE. By specifying that Directory we enable the Binder to link us to the C run-time library as well
as the RPG one.
If we want to run the program in an Activation group other than QILE we can specify the ACTGRP
parameter on the H spec.
Much simpler and safer than laying out the whole buffer
And changes in V5 render many such hard coded solutions obsolete
In fact they may not even work !!
D TrgBufferLen S 10I 0
D ORecord E DS Based(TOldPtr) ExtName(Product)
D Prefix(O_)
D NRecord E DS Based(TNewPtr) ExtName(Product)
C *ENTRY PLIST
C PARM TrgBuffer
C PARM TrgBufferLen
C Eval TOldPtr = %ADDR(TrgBuffer) + TOldOffSet
C Eval TNewPtr = %ADDR(TrgBuffer) + TNewOffSet
We haven't got the time to teach you about Triggers, but those of you familiar with them may find this
example of mapping the Before and After buffers useful.
V5R1 introduced a "small problem" for some users of triggers. Although IBM has always said that
the offsets should be used to locate the record images, many RPGers have continued to hard code
their data structures on the assumption that they would follow the 16 byte reserved area. It seems
that at V5R1 this can no longer be guaranteed. On the following pages we will show you how to do it
the safe way using pointers and the image offsets.
Through the use of pointers and based externally described data structures, as illustrated in this
example, you can make the programming to map the trigger buffer record images to record format
field names a breeze! Note that we have prefixed the field names in the data structure for the old
record image with O_ to distinguish them from the fields in the new record image.
If your records include null capable fields, you can map the null flag array in a similar fashion.
Note that this example uses pointer addition, which was introduced into the compiler at V3R7. You
could still use pointers on systems prior to V3R7, but it would require some additional code to set the
pointer addresses.
Note: Of course, the TrgBufferLen field would need to be defined on a D spec (as a 10 I) somewhere
in the program. It was omitted from this example due to space considerations. Also, note that Fields
is a named constant containing the number of fields in the record format. Unfortunately, the value of
this field must be manually set for each case. If (as in our application's case) there are no null
capable fields in the record format, you could omit all the null map information.
/IF DEFINED(TESTING)
* Insert special degugging code here
/ENDIF
/IF DEFINED(CANADA)
* Canadian tax calcs
/ELSEIF DEFINED(MEXICO)
* Mexican tax calcs
/ELSE
* USA tax calcs
/ENDIF
The examples on this chart will rely on the condition (e.g., Testing, Canada, Mexico) being set on
the compile command - either CRTBNDRPG or CRTRPGMOD.
On the next charts, we will have some examples of setting the condition inside the source member
itself.
/IF DEFINED(DSPEC)
If the member COPYCODE
D MyData ............. contains this:
D MoreData ...........
/ENDIF
/IF DEFINED(CSPEC)
C Eval MyData = MyData + 1
C Eval MoreData = 0
/ENDIF
Then the main program
/DEFINE DSPEC can do this:
/COPY CopyCode
< Only the D spec code will be copied here
/UNDEFINE DSPEC
. . .
/DEFINE CSPEC
/COPY CopyCode
< and only the C spec code here
Because RPG IV does not support the specifcation sorting feature of RPT member types, using
Conditional Compiler Directives like these provide a workaround to meet this need. It is necessary
to put 2 /Copy statements in the program (such as in the bottom example). But it does still allow you
to keep the D specs and the C specs in the same member to be copied in.
The chart above shows the relevant portion of the subprocedure source file.
When compiling this source we would not set the "OnlyProtos" condition. The compiler proceeds as
follows:
The test IF NOT DEFINED(OnlyProtos) is true (because the condition is not set) and the H-spec is
therefore included in the compile.
The second directive IF DEFINED(OnlyProtos) is not true and so the compiler ignores the lines
enclosed within the IF/ENDIF and therefore will not jump to the end of file but rather will continue
processing the source as normal.
Any program that wishes to use the subprocedures contained in the source member can do so by
including code similar to the following:
/Define OnlyProtos
/Copy sourcefilename
CGI programming
Using the C function library
ILE Error handling
and much more .... SG24-5402
Don't forget to download the source files
for the examples!
Use our link to get easy instructions on
downloading everything from IBM's site: International Technical Support Organization
www.partner400.com/RPGRedbook.htm Rochester, Minnesota