Now I will create the Format Name parameter and the Error Code parameter. The API spec tells us to use SBSL0100 for the format name:
parameters.Append(FORMAT_NAME,
cwbx.cwbrcParameterTypeEnum.cwbrcInput,
(int)QWCLASBSDataLengths.iFormatName);
stringConverter.Length = (int)QWCLASBSDataLengths.iFormatName;
parameters[FORMAT_NAME].Value = stringConverter.ToBytes("SBSL0100");
parameters.Append(ERROR_CODE,
cwbx.cwbrcParameterTypeEnum.cwbrcInout,
(int)QWCLASBSDataLengths.iErrorCode);
The Program Execution
Now I need to actually call the program and pass the parameters.
try
{
program.Call(parameters);
Retrieve the Results from the User Space
Now, QWCLASBS writes the list of subsystems to the user space that we created, so now we must retrieve the data from the user space. I will call another one of my API functions to do that.
byte[] results =
RetrieveUserSpace(server, userId, password,
userSpaceName, userSpaceLibrary, 125, 16);
The 125 is the offset into the user space, and the 16 is the number of bytes to read. To see what the data format is of the data written to in the user space, click the "User Space Format for List APIs" link in the "Format of the Generated List" section of the API specification. Here is an image of the structure format:

The format is too complicated for me to describe here, but we are interested in the 4 integers that begin at offset x7C (125). These 4 integers tell us the offset of the array of repeating subsystems, the size of the entire repeating array, the number of entries in the array, and the size of each entry. We will use most of these values for looping through the array, while retrieving the user space.
To see what that array looks like, click the "Format of the Generated List" link in the Format Name section of the API specification, you will see the format for SBSL0100. It is a repeating array of entries that looks like:
| Offset (Decimal) |
Type |
Field |
| 0 |
CHAR(10) |
Subsystem description name |
| 10 |
CHAR(10) |
Subsystem description library name |
So basically we have an array of 20 byte strings where the first 10 bytes are the subsystem description name and the second 10 bytes are the subsystem library name.
Creating the Results Output Structure
So, our last code returned a 16 byte array containing the 4 integers we need to loop through the subsystems. Next we need to create a StructureClass that we can use to get those 4 integers out of the 16 byte array. This is the most important part for learning how to work with structures and arrays:
cwbx.Structure mystruct = new cwbx.StructureClass();
mystruct.Bytes = results;
Next, we create 4, 4 byte fields in that structure class:
mystruct.Fields.Append(DATA_OFFSET, (int)QWCLASBSDataLengths.oDataOffset);
mystruct.Fields.Append(DATA_SECTION_SIZE,
(int)QWCLASBSDataLengths.oDataSectionSize);
mystruct.Fields.Append(ENTRY_COUNT, (int)QWCLASBSDataLengths.oEntryCount);
mystruct.Fields.Append(ENTRY_SIZE, (int)QWCLASBSDataLengths.oEntrySize);
So now we have defined that Structure class to have the 4 integer fields. Now, I'll read those 4 integers:
int offset = longConverter.FromBytes(mystruct.Fields[DATA_OFFSET].Value);
int sectionSize =
longConverter.FromBytes(mystruct.Fields[DATA_SECTION_SIZE].Value);
int entryCount = longConverter.FromBytes(mystruct.Fields[ENTRY_COUNT].Value);
int entrySize = longConverter.FromBytes(mystruct.Fields[ENTRY_SIZE].Value);
You now see how to access data that is returned in a structure.
Retrieving the Output Structure from the User Space
So now I know where the array of subsystems is (the offset), how big that array is, how many elements are in the array, and how big each element is. So first, I am going to go retrieve the data from the user space by specifying the offset and the section size to return a single byte array with all subsystems:
results =
RetrieveUserSpace(server, userId, password, userSpaceName,
userSpaceLibrary, offset, sectionSize);
So now the results array of bytes contains all the subsystems.
Create a Structure for the Each Array Element
Now I need to build yet another Structure class to define what each element of the array looks like:
cwbx.Structure listItemStruct = new cwbx.StructureClass();
listItemStruct.Fields.Append(SUBSYSTEM_NAME,
(int)QWCLASBSDataLengths.oSubsystemName);
listItemStruct.Fields.Append(SUBSYSTEM_LIBRARY_NAME,
(int)QWCLASBSDataLengths.oSussystemLibraryName);
So that structure is now defined to have two 10 byte fields.
Next I will create a C# 2.0 generic List:
List<SubsystemData> list = new List<SunsystemData>();
This is similar to generics in Java and templates in C++. If you are not familiar with any of these concepts, you may want to read up on C# generics. I'll summarize it as, it is C#'s way of dealing with data types in a generic way so that they do not have to be specified. This allows code to be very flexible. That line above is declaring a list that can only contain SubsystemData class objects. When I retrieve an element from that list, I don't even have to cast it. It knows it is a SubsystemData object.
Looping Through the Array of Subsystems
Now I need to loop for the number of subsystems in the array:
for(int i = 0; i < entryCount; i++)
{
Next I will call a function I wrote to return a portion of a byte array. Since we have this big byte array containing all of the subsystems, I want to return just a portion of that byte array to get a single element in the array:
listItemStruct.Bytes = GetSubByteArray(results, i * entrySize, entrySize);
The second parameter of that function call is the offset into the byte array, and the third is the length to read. So the first time I call it, I will pass a 0 as the offest, and 20 as the length. The second time I call it, I will pass an offset of 20 and a length of 20, and so forth.
Now I will create a SubsystemData object to add to the list:
SubsystemData item = new SubsystemData();
Obtaining the Subsystem Data from the Structure
Now I need to read the fields out of that sub byte array I returned:
stringConverter.Length = (int)QWCLASBSDataLengths.oSubsystemName;
item.subsystemName =
stringConverter.FromBytes(
listItemStruct.Fields[SUBSYSTEM_NAME].Value).Trim();
stringConverter.Length = (int)QWCLASBSDataLengths.oSussystemLibraryName;
item.subsystemLibraryName =
stringConverter.FromBytes(
listItemStruct.Fields[SUBSYSTEM_LIBRARY_NAME].Value).Trim();
And add the SubsystemData object to the list:
list.Add(item);
}
Next I will call my API function to delete the user space, and return the list:
DeleteUserSpace(server, userId, password, userSpaceName, userSpaceLibrary);
return(list);
}
I know there is a lot to this code, but the important part for this article is the way I create the StructureClass objects, append fields to the structure, and then read the data out by indexing into the structure's Fields array using the name of the parameter. Creating input structures works the same way, except you are assigning the data to the structure's fields array element for each field.
The Entire Method
Here is the entire method:
enum QWCLASBSDataLengths : int
{
iUserSpaceName = 10,
iUserSpaceLibrary = 10,
iFormatName = 8,
iErrorCode = 8,
oDataOffset = 4,
oDataSectionSize = 4,
oEntryCount = 4,
oEntrySize = 4,
oSubsystemName = 10,
oSussystemLibraryName = 10,
};
// QWCLASBS
public List ListActiveSubsystems(string server, string userId, string password)
{
string QUALIFIED_USERSPACE_NAME = "Qualified User Space Name";
string FORMAT_NAME = "Format Name";
string ERROR_CODE = "Error Code";
string DATA_OFFSET = "Data Offset";
string DATA_SECTION_SIZE = "Section Size";
string ENTRY_COUNT = "Entry Count";
string ENTRY_SIZE = "Entry Size";
string SUBSYSTEM_NAME = "Subsystem Name";
string SUBSYSTEM_LIBRARY_NAME = "Subsystem Library Name";
Connect(server);
cwbx.Program program = GetProgram(as400);
program.LibraryName = "QSYS";
program.ProgramName = "QWCLASBS";
string userSpaceName = "STOREFRONT";
string userSpaceLibrary = "QTEMP";
cwbx.StringConverter stringConverter = new cwbx.StringConverterClass();
cwbx.LongConverter longConverter = new cwbx.LongConverterClass();
// Create the userspace.
CreateUserSpace(server, userId, password, userSpaceName, userSpaceLibrary);
// Create the parameters.
cwbx.ProgramParameters parameters = new cwbx.ProgramParametersClass();
// UserSpace Name and Library.
string qualifiedUserSpaceName =
String.Format("{0,-10}{1,-10}", userSpaceName, userSpaceLibrary);
parameters.Append(QUALIFIED_USERSPACE_NAME,
cwbx.cwbrcParameterTypeEnum.cwbrcInput,
(int)QWCLASBSDataLengths.iUserSpaceName
+ (int)QWCLASBSDataLengths.iUserSpaceLibrary);
stringConverter.Length = (int)QWCLASBSDataLengths.iUserSpaceName
+ (int)QWCLASBSDataLengths.iUserSpaceLibrary;
parameters[QUALIFIED_USERSPACE_NAME].Value =
stringConverter.ToBytes(qualifiedUserSpaceName);
// Format Name
parameters.Append(FORMAT_NAME,
cwbx.cwbrcParameterTypeEnum.cwbrcInput,
(int)QWCLASBSDataLengths.iFormatName);
stringConverter.Length = (int)QWCLASBSDataLengths.iFormatName;
parameters[FORMAT_NAME].Value = stringConverter.ToBytes("SBSL0100");
// Error Code
parameters.Append(ERROR_CODE,
cwbx.cwbrcParameterTypeEnum.cwbrcInout,
(int)QWCLASBSDataLengths.iErrorCode);
try
{
program.Call(parameters);
// So far so good. Let's get the header from the userspace.
byte[] results = RetrieveUserSpace(server, userId, password,
userSpaceName, userSpaceLibrary, 125, 16);
cwbx.Structure mystruct = new cwbx.StructureClass();
mystruct.Bytes = results;
mystruct.Fields.Append(DATA_OFFSET, (int)QWCLASBSDataLengths.oDataOffset);
mystruct.Fields.Append(DATA_SECTION_SIZE,
(int)QWCLASBSDataLengths.oDataSectionSize);
mystruct.Fields.Append(ENTRY_COUNT, (int)QWCLASBSDataLengths.oEntryCount);
mystruct.Fields.Append(ENTRY_SIZE, (int)QWCLASBSDataLengths.oEntrySize);
int offset = longConverter.FromBytes(mystruct.Fields[DATA_OFFSET].Value);
int sectionSize = longConverter.FromBytes(mystruct.Fields[DATA_SECTION_SIZE].Value);
int entryCount = longConverter.FromBytes(mystruct.Fields[ENTRY_COUNT].Value);
int entrySize = longConverter.FromBytes(mystruct.Fields[ENTRY_SIZE].Value);
// Ok, lets get the list data.
results = RetrieveUserSpace(server, userId, password,
userSpaceName, userSpaceLibrary, offset, sectionSize);
// Build a reusable structure.
cwbx.Structure listItemStruct = new cwbx.StructureClass();
listItemStruct.Fields.Append(SUBSYSTEM_NAME,
(int)QWCLASBSDataLengths.oSubsystemName);
listItemStruct.Fields.Append(SUBSYSTEM_LIBRARY_NAME,
(int)QWCLASBSDataLengths.oSussystemLibraryName);
// Create our list.
List<SubsystemData> list = new List<SubsystemData>();
// Populate the list.
for(int i = 0; i < entryCount; i++)
{
listItemStruct.Bytes = GetSubByteArray(results, i * entrySize, entrySize);
SubsystemData item = new SubsystemData();
stringConverter.Length = (int)QWCLASBSDataLengths.oSubsystemName;
item.subsystemName =
stringConverter.FromBytes(listItemStruct.Fields[SUBSYSTEM_NAME].Value).Trim();
stringConverter.Length = (int)QWCLASBSDataLengths.oSussystemLibraryName;
item.subsystemLibraryName =
stringConverter.FromBytes(
listItemStruct.Fields[SUBSYSTEM_LIBRARY_NAME].Value).Trim();
list.Add(item);
}
DeleteUserSpace(server, userId, password, userSpaceName, userSpaceLibrary);
return(list);
}
finally
{
//program.system.Disconnect(cwbx.cwbcoServiceEnum.cwbcoServiceAll);
}
}
Download The Entire Project
You may download the entire Visual Studio.Net project here. It requires the .Net Framework 2.0.
Here is a screenshot of the demo application:
