Appeler l’API QUSLOBJ en .NET avec NTi
Introduction
L'IBM i propose de nombreuses API système permettant d’accéder aux ressources du système d’exploitation. Certaines renvoient directement des données exploitables, tandis que d’autres nécessitent l’utilisation d’un User Space pour stocker les résultats avant de pouvoir les exploiter.
L’API QUSLOBJ (List Objects) fait partie de cette deuxième catégorie : elle ne retourne pas directement la liste des objets en sortie, mais écrit les résultats dans un User Space, une zone mémoire temporaire. Ce mécanisme est fréquent pour les API IBM i qui gèrent des occurrences de données dynamiques, car il permet de récupérer des volumes importants d’informations sans limitation stricte sur la taille des résultats.
Cet exemple explique comment implémenter une méthode avec NTi pour appeler cette API depuis .NET, et comment interpréter la documentation IBM pour construire correctement les structures d'entrée et analyser les données de retour.
💡Le code source complet de l’implémentation est disponible ici. Suivez chaque étape pour comprendre le fonctionnement, puis référez-vous au récapitulatif pour une utilisation directe.
Étape 1 - Comprendre l'API QSYS.QUSLOBJ
La première étape consiste à consulter la documentation IBM de QUSLOBJ.
L'appel à cette API suit le processus suivant :
- Créer un User Space pour stocker les résultats via l'API QUSCRTUS.
- Appeler l'API QUSLOBJ en spécifiant les paramètres d'entrée attendus.
- Lire les résultats stockés dans le User Space via l'API QUSRTVUS.
- Parcourir les objets retournés et les mapper en objets .NET.
Étape 2 - Lire la documentation IBM
Avant d’implémenter l’appel à QUSLOBJ, voici ce que cette API attend comme paramètres et ce qu'elle renvoie en sortie.
Paramètres d’entrée
| Paramètre | Type | Direction | Description |
|---|---|---|---|
| User Space | Char(20) | Input | Nom (10 caractères) et bibliothèque (10 caractères) où stocker la liste |
| Format Name | Char(8) | Input | Format de données (OBJL0400 dans notre exemple) |
| Object & LibraryName | Char(20) | Input | Nom de l’objet (10 caractères) et bibliothèque (10 caractères) |
| Object Type | Char(10) | Input | Type d’objet (*FILE, *PGM, etc.) |
| Error Code | Char(* ) | Input / Output | Structure d'erreur (optionnelle) |
Paramètres de sortie
L’API QUSLOBJ ne retourne pas directement la liste des objets en tant que paramètres de sortie. Elle stocke les résultats dans un User Space, une zone mémoire temporaire que nous devons spécifier en entrée.
Un User Space est un objet système utilisé pour stocker de grandes quantités de données, notamment les résultats renvoyés par certaines API IBM i.
- Il est créé avant l’appel à QUSLOBJ à l’aide de l’API QUSCRTUS.
- Une fois QUSLOBJ exécuté, les objets listés sont écrits dans ce User Space.
- Pour récupérer les résultats, on utilise l’API QUSRTVUS qui permet de lire son contenu.
💡 Toutes les données retournées sont stockées dans ce User Space et devront être analysées en respectant la structure définie par IBM.
Étape 3 - Implémenter la méthode d'appel à QUSLOBJ
Définir le modèle de données
Définissez une classe C# représentant chaque objet retourné par l'API. Le format OBJL0400 est utilisé pour récupérer des informations détaillées :
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; }
...
}Ouvrir la connexion
Déclarez une instance de NTiConnection et ouvrez la connexion :
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
conn.Open();Définir la méthode principale
La méthode accepte libraryName, objectName et objectType en paramètres :
public static List<ListObjectsInformation> RetrieveObjectList(
string libraryName,
string objectName = "*ALL",
string objectType = "*ALL"
)
{
}Créer le User Space
Avant d'appeler QUSLOBJ, créez un User Space dans QTEMP via l'API QUSCRTUS qui permettra de récupérer la liste d'objet générée.
La taille des données renvoyées n’étant pas connue à l’avance, une boucle while est utilisée pour vérifier si l’espace alloué est suffisant et l’augmenter si nécessaire.
int initialSize = 10000;
bool spaceAllocated = false;
while (!spaceAllocated)
{
// Création du User Space avec la taille actuelle
var initialParameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // Nom du User Space
new NTiProgramParameter("QUSLOBJ", 10), // Nom d’extension
new NTiProgramParameter(initialSize), // Taille initiale
new NTiProgramParameter(new byte[] { 0x00 }), // Valeur d'initialisation (espace vierge)
new NTiProgramParameter("*ALL", 10), // Attributs
new NTiProgramParameter("List Object Information Userspace", 50) // Description
};
conn.CallProgram("QSYS", "QUSCRTUS", initialParameters);Appeler l'API QUSLOBJ
L'API attend des paramètres sous forme de Char de longueur fixe :
- Le User Space est une chaîne de 20 caractères (10 pour le nom, 10 pour la bibliothèque), d’où
.Append("QTEMP", 10)pour concaténer. - Le format des données est une chaîne de 8 caractères (
"OBJL0400"). - Le nom de l’objet et la bibliothèque forment une chaîne de 20 caractères (
.Append(libraryName, 10)). - Le type d’objet est une chaîne de 10 caractères (
*PGM,*FILE, etc.). - Enfin, la structure d’erreur est une chaîne de 16 caractères, vide ici.
var parameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // User Space
new NTiProgramParameter("OBJL0400", 8), // Format des données
new NTiProgramParameter(objectName, 10).Append(libraryName, 10), // Objet et bibliothèque
new NTiProgramParameter(objectType, 10), // Type d’objet
new NTiProgramParameter("", 16) // Structure d'erreur (optionnelle)
};
conn.CallProgram("QSYS", "QUSLOBJ", parameters);Récupérer les résultats
Utilisez l'API QUSRTVUS pour lire le contenu du User Space :
- Le premier paramètre désigne le User Space (
NTILOBJdansQTEMP). - Le second paramètre est une valeur fixe (
1), indiquant le format de récupération. - Le troisième paramètre correspond à la taille du User Space allouée, définie dynamiquement précédemment.
- Le dernier paramètre est une chaîne vide de la taille du User Space, marquée en sortie (
.AsOutput()), où les données seront écrites.
var finalParameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10),
new NTiProgramParameter(1),
new NTiProgramParameter(initialSize),
new NTiProgramParameter("", initialSize).AsOutput() // Récupération des données
};
conn.CallProgram("QSYS", "QUSRTVUS", finalParameters);Analyse des données retournées
Extrayez les informations générales du User Space. La structure générale des données pour les API de liste est disponible ici.
offsetToData- offset indiquant où commence la liste des objetsnumberOfEntries- nombre d'objets retournésentrySize- nombre d'octets alloués à chaque objetlistSize- taille totale des données dans le User Space
💡L'API QUSLOBJ peut renvoyer un code erreur
CPFxxxxsi elle rencontre un problème. Ce code est stocké dansparameters[4](5ᵉ paramètre de l'appel) et doit être extrait et nettoyé pour éviter les caractères de contrôle.
string error = new string(parameters[4].GetString(0, 16)
.Where(c => !char.IsControl(c))
.ToArray())
.Trim();
var listOfParameters = finalParameters[3]; // 4ème paramètre (indexé à partir de 0)
var offsetToData = listOfParameters.GetInt(0x7C); // Offset du début des données
var numberOfEntries = listOfParameters.GetInt(0x84); // Nombre d’entrées renvoyées
var entrySize = listOfParameters.GetInt(0x88); // Taille de chaque entrée
var listSize = listOfParameters.GetInt(0x80); // Taille totale des données
Si la taille totale dépasse l'espace alloué, supprimez le User Space, augmentez la taille et relancez l'appel :
var totalSize = listSize + offsetToData;
if (totalSize > initialSize)
{
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
initialSize = totalSize;
}
Si l’espace est suffisant, parcourez la liste des objets pour extraire leurs informations. Chaque objet retourné par l’API est stocké sous forme de blocs de données à une position bien précise. La structure est définie par IBM et suit une disposition stricte où chaque champ commence à un offset spécifique et a une longueur fixe.
L’approche consiste à parcourir la liste des objets en boucle, en se basant sur le nombre d’entrées retournées. À chaque itération, calculez la position exacte de l’objet courant en additionnant l’offset de début des données à l’index de l’entrée multiplié par la taille d’une entrée. Cela permet de pointer directement sur l’objet analysé.
Pour extraire les informations, utilisez les méthodes adaptées selon le type de données :
GetString(offset, length).Trim()- extrait une chaîne de caractères de longueur fixe en supprimant les espaces inutilesGetInt(offset)- récupère une valeur numérique binaire (4 octets)GetDTSTimestamp(offset)- méthode spécifique à NTi qui convertit un timestamp IBM i enDateTime.NET
var result = new List<ListObjectsInformation>();
if (numberOfEntries > 0)
{
for (int i = 0; i < numberOfEntries; i++)
{
// Calcul de l'offset de l'entrée actuelle dans le User Space
int currentOffset = offsetToData + (i * entrySize);
result.Add(new ListObjectsInformation
{
ObjectName = listOfParameters.GetString(currentOffset, 10).Trim(), // À l'offset 0, longueur 10
LibraryName = listOfParameters.GetString(currentOffset + 10, 10).Trim(), // À l'offset 10, longueur 10
ObjectType = listOfParameters.GetString(currentOffset + 20, 10).Trim(), // À l'offset 20, longueur 10
InformationStatus = listOfParameters.GetString(currentOffset + 30, 1).Trim(), // À l'offset 30, longueur 1
ExtendedAttribute = listOfParameters.GetString(currentOffset + 31, 10).Trim(), // À l'offset 31, longueur 10
TextDescription = listOfParameters.GetString(currentOffset + 41, 50).Trim(), // À l'offset 41, longueur 50
UserDefinedAttribute = listOfParameters.GetString(currentOffset + 91, 10).Trim(), // À l'offset 91, longueur 10
AspNumber = listOfParameters.GetInt(currentOffset + 108), // À l'offset 108, binary 4
Owner = listOfParameters.GetString(currentOffset + 112, 10).Trim(), // À l'offset 112, longueur 10
ObjectDomain = listOfParameters.GetString(currentOffset + 122, 2).Trim(), // À l'offset 122, longueur 2
CreationDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 124), // À l'offset 124, format spécifique NTi (timestamp)
ChangeDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 132), // À l'offset 132, format spécifique NTi (timestamp)
StorageStatus = listOfParameters.GetString(currentOffset + 140, 10).Trim() // À l'offset 140, longueur 10
});
}
}
Une fois les données extraites et stockées sous forme d'objets C#, supprimez le User Space temporaire :
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
return result;
Si aucune entrée n’a été récupérée, retournez une liste vide :
return new List<ListObjectsInformation>();Récapitulatif
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>();
}Et maintenant ?
- Appeler un programme - appel de programme RPG avec paramètres d'entrée/sortie
- Procédure stockée - appel de procédure stockée SQL avec Dapper et DataReader
- NTiProgramParameter - référence complète de la classe de paramètres