When IBM added Named Constants support to RPGIII it retrofit that
specification into the legacy Input Specs of RPGIII. Then, in RPG IV
they migrated Named Constants to the Definition specification. This made
them easier to declare and read. But I want more!
For years we've been asking IBM to improve Named Constant support to
include data-typing. Why not have the ability to declare a constant that is
a date value, a numeric value, or a character value? As I learned, early on,
it turns out they already
have that kind of support built-in. For example:
.....D true C Const(*ON)
D false C Const(*OFF)
D YES C Const('Y')
D NO C Const('N')
D biCentennial C Const(D'1976-07-04')
D MAXFILES C Const(32)
In this example, the TRUE and FALSE named constants are logical data-types, the YES, NO constants are character data-types, the
BICENTENNIAL constant is a DATE data-type, and the MAXFILES is numeric. So
while we do not have explicitly typed constants, we do have implicitly
"typed" constants.
Qualified Constants
More recently I began to realize that we RPG developers
needed something more than simple named constants; we need Qualified
Named Constants.
By the term Qualified Named Constants I mean the ability to declare a named
constant within a parent class. The closest thing RPG IV has to this is
Qualified Data Structure subfields. When I spoke with IBM about adding this
feature I used an example similar to the following to illustrate this
feature.
.....D IFS NC Qualified
D O_RDONLY Const(1)
D O_WRONLY Const(2)
D O_RDWR Const(4)
D O_CREAT Const(8)
D O_EXCL Const(16)
D O_CCSID Const(32)
D O_TRUNC Const(64)
D O_APPEND Const(256)
The container is named "IFS". This container contains named constants. To
access them, you would use the same qualified syntax as Qualified Data
Structures:
fd = open('myfile.txt' : IFS.O_RDONLY);
Note the "IFS." prefix on the O_RDONLY symbol. This can greatly help avoid
naming conflicts with things like 3rd-party software, RPG IV add-ons (such
as RPGOpen or
RPG xTools) and stuff you find in this newsletter.
While IBM clearly understands this requirement, I don't know if we will see
it anytime soon. In the mean time, I realized by looking at my own example
implementation that we may already have something nearly as good as
Qualified Named Constants. If I tweak my "NC" declaration and turn it
into a Data Structure, I end up with this:
.....D IFS DS Qualified Template
D R_OK 10I 0 Inz(4)
D W_OK 10I 0 Inz(2)
D X_OK 10I 0 Inz(1)
D F_OK 10I 0 Inz(0)
D SEEK_SET 10I 0 Inz(0)
D SEEK_CUR 10I 0 Inz(1)
D SEEK_END 10I 0 Inz(2)
D O_RDONLY 10I 0 Inz(1)
D O_WRONLY 10I 0 Inz(2)
D O_RDWR 10I 0 Inz(4)
D O_CREAT 10I 0 Inz(8)
D O_EXCL 10I 0 Inz(16)
D O_CCSID 10I 0 Inz(32)
D O_TRUNC 10I 0 Inz(64)
D O_APPEND 10I 0 Inz(256)
What I've done here is to create a classic Qualified Data Structure
Template with
subfield names matching what were my Named Constant names--replacing the
named constant declarations with subfield declarations. Now when I need to use an IFS symbol, such as O_RDONLY, I
can use the same syntax I specified in my requirement to IBM for a Qualified
Named Constants feature, for example:
fd = open('myfile.txt' : IFS.O_RDONLY);
Unfortunately, when this is compiled, the following errors are generated:
*RNF0655 30 297 002200 Item IFS was defined with TEMPLATE and cannot be used in
this context.
*RNF0655 30 297 002200 Item O_RDONLY was defined with TEMPLATE and cannot be use
in this context.
Even though we are, in fact, using IFS.O_RDONLY as a read-only value, the TEMPLATE
keyword/feature was not intended to be used in this manner, so it generates those
two "cannot be used" errors.
Qualified Named Constant Work-Around
To work around this problem, the TEMPLATE keyword needs to be removed
from the IFS data structure. The IFS data structure declaration line appears
as follows:
.....D IFS DS Qualified
Now when we compile the previous example, the IFS open() statement compiles
just fine.
The issue when NOT using TEMPLATE is that the IFS data structure can be
changed at runtime. But in practice, I find
that virtually never happens. For now, it is a good enough work
around, but Read-Only Data Structures and read-only variables would be a
welcome addition to the RPG IV language.
Here's a complete working example that successfully compiles and runs my v7r1 system,
however it will compile and run on v5r1 or later.
.....H dftactgrp(*NO)
/include rpgopen/qcpysrc,ifsProtos
D IFS DS Qualified
D R_OK 10I 0 Inz(4)
D W_OK 10I 0 Inz(2)
D X_OK 10I 0 Inz(1)
D F_OK 10I 0 Inz(0)
D SEEK_SET 10I 0 Inz(0)
D SEEK_CUR 10I 0 Inz(1)
D SEEK_END 10I 0 Inz(2)
D O_RDONLY 10I 0 Inz(1)
D O_WRONLY 10I 0 Inz(2)
D O_RDWR 10I 0 Inz(4)
D O_CREAT 10I 0 Inz(8)
D O_EXCL 10I 0 Inz(16)
D O_CCSID 10I 0 Inz(32)
D O_TRUNC 10I 0 Inz(64)
D O_APPEND 10I 0 Inz(256)
D fd S 10I 0
C eval *INLR = *ON
/free
fd = openIFS('myfile.txt' : ifs.O_RDONLY);
closeIFS( fd );
return;
/end-free
If you are a MidrangeNews.com member, you can download a copy of my free RPG Open service program
(used in this example) by visiting the service program's website at:
www.RPGOpen.com
Recently I had a client who was using some legacy skills to connect to
IBM i systems running on their network. To identify the systems in their network,
they correctly adding their names and IP addresses to the HOSTS table using CFGTCP option 10.
However, in their applications (such as when they were using FTP) rather than refer to the symbolic name used in the
HOSTS table, they would read the IP address out of the host table (manually)
and then hard-coded the IP address for each of their 8 to 10 remote systems
into the source code.
Upon analyzing their code, I realized that not only did they hard-code
the IP addresses using Named Constants, they did this in each and ever source
member that needed the IP address. If the IP ever changed they had to have
programmers go into the source code, change it and then recompile the source
code. PDM's FIND option was being used a lot; like I said "Legacy Skills" in
action.
To make things worse, in some applications they would actually code the
symbolic name (the one in the HOSTS table) into the RPG code and create a
compile-time array with that symbolic name and the IP address. Then at
runtime retrieve the IP using the symbolic name. Effectively hard-coding
something that the HOSTS table provides--no I didn't pull out all
my hair, but nearly all of it.
Of course now that they've brought me into the situation, their ISP and
therefore their IP addresses are changing. Of course I had to solve this
situation by providing the least impact to existing code. So rather than
rewrite everything, I wanted to simply replace the compile-time table
look-up and hard-coded IP addresses with a SOCKETS API call. I know, SOCKETS
programming is complex, but remember, we do have the power of subprocedures
to hide any complexity. Besides, as it turns out, we only really need
to SOCKETS API calls to make this work in RPG IV:
The first API returns a pointer to a data structure with the following
format:
.....D hostent DS Qualified Based(pHostent)
D h_name *
D h_aliases *
D h_addrtype 5I 0
D h_length 5I 0
D h_addr_list *
The second API dereferences the h_addr_list member variable and returns
its dotted IP address. I created a wrapper for these two APIs and named it GetHostIP.
(Actually I simply pulled the code from my free SOCKETS library named
ISOCKETS which has been available for free, for years at
www.iSockets.net)
To use GetHostIP, pass it
something like "google.com" and it returns the IP address for that domain.
It will also, and this is why it will work for my clients legacy systems,
return an IP from the HOSTS table on your system. If you have a remote
system with a HOSTS table entry name of "DEVSYS" (for Development System)
you can specify getHostIP('DEVSYS') and it will return that system's
IP address from the HOSTS table. So now I can easily go into legacy code and
replace the hard coded IP address or the compile-time table lookup with a
single call to GETHOSTIP.
I've added GETHOSTIP to my free RPGOPEN service program. You can
downloading the latest release
of RPG Open from this link. To use it, simply include the /COPY or
/INCLUDE directive for the prototype source member in QCPYSRC. That member
name is GETHOSTIP. The entire GETHOSTIP subprocedure
source member is reproduced here for your reference.
.....H NOMAIN OPTION(*NODEBUGIO:*SRCSTMT)
H Copyright('RPG OPEN - (c) 2009 Robert Cozzi, Jr. All rights reserved.')
/IF NOT DEFINED(*V6R1M0)
H BNDDIR('QC2LE')
/ENDIF
/include RPGOPEN/QCPYSRC,gethostip
/include RPGOPEN/QCPYSRC,joblog
D inet_ntoa PR * extProc('inet_ntoa')
D nIntIPAddr 10U 0 Value
D getHostByName PR * extProc('gethostbyname')
D szHostName * Value OPTIONS(*STRING)
D inet_addr PR 10U 0 extProc('inet_addr')
D pIPV4 * Value OPTIONS(*STRING)
// NOTE: The gethostbyname returns errors via __h_errno instead
// of the C runtime __errno. So we map that API to our
// errno prototype here.
D errno PR * extProc('__h_errno')
D strerror PR * extProc('strerror')
D errno 10I 0 Value
D SOCKERR DS Qualified
D HOST_NOT_FOUND 10I 0 Inz(5)
D NO_DATA 10I 0 Inz(10)
D NO_ADDRESS 10I 0 Inz(10)
D NO_RECOVERY 10I 0 Inz(15)
D TRY_AGAIN 10I 0 Inz(20)
P GetHostIP B Export
D GetHostIP PI 15A
D hostName 255A Const Varying
D hostEnt DS Qualified Based(pHostEnt)
D h_name *
D h_aliases *
D h_addrtype 5I 0
D h_length 5I 0
D h_addr_list *
// Pointer to pointer crap
D pDotted S *
D dottedIP S 15A Based(pDotted)
D ph_aliases S * Based(hostEnt.h_aliases)
D h_aliase S 10U 0 Based(ph_aliases)
D ph_addr_list S * Based(hostEnt.h_addr_list)
D h_addr S 10U 0 Based(ph_addr_list)
D nErrNo S 10I 0 Based(pErrNo)
D nLen S 10I 0
D nIP S 10U 0
/free
pHostent=gethostbyname( %trimR(hostName) );
if (pHostent = *NULL); /If it failed, log the error.
pErrNo = errno();
joblog('gethostbyname failed. %s not found. +
errno(%s) %s': hostName :
%char(nErrNo): %str(strerror(nErrNo)));
if (nErrNo=SOCKERR.HOST_NOT_FOUND);
joblog('Host not found.');
elseif (nErrNo=SOCKERR.NO_DATA);
joblog('No data.');
elseif (nErrNo=SOCKERR.NO_ADDRESS);
joblog('No Address.');
elseif (nErrNo=SOCKERR.NO_RECOVERY);
joblog('No Recovery.');
elseif (nErrNo=SOCKERR.TRY_AGAIN);
joblog('Try again later.');
endif;
return '';
else; // It worked? Great, let's get the IP Address
pDotted = inet_ntoa(h_Addr);
// NOTE: In production, you may remove this joblog entry.
joblog('gethostbyname(%s) returned %s': hostName :
%str(pDotted));
endif;
nIP = h_Addr;
return dottedIP;
/end-free
P GetHostIP E
Call Me
Bob Cozzi has been providing the solutions to midrange problems,
in the form or articles and books since 1983. He is available
for consulting/contract development or on-site RPG IV, SQL, and CGI/Web training.
Currently many shops are contracting with Cozzi for 1
to 3 days of Q&A and consulting with their RPG staff. Your staff gets to ask real-world
questions that apply to their unique development situations. To contact Cozzi,
send an email to: bob at rpgworld.com
Bob also accepts your questions for use in future RPG Report
articles or content for midrangeNews.com. Topics of interest include: RPG IV,
Web development with RPG IV, APIs, C/C++ or anything else IBM i development related (except subfiles,
data areas and RPGII/III because Bob doesn't care about that stuff) write
your questions
using the COMMENTS link at the bottom of any RPG Report article on the midrangeNews.com website.
You can subscribe to RPG Report (we call it "follow") by
visiting the RPG Report
page
on midrangeNews.com and then click the FOLLOW link in the table of contents
for that
page. To unsubscribe, simply click that same link. You must be
signed up and signed
in to midrangeNews.com to start following RPG Report.
Follow Bob Cozzi on Twitter