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 volumes de données variables, car il permet de récupérer de grandes quantités d'informations sans limitation stricte sur la taille des résultats.
Avec NTi, les API système s'appellent comme des programmes ordinaires, via CallProgram et NTiProgramParameter. Cet exemple explique comment implémenter une méthode 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 standard des API (optionnelle) |
La structure d'erreur standard des API commence par deux compteurs BINARY(4) : bytes provided (la taille de la structure que vous fournissez) et bytes available (renseigné par l'API en retour, 0 si aucune erreur), suivis de l'identifiant du message d'erreur en CHAR(7) à l'offset 8, puis d'un octet réservé. Nous y reviendrons à l'appel.
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 :
using System;
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 string? UserDefinedAttribute { get; set; }
public int AspNumber { get; set; }
public string? Owner { get; set; }
public string? ObjectDomain { get; set; }
public DateTime CreationDateTime { get; set; }
public DateTime ChangeDateTime { get; set; }
public string? StorageStatus { get; set; }
public string? CompressionStatus { get; set; }
public string? AllowChangeByProgram { get; set; }
public string? ChangedByProgram { get; set; }
public string? ObjectAuditing { get; set; }
public string? IsDigitallySigned { get; set; }
public string? IsSystemTrustedSigned { get; set; }
public string? HasMultipleSignatures { get; set; }
public int LibraryAspNumber { get; set; }
public string? SourceFileName { get; set; }
public string? SourceFileLibrary { get; set; }
public string? SourceFileMember { get; set; }
public string? SourceFileUpdatedDateTime { get; set; }
public string? CreatorUserProfile { get; set; }
public string? CreationSystem { get; set; }
public string? SystemLevel { get; set; }
public string? Compiler { get; set; }
public string? ObjectLevel { get; set; }
public string? IsUserChanged { get; set; }
public string? LicensedProgram { get; set; }
public string? PTF { get; set; }
public string? APAR { get; set; }
public string? PrimaryGroup { get; set; }
public string? IsOptimallyAligned { get; set; }
public int PrimaryAssociatedSpaceSize { get; set; }
}Ouvrir la connexion
Déclarez une instance de NTiConnection et ouvrez la connexion. L'ouverture asynchrone est la voie normale dans une application serveur. Open() reste disponible en synchrone :
using Aumerial.Data.Nti;
using var conn = new NTiConnection("server=serverName;user=userName;password=password");
await conn.OpenAsync(); // ou conn.Open() en synchroneDéfinir la méthode principale
La méthode reçoit la connexion ouverte en premier paramètre, puis libraryName, objectName et objectType :
public static List RetrieveObjectList(
NTiConnection conn,
string libraryName,
string objectName = "*ALL",
string objectType = "*ALL")
{
// Corps construit dans les étapes suivantes
} 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'objets 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.
QUSCRTUS attend six paramètres : le nom qualifié du User Space (CHAR(20)), son attribut étendu (CHAR(10)), sa taille initiale (BINARY(4)), la valeur d'initialisation de chaque octet (CHAR(1)), l'autorité publique accordée sur l'objet (CHAR(10), ici *ALL) et sa description (CHAR(50)).
int initialSize = 10000;
while (true)
{
// Création du User Space avec la taille courante
var initialParameters = new List
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // Nom et bibliothèque du User Space
new NTiProgramParameter("QUSLOBJ", 10), // Attribut étendu
new NTiProgramParameter(initialSize), // Taille initiale
new NTiProgramParameter(new byte[] { 0x00 }), // Valeur d'initialisation de chaque octet
new NTiProgramParameter("*ALL", 10), // Autorité publique
new NTiProgramParameter("List Object Information Userspace", 50) // Description
};
conn.CallProgram("QSYS", "QUSCRTUS", initialParameters);
💡 Le User Space vit dans le job commande. NTi ouvre deux jobs serveur par connexion : les requêtes SQL s'exécutent dans le job base de données (QZDASOINIT), les commandes CL et les appels de programme dans le job commande (QZRCSRVS), et chaque job possède sa propre QTEMP.
QUSCRTUS,QUSLOBJ,QUSRTVUSet leDLTUSRSPCfinal passent tous par le job commande. Ils voient donc le mêmeQTEMP/NTILOBJpendant toute la séquence. En revanche, une requête SQL de la même connexion ne verra jamais ce User Space. Les propriétésconn.DatabaseJobetconn.CommandJobpermettent d'identifier les deux jobs.
Appeler l'API QUSLOBJ
L'API attend des paramètres 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 de 16 octets : bytes provided en BINARY(4) initialisé à 16, bytes available en BINARY(4) renseigné en retour, puis 8 octets pour l'identifiant de message et l'octet réservé.
// Structure d'erreur : bytes provided = 16, bytes available = 0, zone message vide
var errorCode = new NTiProgramParameter(16).Append(0).Append("", 8);
var parameters = new List
{
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
errorCode // Structure d'erreur
};
conn.CallProgram("QSYS", "QUSLOBJ", parameters);
💡 Si l'API rencontre un problème (bibliothèque inexistante, autorité insuffisante), elle renseigne bytes available et écrit l'identifiant du message d'erreur
CPFxxxxen CHAR(7) à l'offset 8 de la structure : testezerrorCode.GetInt(4)puis lisezerrorCode.GetString(8, 7). Autre approche : passer bytes provided = 0. L'API signale alors l'erreur par un message d'échappement et l'appel lève uneNTiCommandExceptionportant la pile de messages du job.
if (errorCode.GetInt(4) > 0) // bytes available : 0 = aucune erreur
{
string messageId = errorCode.GetString(8, 7); // Identifiant du message, ex. CPF9810
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
throw new InvalidOperationException($"QUSLOBJ a signalé l'erreur {messageId}");
}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 deuxième paramètre est la position de départ de la lecture, exprimée en base 1 : la valeur
1signifie « lire à partir du premier octet du User Space ». - Le troisième paramètre est le nombre d'octets à lire, ici la taille allouée.
- Le dernier paramètre est le récepteur, 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
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // User Space
new NTiProgramParameter(1), // Position de départ (base 1)
new NTiProgramParameter(initialSize), // Nombre d'octets à lire
new NTiProgramParameter("", initialSize).AsOutput() // Récepteur
};
conn.CallProgram("QSYS", "QUSRTVUS", finalParameters); Analyser les données retournées
Extrayez d'abord l'en-tête générique du User Space. La structure générale des données pour les API de liste est documentée ici.
offsetToData- offset où commence la liste des objetslistSize- taille totale de la liste dans le User SpacenumberOfEntries- nombre d'objets retournésentrySize- nombre d'octets alloués à chaque objet
var receiver = finalParameters[3]; // 4e paramètre (indexé à partir de 0)
var offsetToData = receiver.GetInt(0x7C); // Offset du début de la liste
var listSize = receiver.GetInt(0x80); // Taille totale de la liste
var numberOfEntries = receiver.GetInt(0x84); // Nombre d'entrées renvoyées
var entrySize = receiver.GetInt(0x88); // Taille de chaque entrée
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;
continue; // La boucle recrée le User Space avec la nouvelle taille
}
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 (BINARY(4))GetDTSTimestamp(offset)- méthode spécifique à NTi qui convertit un horodatage *DTS IBM i (8 octets) enDateTime.NET
var result = new List();
for (int i = 0; i < numberOfEntries; i++)
{
// Offset de l'entrée courante dans le User Space
int currentOffset = offsetToData + (i * entrySize);
result.Add(new ListObjectsInformation
{
ObjectName = receiver.GetString(currentOffset, 10).Trim(), // Offset 0, CHAR(10)
LibraryName = receiver.GetString(currentOffset + 10, 10).Trim(), // Offset 10, CHAR(10)
ObjectType = receiver.GetString(currentOffset + 20, 10).Trim(), // Offset 20, CHAR(10)
InformationStatus = receiver.GetString(currentOffset + 30, 1).Trim(), // Offset 30, CHAR(1)
ExtendedAttribute = receiver.GetString(currentOffset + 31, 10).Trim(), // Offset 31, CHAR(10)
TextDescription = receiver.GetString(currentOffset + 41, 50).Trim(), // Offset 41, CHAR(50)
UserDefinedAttribute = receiver.GetString(currentOffset + 91, 10).Trim(), // Offset 91, CHAR(10)
AspNumber = receiver.GetInt(currentOffset + 108), // Offset 108, BINARY(4)
Owner = receiver.GetString(currentOffset + 112, 10).Trim(), // Offset 112, CHAR(10)
ObjectDomain = receiver.GetString(currentOffset + 122, 2).Trim(), // Offset 122, CHAR(2)
CreationDateTime = receiver.GetDTSTimestamp(currentOffset + 124), // Offset 124, horodatage *DTS
ChangeDateTime = receiver.GetDTSTimestamp(currentOffset + 132), // Offset 132, horodatage *DTS
StorageStatus = receiver.GetString(currentOffset + 140, 10).Trim(), // Offset 140, CHAR(10)
CompressionStatus = receiver.GetString(currentOffset + 150, 1).Trim() // Offset 150, CHAR(1)
// Le récapitulatif mappe la suite des champs du format OBJL0400
});
}
💡 Attention aux longueurs : à l'offset 150, le statut de compression est un CHAR(1). Le lire sur 10 octets déborderait sur les champs suivants (allow change by program à l'offset 151, changed by program à l'offset 152).
Une fois les données extraites et stockées sous forme d'objets C#, supprimez le User Space temporaire et retournez le résultat. La boucle for gère naturellement le cas « aucune entrée » en retournant une liste vide :
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
return result;Récapitulatif
La méthode complète, prête à compiler. La connexion est passée en paramètre et le mapping des entrées est isolé dans une méthode privée, réutilisée par la variante asynchrone ci-dessous. La classe ListObjectsInformation est celle de l'étape 3.
using System;
using System.Collections.Generic;
using Aumerial.Data.Nti;
public static class ObjectLister
{
public static List RetrieveObjectList(
NTiConnection conn,
string libraryName,
string objectName = "*ALL",
string objectType = "*ALL")
{
int initialSize = 10000;
while (true)
{
// 1- Création du User Space
var initialParameters = new List
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // Nom et bibliothèque
new NTiProgramParameter("QUSLOBJ", 10), // Attribut étendu
new NTiProgramParameter(initialSize), // Taille initiale
new NTiProgramParameter(new byte[] { 0x00 }), // Valeur d'initialisation
new NTiProgramParameter("*ALL", 10), // Autorité publique
new NTiProgramParameter("List Object Information Userspace", 50) // Description
};
conn.CallProgram("QSYS", "QUSCRTUS", initialParameters);
// 2- Appel de l'API QUSLOBJ
var errorCode = new NTiProgramParameter(16).Append(0).Append("", 8); // bytes provided = 16
var parameters = new List
{
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
errorCode // Structure d'erreur
};
conn.CallProgram("QSYS", "QUSLOBJ", parameters);
if (errorCode.GetInt(4) > 0) // bytes available : 0 = aucune erreur
{
string messageId = errorCode.GetString(8, 7); // Identifiant du message, ex. CPF9810
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
throw new InvalidOperationException($"QUSLOBJ a signalé l'erreur {messageId}");
}
// 3- Lecture du User Space
var finalParameters = new List
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // User Space
new NTiProgramParameter(1), // Position de départ (base 1)
new NTiProgramParameter(initialSize), // Nombre d'octets à lire
new NTiProgramParameter("", initialSize).AsOutput() // Récepteur
};
conn.CallProgram("QSYS", "QUSRTVUS", finalParameters);
var receiver = finalParameters[3];
var offsetToData = receiver.GetInt(0x7C); // Offset du début de la liste
var listSize = receiver.GetInt(0x80); // Taille totale de la liste
var numberOfEntries = receiver.GetInt(0x84); // Nombre d'entrées renvoyées
var entrySize = receiver.GetInt(0x88); // Taille de chaque entrée
// 4- Agrandissement du User Space si nécessaire
var totalSize = listSize + offsetToData;
if (totalSize > initialSize)
{
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
initialSize = totalSize;
continue;
}
// 5- Mapping des entrées puis suppression du User Space
var result = ParseEntries(receiver, offsetToData, numberOfEntries, entrySize);
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
return result;
}
}
private static List ParseEntries(
NTiProgramParameter receiver, int offsetToData, int numberOfEntries, int entrySize)
{
var result = new List();
for (int i = 0; i < numberOfEntries; i++)
{
int currentOffset = offsetToData + (i * entrySize);
result.Add(new ListObjectsInformation
{
ObjectName = receiver.GetString(currentOffset, 10).Trim(),
LibraryName = receiver.GetString(currentOffset + 10, 10).Trim(),
ObjectType = receiver.GetString(currentOffset + 20, 10).Trim(),
InformationStatus = receiver.GetString(currentOffset + 30, 1).Trim(),
ExtendedAttribute = receiver.GetString(currentOffset + 31, 10).Trim(),
TextDescription = receiver.GetString(currentOffset + 41, 50).Trim(),
UserDefinedAttribute = receiver.GetString(currentOffset + 91, 10).Trim(),
AspNumber = receiver.GetInt(currentOffset + 108),
Owner = receiver.GetString(currentOffset + 112, 10).Trim(),
ObjectDomain = receiver.GetString(currentOffset + 122, 2).Trim(),
CreationDateTime = receiver.GetDTSTimestamp(currentOffset + 124),
ChangeDateTime = receiver.GetDTSTimestamp(currentOffset + 132),
StorageStatus = receiver.GetString(currentOffset + 140, 10).Trim(),
CompressionStatus = receiver.GetString(currentOffset + 150, 1).Trim(), // CHAR(1), et non 10
AllowChangeByProgram = receiver.GetString(currentOffset + 151, 1).Trim(),
ChangedByProgram = receiver.GetString(currentOffset + 152, 1).Trim(),
ObjectAuditing = receiver.GetString(currentOffset + 153, 10).Trim(),
IsDigitallySigned = receiver.GetString(currentOffset + 163, 1).Trim(),
IsSystemTrustedSigned = receiver.GetString(currentOffset + 164, 1).Trim(),
HasMultipleSignatures = receiver.GetString(currentOffset + 165, 1).Trim(),
LibraryAspNumber = receiver.GetInt(currentOffset + 168),
SourceFileName = receiver.GetString(currentOffset + 172, 10).Trim(),
SourceFileLibrary = receiver.GetString(currentOffset + 182, 10).Trim(),
SourceFileMember = receiver.GetString(currentOffset + 192, 10).Trim(),
SourceFileUpdatedDateTime = receiver.GetString(currentOffset + 202, 13).Trim(),
CreatorUserProfile = receiver.GetString(currentOffset + 215, 10).Trim(),
CreationSystem = receiver.GetString(currentOffset + 225, 8).Trim(),
SystemLevel = receiver.GetString(currentOffset + 233, 9).Trim(),
Compiler = receiver.GetString(currentOffset + 242, 16).Trim(),
ObjectLevel = receiver.GetString(currentOffset + 258, 8).Trim(),
IsUserChanged = receiver.GetString(currentOffset + 266, 1).Trim(),
LicensedProgram = receiver.GetString(currentOffset + 267, 16).Trim(),
PTF = receiver.GetString(currentOffset + 283, 10).Trim(),
APAR = receiver.GetString(currentOffset + 293, 10).Trim(),
PrimaryGroup = receiver.GetString(currentOffset + 303, 10).Trim(),
IsOptimallyAligned = receiver.GetString(currentOffset + 315, 1).Trim(),
PrimaryAssociatedSpaceSize = receiver.GetInt(currentOffset + 316)
});
}
return result;
}
} Variante async
Toutes les briques utilisées ici existent en asynchrone réel : OpenAsync, CallProgramAsync et ExecuteClCommandAsync acceptent un jeton d'annulation et n'exécutent aucune I/O synchrone. C'est la voie recommandée dans une application serveur. Ajoutez using System.Threading; et using System.Threading.Tasks; en tête de fichier, puis complétez la classe ObjectLister :
public static async Task> RetrieveObjectListAsync(
NTiConnection conn,
string libraryName,
string objectName = "*ALL",
string objectType = "*ALL",
CancellationToken cancellationToken = default)
{
int initialSize = 10000;
while (true)
{
// 1- Création du User Space
var initialParameters = new List
{
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)
};
await conn.CallProgramAsync("QSYS", "QUSCRTUS", initialParameters, cancellationToken);
// 2- Appel de l'API QUSLOBJ
var errorCode = new NTiProgramParameter(16).Append(0).Append("", 8);
var parameters = new List
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10),
new NTiProgramParameter("OBJL0400", 8),
new NTiProgramParameter(objectName, 10).Append(libraryName, 10),
new NTiProgramParameter(objectType, 10),
errorCode
};
await conn.CallProgramAsync("QSYS", "QUSLOBJ", parameters, cancellationToken);
if (errorCode.GetInt(4) > 0)
{
string messageId = errorCode.GetString(8, 7);
await conn.ExecuteClCommandAsync("DLTUSRSPC QTEMP/NTILOBJ", cancellationToken);
throw new InvalidOperationException($"QUSLOBJ a signalé l'erreur {messageId}");
}
// 3- Lecture du User Space
var finalParameters = new List
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10),
new NTiProgramParameter(1),
new NTiProgramParameter(initialSize),
new NTiProgramParameter("", initialSize).AsOutput()
};
await conn.CallProgramAsync("QSYS", "QUSRTVUS", finalParameters, cancellationToken);
var receiver = finalParameters[3];
var offsetToData = receiver.GetInt(0x7C);
var listSize = receiver.GetInt(0x80);
var numberOfEntries = receiver.GetInt(0x84);
var entrySize = receiver.GetInt(0x88);
// 4- Agrandissement du User Space si nécessaire
var totalSize = listSize + offsetToData;
if (totalSize > initialSize)
{
await conn.ExecuteClCommandAsync("DLTUSRSPC QTEMP/NTILOBJ", cancellationToken);
initialSize = totalSize;
continue;
}
// 5- Mapping des entrées puis suppression du User Space
var result = ParseEntries(receiver, offsetToData, numberOfEntries, entrySize);
await conn.ExecuteClCommandAsync("DLTUSRSPC QTEMP/NTILOBJ", cancellationToken);
return result;
}
}
💡 Annuler le jeton pendant un échange casse la connexion par contrat (la trame en cours est perdue) et se traduit par une
OperationCanceledExceptionportant votre jeton. Il faut alors rouvrir la connexion.
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