Introduction
IBM i offers many system APIs that allow access to operating system resources. Some of these APIs return directly usable data as structures or streams, while others require the use of a User Space to store the results before they can be processed.
The QUSLOBJ API (List Objects) belongs to the latter category: it does not return the list of objects directly in the output, but instead writes the results into a User Space, a temporary memory area. This mechanism is common among IBM i APIs that handle dynamic data occurrences, as it allows the retrieval of large volumes of information without strict limits on result size.
The goal of this tutorial is to explain how to properly implement a method using NTi to call this API from .NET, and how to interpret IBM’s documentation to correctly build the input structures and analyze the returned data.
The full source code of the implementation is available at the end of this tutorial..
You can follow each step to understand how it works, then refer back to it for an overview and direct usage.
Step 1 – Choose an API: QSYS.QUSLOBJ
To use the QUSLOBJ API, the first thing to do is to consult its documentation.
The main steps to follow are:
- Create a User Space to store the results using the QUSCRTUS API.
- Call the QUSLOBJ API, providing the expected input parameters.
- Read the results stored in the User Space using the QUSRTVUS API.
- Iterate through the returned objects and map them to .NET objects.
Step 2 – Read the IBM documentation
Before implementing the call to QUSLOBJ, it's important to understand what parameters this API expects and what it returns as output.
📥 Input parameters:
Parameter | Type | Direction | Description |
---|---|---|---|
User Space | Char(20) | Input | Name (10 characters) and library (10 characters) where the list is stored |
Format Name | Char(8) | Input | Data format (OBJL0400 in our example) |
Object & LibraryName | Char(20) | Input | Object name (10 characters) and library (10 characters) |
Object Type | Char(10) | Input | Object type (*FILE, *PGM, etc.) |
Error Code | Char(* ) | Input / Output | Error structure (optional) |
📥 Output parameters:
The QUSLOBJ API does not directly return the list of objects as output parameters. Instead, it stores the results in a User Space, a temporary memory area that must be specified as input.
A User Space is a system object used to store large amounts of data, especially the results returned by certain IBM i APIs.
It's created before the QUSLOBJ call using the QUSCRTUS API. Once QUSLOBJ is executed, the listed objects are written into this User Space. To retrieve the results, the QUSRTVUS API is used to read its contents.
We will not go into the implementation details here, but keep in mind that all returned data is stored in this User Space and must be analyzed according to IBM's defined structure.
Step 3 – Implement the QUSLOBJ call method with NTi
We now understand how QUSLOBJ works and how to interpret the results stored in the User Space. Let’s move on to implementing our method in C# using NTi.
1️⃣ Define the data model
First, we need to define a data model that will represent each object returned by the API. We use the OBJL0400 format to retrieve detailed information about the objects. We therefore create a C# class that reflects this structure:
public class ListObjectsInformation
{
public string? ObjectName { get; set; }
public string? LibraryName { get; set; }
public string? ObjectType { get; set; }
public string? InformationStatus { get; set; }
public string? ExtendedAttribute { get; set; }
public string? TextDescription { get; set; }
public int AspNumber { get; set; }
public string? Owner { get; set; }
public DateTime CreationDateTime { get; set; }
public DateTime ChangeDateTime { get; set; }
...
}
2️⃣ Define the main method
We define the method we are going to build to retrieve a list of objects from a given library. The API expects the parameters libraryName
, objectName
, and objectType
.
public static List<ListObjectsInformation> RetrieveObjectList(
string libraryName,
string objectName = "*ALL",
string objectType = "*ALL"
)
{
}
3️⃣ Create the User Space
Before calling QUSLOBJ, we must create a User Space in QTEMP as previously described. This User Space will hold the generated object list. The QUSCRTUS API is used to create this User Space. It requires several parameters, including:
- The name of the User Space — here, NTILOBJ in QTEMP
- An initial size that will be adjusted dynamically based on the returned data
- An initialization value, here 0x00, meaning a blank space
- A generic attribute and a description
Since the size of the returned data is not known in advance, a while loop is used to check whether the allocated space is sufficient and to increase it if needed.
int initialSize = 10000;
bool spaceAllocated = false;
while (!spaceAllocated)
{
// Create the User Space with the current size
var initialParameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // User Space name
new NTiProgramParameter("QUSLOBJ", 10), // Extension name
new NTiProgramParameter(initialSize), // Initial size
new NTiProgramParameter(new byte[] { 0x00 }), // Initialization value
new NTiProgramParameter("*ALL", 10), // Attributes
new NTiProgramParameter("List Object Information Userspace", 50) // Description
};
conn.CallProgram("QSYS", "QUSCRTUS", initialParameters);
4️⃣ Call the QUSLOBJ API after creating the User Space
Once the User Space is created, we can call the QUSLOBJ API to retrieve the list of objects. The API expects several input parameters, which must be correctly formatted to avoid errors.
The QUSLOBJ API expects fixed-length Char parameters:
- The User Space is a 20-character string (10 for the name, 10 for the library), hence .Append("QTEMP", 10) to concatenate.
- The data format is an 8-character string ("OBJL0400").
- The object name and library form a 20-character string (.Append(libraryName, 10)).
- The object type is a 10-character string (*PGM, *FILE, etc.).
- Finally, the error structure is a 16-character string, empty in this case.
Once these parameters are defined, we call our program and execute the API.
var parameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // User Space
new NTiProgramParameter("OBJL0400", 8), // Data format
new NTiProgramParameter(objectName, 10).Append(libraryName, 10), // Object and library
new NTiProgramParameter(objectType, 10), // Object type
new NTiProgramParameter("", 16) // Error structure (optional)
};
conn.CallProgram("QSYS", "QUSLOBJ", parameters);
5️⃣ Retrieve the results
Once QUSLOBJ is executed, the results are stored in the User Space. To retrieve them, we use the QUSRTVUS API, which allows us to read its content.
- The first parameter always refers to the User Space (NTILOBJ in QTEMP).
- The second parameter is a fixed value (1), indicating the retrieval format.
- The third parameter corresponds to the allocated size of the User Space, defined dynamically earlier.
- Finally, the last parameter is an empty string matching the size of the User Space, marked as output
(.AsOutput())
, where the data will be written.
var finalParameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10),
new NTiProgramParameter(1),
new NTiProgramParameter(initialSize),
new NTiProgramParameter("", initialSize).AsOutput() // Retrieve data
};
conn.CallProgram("QSYS", "QUSRTVUS", finalParameters);
6️⃣ Analyze the returned data
We now need to extract and analyze the data contained in the User Space. The results are returned in the last parameter defined as output during the QUSRTVUS call. We retrieve the general information: the data offset, the number of entries, and the size of each entry. These values help us understand where and how to iterate through the list of returned objects. The general data structure for list APIs is available here.
- The data offset
offsetToData
indicates where the actual list of objects begins. - The number of entries
numberOfEntries
indicates how many objects are listed. - The entry size
entrySize
tells us how many bytes are allocated for each object. - The total data size
listSize
corresponds to the memory space occupied in the User Space.
The QUSLOBJ API may return an CPFxxxx error code if a problem occurs. This code is stored in
parameters[4]
, which is the fifth parameter of our call. We must extract and clean it to remove any control characters.
string error = new string(parameters[4].GetString(0, 16)
.Where(c => !char.IsControl(c))
.ToArray())
.Trim();
var listOfParameters = finalParameters[3]; // 4th parameter (zero-based index)
var offsetToData = listOfParameters.GetInt(0x7C); // Offset to the beginning of the data
var numberOfEntries = listOfParameters.GetInt(0x84); // Number of returned entries
var entrySize = listOfParameters.GetInt(0x88); // Size of each entry
var listSize = listOfParameters.GetInt(0x80); // Total size of the data
Next, we make sure that the allocated space for the User Space is sufficient. If the total data size listSize
+ offsetToData
exceeds the initially allocated size, we must delete the User Space, increase its size, and re-run the QUSLOBJ call.
var totalSize = listSize + offsetToData;
if (totalSize > initialSize)
{
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
initialSize = totalSize;
}
If the space is sufficient, we iterate through the list of objects to extract their information. Each object returned by the API is stored as a data block at a precise position. The structure is defined by IBM and follows a strict layout where each field starts at a specific offset and has a fixed length.
The approach consists of looping through the list of objects based on the number of returned entries. At each iteration, we calculate the exact position of the current object by adding the data offset to the entry index multiplied by the entry size. This allows us to point directly to the object we want to analyze.
To extract information, we use methods adapted to the data type:
GetString(offset, length).Trim()
for text fields, extracting a fixed-length string and trimming trailing spaces.GetInt(offset)
for binary numeric values stored in 4 or 8 bytes, no length required.GetDTSTimestamp(offset)
is a method specific to NTi that converts an IBM i timestamp into a .NET-friendly DateTime.
Once the values are extracted, they are stored in a ListObjectsInformation
object and added to the result list.
var result = new List<ListObjectsInformation>();
if (numberOfEntries > 0)
{
for (int i = 0; i < numberOfEntries; i++)
{
// Calculate the offset of the current entry in the User Space
int currentOffset = offsetToData + (i * entrySize);
result.Add(new ListObjectsInformation
{
ObjectName = listOfParameters.GetString(currentOffset, 10).Trim(), // At offset 0, length 10
LibraryName = listOfParameters.GetString(currentOffset + 10, 10).Trim(), // At offset 10, length 10
ObjectType = listOfParameters.GetString(currentOffset + 20, 10).Trim(), // At offset 20, length 10
InformationStatus = listOfParameters.GetString(currentOffset + 30, 1).Trim(), // At offset 30, length 1
ExtendedAttribute = listOfParameters.GetString(currentOffset + 31, 10).Trim(), // At offset 31, length 10
TextDescription = listOfParameters.GetString(currentOffset + 41, 50).Trim(), // At offset 41, length 50
UserDefinedAttribute = listOfParameters.GetString(currentOffset + 91, 10).Trim(), // At offset 91, length 10
AspNumber = listOfParameters.GetInt(currentOffset + 108), // At offset 108, binary 4
Owner = listOfParameters.GetString(currentOffset + 112, 10).Trim(), // At offset 112, length 10
ObjectDomain = listOfParameters.GetString(currentOffset + 122, 2).Trim(), // At offset 122, length 2
CreationDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 124), // At offset 124, NTi-specific timestamp
ChangeDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 132), // At offset 132, NTi-specific timestamp
StorageStatus = listOfParameters.GetString(currentOffset + 140, 10).Trim() // At offset 140, length 10
});
}
}
Once all data has been extracted and stored as C# objects, it is important to clean up memory by deleting the temporary User Space. Although the User Space is stored in QTEMP (and therefore deleted at the end of the session), it is preferable to remove it immediately after use to avoid unnecessary overhead in case of multiple consecutive calls.
We use the CL command DLTUSRSPC to delete the specified User Space. Once this deletion is complete, the method returns the list of extracted objects. If no objects were found by the API, an empty list is returned to ensure clean result handling and to avoid potential errors in downstream processing.
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
return result;
If no entries were retrieved, we return an empty list:
return new List<ListObjectsInformation>();
Conclusion
With this final step, our method is fully functional. It follows a structured process that enables:
- The dynamic creation of a User Space
- The call to QUSLOBJ to retrieve the list of objects
- The extraction of data according to IBM’s defined structure
- The dynamic memory handling by adjusting the User Space size when needed
- The transformation of results into usable C# objects
- The systematic deletion of the User Space after use
public static List<ListObjectsInformation> RetrieveObjectList(
string libraryName,
string objectName = "*ALL",
string objectType = "*ALL"
)
{
int initialSize = 10000;
bool spaceAllocated = false;
List<ListObjectsInformation> listResult = new List<ListObjectsInformation>();
string errorCode = null;
while (!spaceAllocated)
{
// 1- Création user space
var initialParameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10),
new NTiProgramParameter("QUSLOBJ", 10),
new NTiProgramParameter(initialSize),
new NTiProgramParameter(new byte[] { 0x00 }),
new NTiProgramParameter("*ALL", 10),
new NTiProgramParameter("List Object Information Userspace", 50)
};
conn.CallProgram("QSYS", "QUSCRTUS", initialParameters);
// 2- Appel de l'API QUSLOBJ
var parameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10),
new NTiProgramParameter("OBJL0400", 8),
new NTiProgramParameter(objectName, 10).Append(libraryName, 10),
new NTiProgramParameter(objectType, 10),
new NTiProgramParameter("", 16)
};
conn.CallProgram("QSYS", "QUSLOBJ", parameters);
// 3. Récupération des données
var finalParameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10),
new NTiProgramParameter(1),
new NTiProgramParameter(initialSize),
new NTiProgramParameter("", initialSize).AsOutput()
};
conn.CallProgram("QSYS", "QUSRTVUS", finalParameters);
string error = new string(parameters[4].GetString(0, 16).Where(c => !char.IsControl(c)).ToArray()).Trim();
var listOfParameters = finalParameters[3];
var offsetToData = listOfParameters.GetInt(0x7C);
var numberOfEntries = listOfParameters.GetInt(0x84);
var entrySize = listOfParameters.GetInt(0x88);
var listSize = listOfParameters.GetInt(0x80);
var totalSize = listSize + offsetToData;
if(totalSize > initialSize)
{
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
initialSize = totalSize;
}
else
{
spaceAllocated = true;
var result = new List<ListObjectsInformation>();
if (numberOfEntries <= 0)
{
return new List<ListObjectsInformation>();
}
for(int i = 0; i < numberOfEntries; i++)
{
int currentOffset = offsetToData + (i * entrySize);
result.Add(new ListObjectsInformation
{
ObjectName = listOfParameters.GetString(currentOffset, 10).Trim(),
LibraryName = listOfParameters.GetString(currentOffset + 10 , 10).Trim(),
ObjectType = listOfParameters.GetString(currentOffset + 20, 10).Trim(),
InformationStatus = listOfParameters.GetString(currentOffset + 30, 1).Trim(),
ExtendedAttribute = listOfParameters.GetString(currentOffset + 31, 10).Trim(),
TextDescription = listOfParameters.GetString(currentOffset + 41, 50).Trim(),
UserDefinedAttribute = listOfParameters.GetString(currentOffset + 91, 10).Trim(),
AspNumber = listOfParameters.GetInt(currentOffset + 108),
Owner = listOfParameters.GetString(currentOffset + 112, 10).Trim(),
ObjectDomain = listOfParameters.GetString(currentOffset + 122, 2).Trim(),
CreationDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 124),
ChangeDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 132),
StorageStatus = listOfParameters.GetString(currentOffset + 140, 10).Trim(),
CompressionStatus = listOfParameters.GetString(currentOffset + 150 , 10).Trim(),
AllowChangeByProgram = listOfParameters.GetString(currentOffset + 151, 1).Trim(),
ChangedByProgram = listOfParameters.GetString(currentOffset + 152, 1).Trim(),
ObjectAuditing = listOfParameters.GetString(currentOffset + 153, 10).Trim(),
IsDigitallySigned = listOfParameters.GetString(currentOffset + 163, 1).Trim(),
IsSystemTrustedSigned = listOfParameters.GetString(currentOffset + 164, 1).Trim(),
HasMultipleSignatures = listOfParameters.GetString(currentOffset + 165, 1).Trim(),
LibraryAspNumber = listOfParameters.GetInt(currentOffset + 168),
SourceFileName = listOfParameters.GetString(currentOffset + 172, 10).Trim(),
SourceFileLibrary = listOfParameters.GetString(currentOffset + 182, 10).Trim(),
SourceFileMember = listOfParameters.GetString(currentOffset + 192, 10).Trim(),
SourceFileUpdatedDateTime = listOfParameters.GetString(currentOffset + 202, 13).Trim(),
CreatorUserProfile = listOfParameters.GetString(currentOffset + 215, 10).Trim(),
CreationSystem = listOfParameters.GetString(currentOffset + 225, 8).Trim(),
SystemLevel = listOfParameters.GetString(currentOffset + 233, 9).Trim(),
Compiler = listOfParameters.GetString(currentOffset + 242, 16).Trim(),
ObjectLevel = listOfParameters.GetString(currentOffset + 258, 8).Trim(),
IsUserChanged = listOfParameters.GetString(currentOffset + 266, 1).Trim(),
LicensedProgram = listOfParameters.GetString(currentOffset + 267, 16).Trim(),
PTF = listOfParameters.GetString(currentOffset + 283, 10).Trim(),
APAR = listOfParameters.GetString(currentOffset + 293, 10).Trim(),
PrimaryGroup = listOfParameters.GetString(currentOffset + 303, 10).Trim(),
IsOptimallyAligned = listOfParameters.GetString(currentOffset + 315, 1).Trim(),
PrimaryAssociatedSpaceSize = listOfParameters.GetInt(currentOffset + 316)
});
}
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
return result;
}
}
return new List<ListObjectsInformation>();
}