QUSLOBJ IBM i: call system API in .NET/C# with NTi

Quentin DESTRADE

Tuesday 20 May 2025

  • tutorials

Discover how to leverage the IBM i system API QUSLOBJ in .NET with NTi to dynamically list the objects of a library (*FILE, *PGM, etc.) from a C# application.

Main image of the article “QUSLOBJ IBM i: call system API in .NET/C# with NTi ”

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>();
}
Back