[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 905
  • Last Modified:

print list from cl

Using CL:
I have obtained a list of connections using the QtosLstNetCnn api.  How do I loop through this list of values and output the list to a print file?  I have done  "dmpobj     mylib/myprog  *usrspc " command, but the list is unreadable to me (be kind, I'm a newbie here).
  • 10
  • 8
2 Solutions
Hi dh
You don't want to use dmpobj.
After you retrieve the list into a user space, you can retreive the entries in your CL pgm. The sample code below retrieves the number of entries. It also retrieves the length of each entry and the starting point of the list of entries. You can loop through the list and do what ever you need.

DCL &NBRENT    *DEC  (8 0)  
DCL &BIN4      *CHAR   4    
DCL &ENTLENB   *CHAR   4    
DCL &ENTLEN    *DEC  (8 0)  
DCL &POINTER   *DEC  (8 0)  
/* GET NUMBER OF ENTRIES */                                            
             CALL QUSRTVUS ( &USRSPCQ X'00000085' X'00000004' &BIN4)  
             CHGVAR &NBRENT %BINARY(&BIN4)                            
             IF (&NBRENT = 0 ) THEN(GOTO ENDPGM)                      
/* GET LIST ENTRY LENGTH AND OFFSET */                                
             CALL QUSRTVUS ( &USRSPCQ X'00000089' X'00000004' &ENTLENB)
             CHGVAR &ENTLEN   %BINARY(&ENTLENB)                        
             CALL QUSRTVUS ( &USRSPCQ X'0000007D' X'00000004' &BIN4)  
             CHGVAR &POINTER  %BINARY(&BIN4)                          
             CHGVAR &POINTER ( &POINTER + 1)                          
             CHGVAR %BINARY(&BIN4) &POINTER                  

<insert your code specific to this API >

            IF ( &NBRENT > 1) THEN(DO)                      
               CHGVAR &NBRENT  ( &NBRENT - 1)                
               CHGVAR &POINTER ( &POINTER + &ENTLEN )        
               GOTO NEXTENTRY                                


This might have been reasonable as a continuation of the earlier question though it does branch into a separate area -- "printing" from CL.

The direct answer is that CL is not "programming" language as is generally thought of. CL is a "control" language. I.e., DOS .BAT files consist of DOS "control" language statements; creating a DOS report generally requires having DOS call a program designed to format and print output. From that, the answer continues as "CL doesn't 'print'. C prints; COBOL prints; RPG prints -- CL calls programs that print."

Now, with that in mind, the question might be changed to be something like "If all I know is CL, can I format a list in a *usrspc out to a spooled file for printing?"

And that question can be answered "Yes, but it's not easy."

First, some other info needs to be supplied --

1. What version of i5/OS (or OS/400) are you running? Later releases have added capabilities. The latest essentially makes the entire library of C functions available to CL.

2. Do you generally feel comfortable writing CL programs and procedures?

3. Are you comfortable with binding multiple modules together to create an executeable program object?

4. Are you familiar with any other languages that are available on your system? (Obviously this could lead to much easier programming?)

From there, we'll see how it goes.

dhenderson12Author Commented:
Thank you both. Barry, I'm going to try your suggestion this afternoon.  As to the questions Tom asks:
1. I have two iSeries machines, one at V5R3 and one at V5R4.  I don't mind writing to the V5R4 machine because of the extras CL offers there and the fact that I'm using CL for prototyping.

2. I fairly comfortable with CL ... I'm trying to learn RPG at the moment, but I find CL usefull for trying things.
3. Creating executables from object modules is not a problem.  I have done a little C++ in windows.  I know CL isn't a "programming" language, but I've read that it just keeps getting more capable every day.

Anyway, I want to be able to do this stuff in CL first, as I said, for prototyping at least, since I'm a bit more comfortable with CL than with RPG.
Visualize your virtual and backup environments

Create well-organized and polished visualizations of your virtual and backup environments when planning VMware vSphere, Microsoft Hyper-V or Veeam deployments. It helps you to gain better visibility and valuable business insights.


Barry's help is always good and reliable, and he might add more before I have time. Don't hesitate to try whatever he suggests. I won't have much time for at least a few more hours. All I can add for now are some general comments for more background.

If your systems are well patched with recent PTFs, creating the 'print from CL' functions will be better done on the V5R4 system. The specific V5R4 enhancements that will be most useful _should_ compile and run on the older V5R3. IBM did a pretty good job of allowing the newest enhancements to work even back to V5R2 as long as V5R4 compiles them. That was a nice trick.

One more question area -- Are familiar with the Qshell (UNIX environment) on your systems? Do you have the Qshell option installed on them?

Qshell is a free option that includes a bunch of UNIX utilities. It includes a full-blown shell scripting environment (the qsh shell) that can extend CL very nicely. CL makes a great prototyping language (IMO); Qshell adds immensely to what you can do both quickly and easily.

When I have some time (and if an answer isn't already here), I'll see what I can provide for a 'print from CL' example. I'll try to do it as a proc that can be used multiple times.

dhenderson12Author Commented:
Yes, qshell is available to me. Thanks, Tom.

Barry - I tried your code and am getting an invalid reference to your var &ENTRY (it's called but not declared ... don't know what this should be). You are calling it after the NEXTENTRY: label in this line of code:  CALL QUSRTVUS ( &USRSPCQ &BIN4 &ENTLENB &ENTRY) .

  dcl  &entry     *char 1024

Now, that might seem too big (and it is for the entries in this list), but CL has a difficult time declaring variables dynamically. I _think_ it can be done in V5R4 with the enhancements by calling pretty much the same C functions to allocate memory and get a pointer to it, but you might as well just declare a block of bytes and be done with it. This really isn't a function that's dying for microseconds of performance/speed/efficiency.

Ideally, you'd look at the API documentation for the structure of the list entries and then DCL a variable big enough to hold an entry. But for a start, you can also simply DCL one big enough to handle almost any entry for any list that any API will every produce. You can fine-tune as time goes by, but a big entry will be available for copying into the next program you write that processes a different API list.

dhenderson12Author Commented:
ok, thanks.  It seems to work however .....
I am using %SST to parse out the values.  The NetCnnType is comming out as the ip address ( No other values are displayed, and after the first record it actually kicks me out of my session by disconnecting.  I am including my code below, which is a composite of Barry and Tom code.  I also added a display file so that I could "walk" through the loop to see what came out.

=============================[ code ]=============================================


             DCLF       DAN/GETPORT

             dcl        &usrspc      *char     20     value( 'GETPORT   +
                          XDAN      ')
             dcl        &qcus_exatr  *char     10     value('          ')

             DCL        VAR(&QCUS_SIZE) TYPE(*CHAR) LEN(4) +
             DCL        VAR(&QCUS_INIT) TYPE(*CHAR) LEN(1) VALUE(X'00')
             DCL        VAR(&QCUS_PUBA) TYPE(*CHAR) LEN(10) VALUE('*ALL')
             DCL        VAR(&QCUS_TEXT) TYPE(*CHAR) LEN(50)
             DCL        VAR(&QCUS_REPL) TYPE(*CHAR) LEN(10) VALUE('*YES')
             DCL        VAR(&QCUS_DOMN) TYPE(*CHAR) LEN(10) +
                          VALUE('*DEFAULT  ')

             DCL        VAR(&QERRCD) TYPE(*CHAR) LEN(96)

             dcl        &qcnnlst     *char   1024
             DCL        &ENTRY   *CHAR 100

             dcl        &NetCnnTyp   *char     10     value( '*TCP' )
             dcl        &LstReqTyp   *char     10     value( '*ALL' )
             dcl        &reserved    *char     12     value( +
                          x'000000000000000000000000' )
             dcl        &LclLwrIP    *char      4     value( x'00000000' )
             dcl        &LclUprIP    *char      4     value( x'00000000' )
             dcl        &LclLwrPrt   *char      4     value( x'00000000' )
             dcl        &LclUprPrt   *char      4     value( x'00000000' )
             dcl        &RmtLwrIP    *char      4     value( x'00000000' )
             dcl        &RmtUprIP    *char      4     value( x'00000000' )
             dcl        &RmtLwrPrt   *char      4     value( x'00000000' )
             dcl        &RmtUprPrt   *char      4     value( x'00000000' )

             DCL        &NBRENT      *DEC      (8 0)
             DCL        &BIN4        *CHAR      4
             DCL        &ENTLENB     *CHAR      4     value( x'00000000')
             DCL        &ENTLEN      *DEC      (8 0)
             DCL        &POINTER     *DEC      (8 0)

             CHGVAR     VAR(%SST(&QERRCD 1 8)) VALUE(X'0000006000000000')


             IF         COND(&IN03 = '1') THEN(DO)
                GOTO       CMDLBL(END)

             CALL       PGM(QUSCRTUS) PARM(&usrspc &qcus_exatr &QCUS_SIZE +
                          &QCUS_INIT &QCUS_PUBA &QCUS_TEXT &QCUS_REPL +
                          &QERRCD &QCUS_DOMN  )

             chgvar     var(&qcnnlst) value(&NetCnnTyp *cat &LstReqTyp *cat +
                          &reserved *cat &LclLwrIP *cat &LclUprIP *cat +
                          &LclLwrPrt *cat &LclUprPrt *cat &RmtLwrIP *cat +
                          &RmtUprIP *cat &RmtLwrPrt *cat &RmtUprPrt )

             callprc    'QtocLstNetCnn' (&usrspc 'NCNN0100' &qcnnlst +
                          x'00000040' 'NCLQ0100' x'00000000' )

             CALL       QUSRTVUS ( &usrspc X'00000085' X'00000004' &BIN4)

             CHGVAR     &NBRENT %BINARY(&BIN4)

             IF         (&NBRENT = 0 ) THEN(GOTO END)

             CALL       QUSRTVUS ( &usrspc X'00000089' X'00000004' +

             CHGVAR     &ENTLEN   %BINARY(&ENTLENB)
             CALL       QUSRTVUS ( &usrspc X'0000007D' X'00000004' &BIN4)

             CHGVAR     &POINTER  %BINARY(&BIN4)
             CHGVAR     &POINTER ( &POINTER + 1)

             CHGVAR     %BINARY(&BIN4) &POINTER

             CALL       QUSRTVUS ( &usrspc &BIN4 &ENTLENB &ENTRY)

             /*<insert    your code specific to this API >*/
             CHGVAR     VAR(&NETTYPE) VALUE(%SST(&ENTRY 1 10))
             CHGVAR     VAR(&REQTYPE) VALUE(%SST(&ENTRY 11 10))
             CHGVAR     VAR(&LLOIP) VALUE(%SST(&ENTRY 33 4))
             CHGVAR     VAR(&LHIIP) VALUE(%SST(&ENTRY 37 4))
             CHGVAR     VAR(&LLOPORT) VALUE(%SST(&ENTRY 41 4))
             CHGVAR     VAR(&LHIPORT) VALUE(%SST(&ENTRY 45 4))
             CHGVAR     VAR(&RLOIP) VALUE(%SST(&ENTRY 49 4))
             CHGVAR     VAR(&RHIIP) VALUE(%SST(&ENTRY 53 4))
             CHGVAR     VAR(&RLOPORT) VALUE(%SST(&ENTRY 57 4))
             CHGVAR     VAR(&RHIPORT) VALUE(%SST(&ENTRY 61 4))


                IF         COND(&IN03 = '1') THEN(DO)
                   GOTO       CMDLBL(END)

                if         COND(&IN10 = '1') THEN(DO)
                   CHGVAR     VAR(&IN10) VALUE('0')
                   goto       cmdlbl(continue)


             IF         ( &NBRENT > 1) THEN(DO)
                CHGVAR     &NBRENT  ( &NBRENT - 1)
                CHGVAR     &POINTER ( &POINTER + &ENTLEN )

                GOTO       NEXTENTRY

            /* dmpobj     xdan/getport  *usrspc*/

            /* dmpclpgm */

             chgvar     var(&IODONE) value('Done')

 wait:       sndrcvf
             IF         COND(&IN03 = '1') THEN(DO)
                GOTO       CMDLBL(END)

             goto       cmdlbl(wait)

dhenderson12Author Commented:
sorry, here is the code for the display file:

     A                                      DSPSIZ(24 80 *DS3)
     A          R MENUFM
     A                                      CF03(03)
     A                                      CF10(10)
     A                                      FRCDTA
     A                                  1  2DATE
     A                                      EDTCDE(Y)
     A                                  1 14TIME
     A                                  1 29'Get Port Number'
     A                                      DSPATR(UL)
     A                                  1 57SYSNAME
     A                                  7  9'Enter to Start'
     A                                      COLOR(RED)
     A                                 10 10'NETTYPE'
     A                                 11 10'REQTYPE'
     A                                 12 10'LLOIP'
     A                                 13 10'LHIIP'
     A                                 14 10'LLOPORT'
     A                                 15 10'LHIPORT'
     A                                 16 10'RLOIP'
     A                                 17 10'RHIIP'
     A                                 18 10'RLOPORT'
     A                                 19 10'RHIPORT'

     A            NETTYPE       10A  B 10 20
     A            REQTYPE       10A  B 11 20
     A            LLOIP         10A  B 12 20
     A            LHIIP         10A  B 13 20
     A            LLOPORT       10A  B 14 20
     A            LHIPORT       10A  B 15 20
     A            RLOIP         10A  B 16 20
     A            RHIIP         10A  B 17 20
     A            RLOPORT       10A  B 18 20
     A            RHIPORT       10A  B 19 20
     A            IODONE        10A  B 19 35

------ Begin paste



   dcl   &fs  *char  80
   dcl   &fs1 *char  80   value( '%1.10s %.5s %1.10s %1.20s' )
   dcl   &rc  *int    4   value( 0 )
   dcl   &nl  *char   1   value( x'15' )
   dcl   &nul *char   1   value( x'00' )
   dcl   &p1  *char  10   value( 'DHENDERSON' )
   dcl   &p2  *char  10   value( 'Barry' )
   dcl   &p3  *char  10   value( 'for help' )

   ovrprtf  stdout  qsysprt +
              dfrwrt( *NO ) +
              splfname( PRINTDEMO ) +
              ovrscope( *ACTGRPDFN ) +
              opnscope( *ACTGRPDFN )

   chgvar         &fs       ( 'Here, ' *cat &fs1 *tcat &nl *cat &nul )

   chgvar         &p2       ( &p2  *bcat &nul )
   chgvar         &p3       ( &p3  *bcat &nul )

   callprc  'printf'  ( +
                        &fs     +
                        &p1     +
                        'asked' +
                        &p2     +
                        &p3     +
                      ) +
                rtnval( &rc )
   callprc  'printf'  ( +
                        &fs     +
                        &p1     +
                        'asked' +
                        &p2     +
                        &p3     +
                      ) +
                rtnval( &rc )

   dltovr   stdout


------ End paste

The above sample is just about the simplest "real" print program I can think of in CL. It might make semi-sense to you even if you aren't certain of all the details. It's written as a trivial stand-alone module that's intended to bound into a stand-alone program.

Compile the module, then run the CRTPGM command. I ended up with the name TSTBIN for both the module and the program, but you can name them whatever you want. I also used the name TSTBIN for the "activation group", but that was purely for convenience.

Once compiled, call the program. When it finishes a second later, run the RCLACTGRP command. Then, do a WRKJOB and look at the spooled files. You should find a spooled file named PRINTDEMO with two printed lines that say:

 DHENDERSON asked Barry  for help
 DHENDERSON asked Barry  for help

If you try to look before running the RCLACTGRP command, you should see the spooled file is still open. Until the TSTBIN activation group is reclaimed, the spooled file won't close.

If you know how to use printf, you might be able to work out pretty much everything else that you need to do to get printing to work as you want.

As far as your latest problem goes, the &entry variable actually contains a data structure with a number of members (fields). Some of those fields contain binary data. If you try to display binary values out to a display file, you're likely to send display control characters that can really interfere with the screen functions.

Before sending a binary value out, you'll need to convert it to displayable characters. The %sst() builtin function will extract substrings, but it doesn't do conversions between binary and character. It simply treats everything as character. To do binary/character conversions, use the %bin() BIF instead of %sst().

According to the docs, the first 15 bytes of an entry contain the remote IP address in dotted-decimal format. That can be extracted:

   dcl   &RmtIPAddr  *char  15
   chgvar   &RmtIPAdr   %sst( &entry  1  15 )

That one's easy. At offset 40 of the entry, there's a BIN(4) called 'Remote port'. CL refers to "positions" rather than "offsets", so it starts in position 41, which is position 1 offset by 40 bytes, and goes for 4 bytes:

   dcl   &RmtPort   *int  4
   chgvar   &RmtPort   %bin( &entry  41  4 )

Once the program knows you're working with an *int, it's much easier to convert to displayable characters --

   dcl   &dRmtPort   *char  6  /* Maybe too small, but Port should be <64K */
   chgvar   &dRmtPort   &RmtPort

&RmtPort was extracted with %bin(). This chgvar converted the *int numeric value into characters. Now &dRmtPort is a displayable version of &RmtPort.

You'll need to pay close attention to the structure offsets and types in the NCNN0100 format. The examples for IP address and remote port should guide you.

You _might_ want to comment out your [GOTO  NEXTENTRY] line and uncomment the DMPCLPGM command at least once. That will cause your program to retrieve just the first entry from the list and to extract the values out of the entry; then you'll get a formatted dump. You'll be able to compare the formatted values in each variable against the hex values in &entry to see if you're getting what you expect. If your offsets are counted wrong or your data types are wrong, the dump should make it visibly obvious.

So far, that's not a bad example of a CL program you stuck together. Things might be on the verge of getting interesting for you.

You wouldn't happen to be familiar with REXX? All AS/400s also come with REXX as a procedures language.

Added note... The CRTPGM specifies BNDDIR( QC2LE ) in order to let the binder know that we're pulling a C library function in. That's where printf is found. A CL compile needs to be told.

dhenderson12Author Commented:
I'm getting an error (mch1202), described as a decimal data error.  This error occurs when I try to access the next record in the list.  The line of code causing this is:

Both of these are defined as *DEC (8 0).  I always get the first record, but nothing after that.

In the code that I can see posted so far, there doesn't seem to be any problem. Please copy/paste every line the affects either &POINTER or &ENTLEN. Somewhere, one of those two variables is getting corrupted.

There is only one _likely_ way that's happening -- a CALL or CALLPRC is specifying a parameter value that allows the called program or procedure to overlay one of those variables.

For example, the call to QUSRTVUS might be retrieving a block of bytes that's longer than the variable that you're retrieving into. If you attempt to retrieve into a variable that's 20 bytes long and you pass in a value that tells the API to retrieve 50 bytes, then the 30 bytes in memory will be corrupted after the variable you're trying to set.

Converting binary values to decimal and/or character and handling offsets can easily mess a value up. The subsequent call to an API then causes a corruption in some unrelated variable. The corruption shows up at some later point and can be tough to track down.

dhenderson12Author Commented:
I found the problem ... it was a typo on my part.  I can now keep testing.  Thanks.
dhenderson12Author Commented:
This may be a bit off-topic, but data types and conversions ....
1. the local and remote port numbers are defined as 4-byte binary.
2. I do as suggested, converting the bin data to integer.
3. When the data is displayed (converted from integer to char) I see the following two values:
            local port:  0000000000000000000000000
            remote port:  -000000000000000263458741
what am I missing?  How would I display the port number correctly?
dhenderson12Author Commented:
I think I see what's happening.  It's got nothing to do with data conversion, as such.  If I compare the output of my api call and the screen from "work with tcp connection status" the ports are being reported out correctly, if they are numeric.  If they are listed as "ldap" or "cifs" or "netbios" then the wierd long number is displayed.  

I'm not sure how to handle this situation.  Any suggestions are appreciated.

Unless someone has direct experience, you might have a while to wait on an answer.

Note that port numbers should generally be positive integers. In CL, an integer variable is declared as type *INT, an unsigned is *UINT. Integer variables in CL are allowed to be either 2-bytes (16-bit) or 4-bytes (32-bits) in length.

Earlier CL had no integer types. The only way to work with integers was to store them in 2- or 4-byte character variables and perform numeric conversions to or from decimal variables with the %BIN() BIF.

If you declare your various port variables like:

  dcl   &port   *uint  4

...perhaps the difference between signed and unsigned might shed some light.

You'll probably need to declare port variables separately from any display fields. Before you attempt to display the values, use CHGVAR to copy the values into a numeric display field. Be sure the sizes of display fields allow for the largest values.

The three services you listed are a bit different from services such as telnet. Quite probably they are communicating over internal sockets that might have unusual port numbers.

In any case, interpretation of the port values is a specialized area. If you have IBM support, it might be worth calling. I probably would. Many support contracts cover "usability" questions. They'll say if the question has to move to Consult Line.

dhenderson12Author Commented:
Thank you both for your help.  I gave points to Barry for his looping answer, but Tom got the majority due to the amount of help.  Both of you have pushed me far ahead of where I started.  If I had more points to give, believe me I would.  Thanks again.
dhenderson12Author Commented:
As a follow up:  I found the problem with the port number being returned as a large negative number ... I was grabbing the wrong field from the returned structure.  Guess I was getting a little bit punch-drunk on this thing.  Any way, all is working.  Thanks again.

This is a possible place where a procedure would be useful. Create a separate proc that does nothing but extract an entry and parse the structure. Once perfected, use CALLPRC to access it the next time. Handling offsets and referencing elements of a structure can get off track by simple typos.

Another possibility is to extend CL by creating a command that returns the entry values. That would be similar to a proc, but there are processing differences. A command interface can be thought of as a kind of 'prototyping' for a program call. Creating your own commands can illustrate some of the real power of CL.


Featured Post

Free learning courses: Active Directory Deep Dive

Get a firm grasp on your IT environment when you learn Active Directory best practices with Veeam! Watch all, or choose any amount, of this three-part webinar series to improve your skills. From the basics to virtualization and backup, we got you covered.

  • 10
  • 8
Tackle projects and never again get stuck behind a technical roadblock.
Join Now