Midrange News for the IBM i Community

Named Constants and Finding your IP Address in RPG IV Published by: Bob Cozzi on 16 Aug 2011 view comments(3)
© 2011 Robert Cozzi, Jr. All rights reserved.

© Robert Cozzi, Jr. All rights reserved. Reproduction/Redistribution prohibited.
A midrangeNews.com Publication

Named Constants on Steroids

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.

Sponsored by: CNX Corp. Valence 3.0

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
          fd = openIFS('myfile.txt' : ifs.O_RDONLY);
          closeIFS( fd );

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


Finding the IP Address of a Host Name

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:

  • gethostbyname
  • inet_ntoa

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 Copyright('RPG OPEN - (c) 2009 Robert Cozzi, Jr. All rights reserved.')
      /IF NOT DEFINED(*V6R1M0)
     H BNDDIR('QC2LE')

      /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
           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.');
              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 :
           nIP = h_Addr;
           return  dottedIP;
     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

Return to midrangenews.com home page.
Sort Ascend | Descend


(Sign in to Post a Comment)
Posted by: DaleB
Premium member *
Reading, PA
Comment on: RPG Report - 16 August 2011
Posted: 8 years 7 months 18 days 15 hours 3 minutes ago
Edited: Thu, 18 Aug, 2011 at 12:14:46 (3151 days ago)

Continuing the workaround for the "constants" in the qualified DS, you could do a Reset IFS; if you have any question whether the DS has been modified or not.

Posted by: dougcmh
Premium member *
Columbus, OH
Comment on: RPG Report - 16 August 2011
Posted: 8 years 7 months 17 days 22 hours 51 minutes ago
Edited: Thu, 18 Aug, 2011 at 12:14:46 (3151 days ago)

Sometimes, using variables and subfields rather than NCs (qualified or not), is necessary - You can't use NCs as host variables in embedded SQL as you can with variables and subfields.

That is another NC enhancement I'd like to see - the ability to use NCs in embedded SQL.

Posted by: bobcozzi
Site Admin ****
Comment on: RPG Report - 16 August 2011
Posted: 8 years 7 months 16 days 13 hours 35 minutes ago

I think having variables (any type) with a READONLY keyword associated with them fixes everything. Assuming that it would allow the variables to continue to be used where ever a variable can be used today--including embedded SQL.