After publishing my previous post several people contacted me regarding the method I had used to write data to and retrieve data from a User Space. In my example code I have used the QUSCHGUS API to write, and the QUSRTVUS API to retrieve. The messages asked why I had taken that approach versus using a pointer?
Either approach is valid, as they both perform the same function. But in the age of modern RPG code using pointers can be considered the better way to go. As I was remiss in my previous post I intend to make amends with this one.
One thing to be aware of if you are using your own User Spaces, rather than ones created by a list API, is your own does not contain the header information for the User Space. I will need to determine the equivalent data myself.
In my example I am going to write data to the User Space, and then retrieve within the same program. The write and retrieve do not have to be in the same program, I am just doing it for my own convenience (as I am writing this post in the wee small hours so the simpler the better).
Let me start with the definitions of the APIs I have to use.
01 **free 02 ctl-opt option(*nodebugio:*srcstmt:*nounref) ; 03 /copy qsysinc/qrpglesrc,qusec //Error DS for APIs 04 dcl-pr DeleteUserSpace extpgm('QUSDLTUS') ; 05 *n char(20) const ; // Name 06 *n char(32767) options(*varsize:*nopass) ; // Error feedback 07 end-pr ; 08 dcl-pr CreateUserSpace extpgm('QUSCRTUS') ; 09 *n char(20) const ; // Name 10 *n char(10) const ; // Attribute 11 *n int(10) const ; // Initial size 12 *n char(1) const ; // Intial value 13 *n char(10) const ; // Authority 14 *n char(50) const ; // Text 15 *n char(10) const ; // Replace existing 16 *n char(32767) options(*varsize:*nopass) ; // Error feedback 17 end-pr ; 18 dcl-pr GetPointer extpgm('QUSPTRUS') ; 19 *n char(20) const ; // Name 20 *n pointer ; // Pointer to user space 21 *n char(32767) options(*varsize:*nopass) ; // Error feedback 22 end-pr ; |
Line 1: I need the **FREE to indicate that I am using totally free RPG.
Line 2: My favorite Control Options.
Line 3: I am copying in the data structure for the standard API error feedback data structure.
Lines 4 – 7: This is the procedure definition for the API, QUSDLTUS, to delete a User Space.
Lines 8 – 17: This is the procedure definition for the API, QUSCRTUS, to create the User Space.
Lines 18 – 22: This is the procedure definition for the API, QUSPTRUS, to get the pointer to the User Space.
The next part is really clever.
23 dcl-ds TestDs qualified based(P1) ; 24 Count int(5) ; 25 dcl-ds DataDs dim(999) ; 26 Str char(4) ; 27 Data char(50) ; 28 End char(4) pos(97) ; 29 end-ds ; 30 end-ds ; |
Line 23: As this data structure is a "based variable" for the pointer P1. When I move data to this data structure, or part of it, the equivalent part of the User Space will be updated. No pointer math needed!
Line 24: This subfield will contain the count of the number of elements the data structure array, lines 25 – 29, has.
Line 25 – 29: This is a nested data structure. Which came out as part of IBM i 7.3 TR1 and 7.2 TR6. If you are on an older release or TR you should be able to achieve the same with.
2A dcl-ds TestDs qualified based(P1) ; 2B Count int(5) ; 2C DataDs likeds(DataDs2) dim(999) ; 2D end-ds ; 2E dcl-ds DataDS2 ; 2F Str char(4) ; 2G Data char(50) ; 2H End char(4) pos(97) ; 2I end-ds ; |
Line 2C: The LIKEDS defines the subfield DataDs to be the same as the data structure DataDs2. And it is an array with 999 elements.
Let me call the User Space APIs I need.
31 dcl-s UserSpaceName char(20) inz('TEST QTEMP') ; 32 DeleteUserSpace(UserSpaceName:QUSEC) ; 33 CreateUserSpace(UserSpaceName:'':1:x'00':'*ALL': 'Example user space':'*YES':QUSEC) ; 34 GetPointer(UserSpaceName:P1) ; |
Line 31: The qualified User Space name has the User Space name in the first ten bytes, followed by the library name, starting in the eleventh position.
Line 32: I am deleting the User Space if it is already present.
Line 33: The CrtUserSpace (QUSCRTUS) API creates the User Space. The parameters are:
- Qualified User Space name
- Attribute, I don't care about this so I pass null ( '' )
- Initial size, if I pass 1 the user Space will automatically extend itself when needed
- I am initializing the User space as hexadecimal 00. I will be using this in the second program
- The User Space has authority of *ALL
- This is the text that will be used for the User Space's description
- Replace existing User Space, as I pass *YES if there is already a User Space with the name in the library it will be deleted and replaced with this new one
- Error data structure
Line 34: The GetPointer (QUSPTRUS) API is what allows me to use a pointer with my user space. The parameters are:
- Qualified name of the User Space
- Name of the pointer
- Error feedback area (which I am not using here)
The rest of the program is easy.
35 for TestDs.Count = 1 to %elem(TestDs.DataDs) ; 36 TestDs.DataDs(TestDs.Count).Str = 'str:' ; 37 TestDs.DataDs(TestDs.Count).End = ':end' ; 38 if (TestDs.Count = 1) ; 39 TestDs.DataDs(TestDs.Count).Data = 'First attempt' ; 40 elseif (TestDs.Count = 2) ; 41 TestDs.DataDs(TestDs.Count).Data = 'Second attempt' ; 42 elseif (TestDs.Count = 3) ; 43 TestDs.DataDs(TestDs.Count).Data = 'Third attempt' ; 44 leave ; 45 endif ; 46 endfor ; 47 *inlr = *on ; |
Lines 35 – 46: This For group will be perform the number of times as there elements in the data structure array. I am using the data structure subfield TestDs.Count as my counter. Therefore, when I exist the For loop it will contain the number of elements in it.
Lines 36 and 37: These are just "place holders" to flag the start and end of each element of the nested data structure array. As I am using a nested data structure its subfields must be qualified by the main and then the nested data structure names.
Lines 38 – 45: I am changing the Data subfield so that we can tell which element is which. I only want to write three elements, so after updating the third element I LEAVE the For group, line 44.
If I was to run debug, add a breakpoint at line 47, and view the content of the data structure I would see.
> EVAL testds TESTDS.COUNT = 3 TESTDS.DATADS.STR(1) = 'str:' TESTDS.DATADS.DATA(1) = 'First attempt TESTDS.DATADS.END(1) = ':end' TESTDS.DATADS.STR(2) = 'str:' TESTDS.DATADS.DATA(2) = 'Second attempt TESTDS.DATADS.END(2) = ':end' TESTDS.DATADS.STR(3) = 'str:' TESTDS.DATADS.DATA(3) = 'Third attempt TESTDS.DATADS.END(3) = ':end' TESTDS.DATADS.STR(4) = ' ' TESTDS.DATADS.DATA(4) = ' TESTDS.DATADS.END(4) = ' ' |
I would use the Dump object command, DMPOBJ, to dump the contents of the User Space to a spool file.
DMPOBJ OBJ(QTEMP/TEST) OBJTYPE(*USRSPC) |
When I look in the generated spool I can see the data I wrote.
*str:First attempt * * * * * *:endstr:Second attempt * * * * * * :endstr:Third attempt * * * * * * :end * * * |
If I need to get data from the User Space I can retrieve it using the appropriate data structure array's element.
I have to credit to the person who gave me this very simple way. A big thank you to Jon Paris for his feedback and example. And "Thank you" to all of you who sent me messages, sometimes we need to be kept on our "toes" by our colleagues and friends.
You can learn more about this from the IBM website:
This article was written for IBM i 7.3, and should work for earlier releases too.
Simon, thanks!
ReplyDeleteI needed to pass large chunks of structured data between several programs for a utility I am writing, and the receiving program would not be necessarily called directly by a module generating the data. And somehow user spaces went under the radar for me. Your series comes very handy for my case.