Published on System iNetwork (http://systeminetwork.com)
Handle Null-Terminated Parameter in CL
By linda.harty@penton.com
Created Oct 21 2009 - 14:55

By:
Scott Klement [1]

In a recent discussion in the System iNetwork forums, someone asked how to write a CL program that receives a null-terminated parameter. My initial reaction was that this was a silly thing to do in CL. However, as I thought about it, it started to make a lot more sense.

After all, if you call a CL program or procedure from C, you'll certainly want to be able to handle null-terminated strings. But perhaps even more useful is the fact that, if you write a CL program that's called from a QShell command or script, it'll pass null-terminated strings. And calling CL from QShell can be very useful indeed!

If you're unfamiliar with the concept, let me say that a null-terminated string is a variable-length character string that ends when a special value of x'00' (called "null") is detected. So it's just a string of consecutive bytes, of any length, that finally ends when a x'00' is found.

Figure Out the Length of the String

In the forum thread, Bruce Vining was kind enough to post his recommended method of handling a null-terminated string. Here's his code:

Pgm        Parm(&String_In)
Dcl        Var(&String_In)   Type(*Char)  Len(32767)

Dcl        Var(&Data)        Type(*Char)  Len(50)
Dcl        Var(&Length)      Type(*UInt)

CallPrc    Prc('__strlen') Parm(&String_In) +
               RtnVal(&Length)
ChgVar     Var(&Data) Value(%sst(&String_In 1 &Length))
SndPgmMsg  Msg(&Data)

Return
EndPgm

A few important things to note about Bruce's example:

  • The &String_in parameter needs to be as long as the largest string your program can handle.
  • This code is ILE CL and should have a source member type of CLLE. If you get errors about CallPrc not being allowed, it means you forgot to use ILE CL.
  • Although the code looks like it's calling __strlen as an external function, in reality, the __strlen MI function will be inserted inline into the program.

Bruce copies the null-terminated string into an ordinary CL variable of type *CHAR so that it can then be used elsewhere in the CL program without any special handling. In his example, he passes it to SNDPGMMSG in order to display the contents of the string, just as a simple test.

Use from QShell

One of QShell's most versatile tools is a utility named find. The find utility can search a particular subtree of the IFS for files based on various criteria. Here's a partial list of what find can search on:

  • date & time a file was last changed
  • date & time a file was last accessed (read from)
  • files owned by a particular user
  • files owned by a particular group
  • files that have certain characters in the file name
  • files of a certain type (directory or data file)
  • files of a particular size or larger

You can optionally instruct find to run a particular program on each file that matches its search criteria--and this is where your CL program comes in. If you need to have a CL program that needs to operate on IFS files that match a particular criterion, you can easily have the find utility invoke your CL program and pass the filename as a parameter. It will invoke your program separately for each file that it finds.

The syntax of QShell's find utility looks like this:

find start-path(s) criteria action
  • start-path(s): one or more IFS path names to search; QShell will search all directories and subdirectories beneath each path name you specify
  • criteria: expression that designates which files you're looking for
  • action: what do you want to do with the files you find? By default, the -print action is taken, which prints the filenames to stdout, but you can specify -exec if you want to run a program for each file

For example, if I want to print a list of all files in the /upload1 directory that were changed within the past day, I could code:

   find /upload1 -type f -mtime 1 -print

The -type switch is part of the criteria. Because I specified type f, it will only include objects that the IFS treats as "files" (as opposed to directories, sockets, and other special objects). The -mtime switch tells it to only include files that have been modified within (in this example) one day. The -print switch tells it to print to standard output, which by default causes the data to print on the display.

Instead of printing to the display, I could use the -exec switch to tell find to execute a program. Here's the syntax of the -exec switch.

find start-path(s) criteria -exec command \;

Note the \; at the end. This is important, because your command string might be more than one word long. The find utility will include everything after -exec as part of the command, until it reaches the \;. The \; tells it that you've completed your command string.

You can insert the special string {} where you want the find utility to insert the path name. Wherever it finds {}, it'll insert the path name in the command string.

For example, if I want to run a CL program named SCOTT/ADDTOPF for every file that has been added to the /upload1 directory in the past day, I might do this:

   find /upload1 -type f -mtime 1 -exec /qsys.lib/scott.lib/addtopf.pgm "{}" \;

Notice that I had to use IFS syntax for my CL program name. In IFS syntax, your traditional libraries and their objects are arranged in a hierarchical fashion, in which every library is within the QSYS library and every object is suffixed by its object type. So in this example, /qsys.lib is the QSYS library. Within that is the SCOTT library, and within the SCOTT library is a program named ADDTOPF. In Unix, if you just list a path and program name without preceding it with another command, Unix will try to run the program (i.e., you don't need to say "call," because running the program is the default action). So if I tell it the command is /qsys.lib/scott.lib/addtopf.pgm, it tells QShell to run that program.

For the sake of example, let's say that these files are uploaded by a customer. The customer's data is always in UTF-16 Unicode, but when he uploads it, it gets marked by FTP with CCSID 819. The ADDTOPF program needs to fix the CCSID on the file, then run CPYFRMIMPF to insert it into a database.

PGM PARM(&STMFNULL)

    DCL VAR(&STMFNULL) TYPE(*CHAR) LEN(5000)
    DCL VAR(&STMF)     TYPE(*CHAR) LEN(5000)
    DCL VAR(&LEN)      TYPE(*UINT) LEN(4)


    /* Convert null-terminated string to non-null */

    CALLPRC PRC('__strlen') PARM(&STMFNULL) +
            RTNVAL(&LEN)
    IF (&LEN *GE 1) DO
       CHGVAR VAR(&STMF) VALUE(%SST(&STMFNULL 1 &LEN))
    ENDDO


    /* Force CCSID to 1200, a common upload error */

    CHGATR OBJ(&STMF) ATR(*CCSID) VALUE(1200)


    /* Add stream file data to our MYFILE file */

    CPYFRMIMPF FROMSTMF(&STMF) TOFILE(MYFILE) +
               MBROPT(*UPDADD) ERRRCDOPT(*ADD) +
               RPLNULLVAL(*FLDDFT)

ENDPGM
© 2010 Penton Media, Inc.

Source URL: http://systeminetwork.com/article/handle-null-terminated-parameter-cl

Links:
[1] http://systeminetwork.com/author/scott-klement