** This article was originally published by IBM Systems Magazine.
A very common question on RPG Internet lists is, "How can I convert a character string to its hex equivalent?" We'd like to answer this popular question by introducing you to the wonderful world of utilizing machine interface (MI) functions in RPG programs. Hopefully most of you have heard of MI, even if youre not quite sure what its all about.
In the simplest of terms, MI is the closest thing there is to a native assembler language on the iSeries platform. In the "olden days" of RPG/400 and the other OPM compilers, MI was the intermediate language that the compilers generated from our RPG and COBOL programs. The resulting MI was then translated into the raw machine instructions that were actually executed. Current ILE compilers dont generate MI, but W-code. Because IBM doesnt make the details of W-code available, MI remains our only access to some of the systems lower level functionality.
Most, but not all, MI instructions are "surfaced" for use in one or both of two forms: C functions and built-ins. A list of all of the supported MI functions is available in the Appendix "Reference Summary" of the ILE C/C++ for AS/400 MI Library Reference.
(Note: To see an example of the generated code, compile an old RPG/400 program specifying the additional option GENOPT(*LIST). Now take a look at the spool file, and youll see the generated MI code. You can learn a lot about MI just by studying the compiler listings, but if you really want to get into the nuts and bolts of MI programming, then theres only one place - get hold of a copy of Leif Svalgaarrd's MI tutorial. You can also study the C/C++ documentation and IBM's MI functional reference manual, but since the seem to "move around" you're best to search for them on IBM's documentation site.)
Now that weve laid the groundwork, lets get to the task at hand. Well be using the MI instruction CVTHC to convert a string to hex. Given the "ABC" character string, the instruction produces a string containing the "C1C2C3" hex representation. CVTHC is surfaced as the C function "cvthc", so thats the actual version we'll be using. Because requests for the reverse operation (converting a hex string to its character equivalent) are almost as frequent, our simple program also demonstrates the MI instruction CVTCH (C function "cvtch").
Ctl-Opt DftActGrp(*No);
(A) Dcl-PR ToHex EXTPROC('cvthc');
(B) HexResult Char(20000) OPTIONS(*VARSIZE);
CharInp Char(10000) OPTIONS(*VARSIZE);
CharNibbles Int(10) VALUE;
End-PR;
Dcl-PR FromHex EXTPROC('cvtch');
CharResult Char(10000) OPTIONS(*VARSIZE);
HexInp Char(20000) OPTIONS(*VARSIZE);
HexLen Int(10) VALUE;
End-PR;
Dcl-S TestString Char(26) INZ('Jon and Susan - Partner400');
Dcl-S HexEquivalent Char(52);
Dcl-S Result Char(26);
Dsply TestString;
(C) ToHex( HexEquivalent
: TestString
: %Len(TestString) * 2);
Dsply HexEquivalent;
(D) FromHex( Result
: HexEquivalent
: %Len(HexEquivalent ));
Dsply Result;
*InLr = *On;
The prototype for "cvthc" is shown at (A). Weve named it "ToHex" because the original name gives the impression that it converts from hex to character rather than the other way around. (And yes, we've made that mistake.)
The procedure takes three parameters (B). The first is the result field; this is where the converted hex string will be placed. The second is the input character string, and the third is the length of that input field in nibbles (i.e., the number of bytes * 2). Why the routine couldn't accept the length of the character field and multiply it by 2 itself is anyone's guess.
Those of you with some familiarity with prototyping C type functions in RPG might have coded the prototype this way:
Dcl-PR ToHex EXTPROC('cvthc');
HexResult pointer VALUE;
CharInp pointer VALUE;
CharNibbles Int(10) VALUE;End-PR;
While theres nothing wrong with this, it does require the invocation to be changed to pass the parameter's addresses. For example:
ToHex( %Addr(HexEquivalent)
: %Addr(TestString)
: %Len(TestString) * 2);
We chose to use a more RPG-like interface. The limit with our current prototype is that it is restricted to converting strings with a maximum length of 10,000 characters. Big enough in most cases and the original example was written when RPG had a maximum string length of 32K. Now that the limit is 16Mb you can certainly increase the size if you need it. Remember your maximum character string size will be half of RPG's capacity as the hex equivalent will be twice as long.
At (D) you can see the invocation of the procedure. We use the %LEN built-in function to provide the length in nibbles of the field to be converted.
Next we display the conversion results before moving on (E) to reverse the process by calling the FromHex procedure ("cvtch") and displaying that result just to prove that everything worked as it should. That's all there is to it.
Now that weve seen a simple example, lets take a quick look at a slightly more complex one. The CPRDATA MI instruction performs a "zip" type function to compress a character sting. This is surfaced as the C function (#8220;cprdata") and also as the _CPRDATA built-in function. Well briefly explain the differences between the two implementations in a bit. Because compressing data is of little use unless we can later decompress it, the example also demonstrates the DCPDATA instruction.
H DFTACTGRP(*NO) BNDDIR('QC2LE')
D Compress pr 10i 0 Extproc('cprdata')
D Output 65535a Options(*Varsize)
D OutputLen 10i 0 Value
D Source 65535a Options(*Varsize)
D SourceLen 10i 0 Value
D Decompress pr 10i 0 Extproc('dcpdata')
D Result 65535a Options(*Varsize)
D ResultLen 10i 0 Value
D Source 65535a Options(*Varsize)
D LengthMsg S 52a
D Compressed S 52a
D CompressedLen S 5p 0
D ResultLen S 5p 0
D Source S 30a
D Result S 30a
C Eval Source = *All'Jon and Susan - '
C Source Dsply
C Eval CompressedLen =
C Compress(Compressed: %Size(Compressed):
C Source: %Size(Source))
C Eval LengthMsg = 'Compressed length '
C + %Char(CompressedLen)
C LengthMsg Dsply
C Compressed Dsply
C Eval ResultLen =
C DeCompress(Result: %Size(Result):
C Compressed)
C Result Dsply
C Eval LengthMsg = 'Result length '
C + %Char(ResultLen)
C Eval *InLR = *On
While we won't walk through this program in detail (hopefully the operation is obvious from the code), we will briefly discuss the parameters and some of the issues that arise. As you will see, the prototype for cprdata identifies four parameters:
- One parameter defines the field to hold the compressed result. Our prototype allows for it to be any length up to the maximum RPG field size of 65535. Note that the compression method this function uses is a simple one. In addition, the compressed string includes information about its length, the original length and compression method used. The result is that if a very small field is "compressed" the resulting string can be longer than the original. In fact, if you run our example, youll see that the compressed 30-character string is 47 characters long. Needless to say, as longer strings are processed, the overhead is assimilated and significant space savings can be seen.
- Another parameter supplies the result fields length. Its vital that the value supplied doesnt exceed the fields actual size, hence the use of the %SIZE BIF in our example. The results can be "interesting" if you get this wrong.
- The other two parameters define the field to be compressed and identifies its length.
The function returns an integer value, which is the length of the compressed string. A word of warning is in order here: According to IBMs documentation, if the result fields length is too short to hold the compressed string, the process is stopped and the return value will indicate the length required to hold the compressed string. However, in our experience this isnt the case; if the field is too small, the function simply fails. Although it hasnt been done in this simplified example, the best way to handle this is to trap the error by wrapping the invocation in a MONITOR group.
The dcpdata decompression function only requires three parameters because, as noted, the compressed strings length is contained within the string itself.
As noted, the compression algorithm used by the C function version is primitive. The underlying CPRDATA MI instruction offers a second compression algorithm; that isnt surfaced in the cprdata C function, but it is available through the _CPRDATA built-in function. These built-ins are considerably more complex to use than the basic C functions; you should study the MI Functional Reference manual to get the details.
While we dont have the space to go through the full details here, this sample program that performs the same compression functions as our earlier example while taking advantage of a more sophisticated compression algorithm. If theres sufficient interest well publish the details of this program in a future iSeries EXTRA.
// Note that because we are using the built-in versions,
// the binding directory QC2LE is not required.
H DFTACTGRP(*NO)
D Compress pr Extproc('_CPRDATA')
D Template Like(CompressTemplate)
// Rather than have multiple parameters to control the built-in's
// action, a DS control block or template is used.
// We have prototyped the call, but a CALLB could have been used
D CompressTemplate...
D DS
D SourceLenC 10i 0 Inz(%Size(Source))
D ResultLenC 10i 0 Inz(%Size(Result))
D CompressedLen 10i 0 Inz(0)
D Algorithm 5i 0 Inz(2)
D 18a Inz(*LOVAL)
D SourcePtrC * Inz(%Addr(Source))
D ResultPtrC * Inz(%Addr(Result))
D LengthMsg S 52
D Source S 52
D Result S 52
D Decompress pr Extproc('_DCPDATA')
D Template Like(DeCompressTemplate)
// The decompression template is simpler becuase much of the data
// needed is embedded within the compressed string
D DeCompressTemplate...
D DS
D 4a Inz(*LOVAL)
D ResultLenD 10i 0 Inz(%Size(Result))
D DeCompressLen 10i 0 Inz(0)
D 20a Inz(*LOVAL)
D SourcePtrD * Inz(%Addr(Result))
D ResultPtrD * Inz(%Addr(Source))
C Eval Source = *All'Jon and Susan - '
C Source Dsply
* The template has all been pre-set so simply call the built-in
C CallP Compress(CompressTemplate)
* Display the results to demonstarte that it works.
C Eval LengthMsg = 'Compressed length '
C + %Char(CompressedLen)
C LengthMsg Dsply
C Result Dsply
* Now decompress it all to make sure everything is intact
C CallP DeCompress(DeCompressTemplate)
C Source Dsply
C Eval *InLR = *On