Call the QUSLOBJ API from .NET with NTi
Introduction
IBM i provides many system APIs for accessing operating system resources. Some return data directly, while others require a User Space to store results before they can be processed.
The QUSLOBJ API (List Objects) falls into this second category: rather than returning the object list directly, it writes the results into a User Space, a temporary memory area. This pattern is common for IBM i APIs that handle dynamic data sets, as it allows large volumes of information to be retrieved without strict size limitations.
This example covers how to implement a method with NTi to call this API from .NET, and how to read the IBM documentation to correctly build the input structures and parse the returned data.
💡The complete source code is available here. Follow each step to understand how it works, then refer to the summary for direct use.
Step 1 - Understand the QSYS.QUSLOBJ API
Start by reading the IBM documentation for QUSLOBJ.
Calling this API follows this process:
- Create a User Space to store the results using the QUSCRTUS API.
- Call the QUSLOBJ API with the expected input parameters.
- Read the results stored in the User Space using the QUSRTVUS API.
- Iterate over the returned objects and map them to .NET objects.
Step 2 - Read the IBM documentation
Before calling QUSLOBJ, here is what the API expects as input parameters and what it returns.
Input parameters
| Parameter | Type | Direction | Description |
|---|---|---|---|
| User Space | Char(20) | Input | Name (10 characters) and library (10 characters) where to store the list |
| Format Name | Char(8) | Input | Data format (OBJL0400 in this 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
QUSLOBJ does not return the object list directly as output parameters. It stores the results in a User Space, a temporary memory area that must be specified as an input parameter.
A User Space is a system object used to store large amounts of data, including results returned by certain IBM i APIs.
- It is created before calling QUSLOBJ using the QUSCRTUS API.
- Once QUSLOBJ has run, the listed objects are written into this User Space.
- To retrieve the results, the QUSRTVUS API is used to read its content.
💡All returned data is stored in this User Space and must be parsed according to the structure defined by IBM.
Step 3 - Build the QUSLOBJ call method
Define the data model
Define a C# class representing each object returned by the API. The OBJL0400 format is used to retrieve detailed information:
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; }
...
}Open the connection
Declare a NTiConnection instance and open the connection:
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
conn.Open();Define the main method
The method accepts libraryName, objectName and objectType as parameters:
public static List<ListObjectsInformation> RetrieveObjectList(
string libraryName,
string objectName = "*ALL",
string objectType = "*ALL"
)
{
}Create the User Space
Before calling QUSLOBJ, create a User Space in QTEMP via the QUSCRTUS API to store the generated object list.
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 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 (blank space)
new NTiProgramParameter("*ALL", 10), // Attributes
new NTiProgramParameter("List Object Information Userspace", 50) // Description
};
conn.CallProgram("QSYS", "QUSCRTUS", initialParameters);Call the QUSLOBJ API
The API expects parameters as fixed-length Char values:
- 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.). - The error structure is a 16-character string, left empty here.
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);Retrieve the results
Use the QUSRTVUS API to read the User Space contents:
- The first parameter is the User Space (
NTILOBJinQTEMP). - The second parameter is a fixed value (
1), indicating the retrieval format. - The third parameter is the allocated User Space size, defined dynamically above.
- The last parameter is an empty string of the User Space size, 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() // Data retrieval
};
conn.CallProgram("QSYS", "QUSRTVUS", finalParameters);Parse the returned data
Extract the general information from the User Space. The general data structure for list APIs is available here.
offsetToData- offset indicating where the object list startsnumberOfEntries- number of returned objectsentrySize- number of bytes allocated per objectlistSize- total size of the data in the User Space
💡 QUSLOBJ may return a
CPFxxxxerror code if it encounters a problem. This code is stored inparameters[4](5th parameter of the call) and must be extracted and cleaned to remove 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-indexed)
var offsetToData = listOfParameters.GetInt(0x7C); // Offset where the data starts
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
If the total size exceeds the allocated space, delete the User Space, increase the size and restart the call:
var totalSize = listSize + offsetToData;
if (totalSize > initialSize)
{
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
initialSize = totalSize;
}
If the space is sufficient, iterate over the object list to extract their data. Each object returned by the API is stored as a fixed-position data block. The structure is defined by IBM and follows a strict layout where each field starts at a specific offset and has a fixed length.
Loop through the object list based on the number of returned entries. At each iteration, calculate the exact position of the current object by adding the data start offset to the entry index multiplied by the entry size. This allows pointing directly to the object being parsed.
Use the appropriate methods depending on the data type:
GetString(offset, length).Trim()- extracts a fixed-length string and trims whitespaceGetInt(offset)- retrieves a binary numeric value (4 bytes)GetDTSTimestamp(offset)- NTi-specific method that converts an IBM i
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(), // Offset 0, length 10
LibraryName = listOfParameters.GetString(currentOffset + 10, 10).Trim(), // Offset 10, length 10
ObjectType = listOfParameters.GetString(currentOffset + 20, 10).Trim(), // Offset 20, length 10
InformationStatus = listOfParameters.GetString(currentOffset + 30, 1).Trim(), // Offset 30, length 1
ExtendedAttribute = listOfParameters.GetString(currentOffset + 31, 10).Trim(), // Offset 31, length 10
TextDescription = listOfParameters.GetString(currentOffset + 41, 50).Trim(), // Offset 41, length 50
UserDefinedAttribute = listOfParameters.GetString(currentOffset + 91, 10).Trim(), // Offset 91, length 10
AspNumber = listOfParameters.GetInt(currentOffset + 108), // Offset 108, binary 4
Owner = listOfParameters.GetString(currentOffset + 112, 10).Trim(), // Offset 112, length 10
ObjectDomain = listOfParameters.GetString(currentOffset + 122, 2).Trim(), // Offset 122, length 2
CreationDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 124), // Offset 124, NTi timestamp
ChangeDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 132), // Offset 132, NTi timestamp
StorageStatus = listOfParameters.GetString(currentOffset + 140, 10).Trim() // Offset 140, length 10
});
}
}
Once the data has been extracted and stored as C# objects, delete the temporary User Space:
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
return result;
If no entries were retrieved, return an empty list:
return new List<ListObjectsInformation>();Summary
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>();
}What's next?
- Call a program - RPG program call with input/output parameters
- Stored procedure - SQL stored procedure call with Dapper and DataReader
- NTiProgramParameter - complete parameter class reference