L'IBM i offre numerose API di sistema che permettono di accedere alle risorse del sistema operativo. Alcune di queste API restituiscono direttamente dati utilizzabili sotto forma di strutture o flussi, mentre altre richiedono l’uso di uno User Space per memorizzare i risultati prima di poterli elaborare.
L’API QUSLOBJ (List Objects) appartiene a questa seconda categoria: non restituisce direttamente l’elenco degli oggetti in uscita, ma scrive i risultati in uno User Space, una zona di memoria temporanea. Questo meccanismo è comune per le API di IBM i che gestiscono insiemi dinamici di dati, poiché consente di recuperare grandi volumi di informazioni senza un limite rigido alla dimensione del risultato.
L’obiettivo di questo tutorial è spiegarti come implementare correttamente una chiamata a questa API da .NET utilizzando NTi, e come interpretare la documentazione IBM per costruire correttamente le strutture di input e analizzare i dati restituiti.
Il codice sorgente completo dell’implementazione è disponibile alla fine di questo tutorial..
Puoi seguire ogni passaggio per comprenderne il funzionamento e farvi riferimento per una visione d’insieme o per l’uso diretto.
Passo 1 – Scelta di un’API: QSYS.QUSLOBJ
Per utilizzare l’API QUSLOBJ, la prima cosa da fare è consultare la documentazione ufficiale.I passaggi principali da seguire sono:
- Creare uno User Space per memorizzare i risultati tramite l’API QUSCRTUS
- Chiamare l’API QUSLOBJ specificando i parametri di input richiesti
- Leggere i risultati memorizzati nello User Space tramite l’API QUSRTVUS
- Scorrere gli oggetti restituiti e convertirli in oggetti .NET
Passo 2 - Lettura della documentazione IBM
Prima di implementare la chiamata a QUSLOBJ, è importante capire quali parametri si aspetta questa API e cosa restituisce in output.
###📥Parametri di input:
Parametro | Tipo | Direzione | Descrizione |
---|---|---|---|
User Space | Char(20) | Input | Nome (10 caratteri) e libreria (10 caratteri) dove memorizzare l’elenco |
Format Name | Char(8) | Input | Formato dei dati (es. OBJL0400 nel nostro caso) |
Object & LibraryName | Char(20) | Input | Nome dell’oggetto (10 caratteri) e libreria (10 caratteri) |
Object Type | Char(10) | Input | Tipo di oggetto (*FILE, *PGM, ecc.) |
Error Code | Char(* ) | Input / Output | Struttura di errore (opzionale) |
###📥Parametri di output:
L’API QUSLOBJ non restituisce direttamente l’elenco degli oggetti come parametri di output. I risultati vengono scritti in uno User Space, un’area di memoria temporanea che deve essere specificata come input.
Uno User Space è un oggetto di sistema utilizzato per memorizzare grandi quantità di dati, in particolare i risultati restituiti da alcune API IBM i.
Viene creato prima della chiamata a QUSLOBJ utilizzando l’API QUSCRTUS. Una volta eseguita la chiamata, gli oggetti vengono scritti nello User Space. Per leggerli, si utilizza l’API QUSRTVUS, che permette di accedere al suo contenuto.
Non entreremo qui nei dettagli dell’implementazione, ma è importante ricordare che tutti i dati restituiti sono memorizzati nello User Space e devono essere analizzati rispettando la struttura definita da IBM.
Passo 3 - Implementazione del metodo di chiamata a QUSLOBJ con NTi
Ora sappiamo come funziona QUSLOBJ e come interpretare i risultati memorizzati nello User Space. Procediamo quindi con l’implementazione del nostro metodo in C# utilizzando NTi.
1️⃣ Definizione del modello dati
Per prima cosa, dobbiamo definire un modello dati che rappresenti ciascun oggetto restituito dall’API. Utilizziamo il formato OBJL0400 per ottenere informazioni dettagliate sugli oggetti. Creeremo quindi una classe C# che riflette questa struttura:
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️⃣ Definizione del metodo principale
Definiamo ora il metodo che costruiremo per recuperare un elenco di oggetti da una libreria specifica. L’API richiede come parametri libraryName
, objectName
e objectType
.
public static List<ListObjectsInformation> RetrieveObjectList(
string libraryName,
string objectName = "*ALL",
string objectType = "*ALL"
)
{
}
3️⃣ Creazione dello User Space
Prima di chiamare QUSLOBJ, è necessario creare uno User Space in QTEMP, come già accennato. Questo spazio ci servirà per memorizzare l’elenco degli oggetti restituiti.
L’API QUSCRTUS viene utilizzata per creare lo User Space. Richiede diversi parametri, tra cui:
- Il nome dello User Space, in questo caso NTILOBJ in QTEMP
- Una dimensione iniziale, che verrà adattata dinamicamente in base ai dati restituiti
- Un valore di inizializzazione, in questo caso 0x00, che indica uno spazio vuoto
- Un attributo generico e una descrizione
Poiché la dimensione dei dati restituiti non è nota in anticipo, viene utilizzato un ciclo "while" per verificare se lo spazio allocato è sufficiente, e aumentarlo se necessario:
int initialSize = 10000;
bool spaceAllocated = false;
while (!spaceAllocated)
{
// Creazione dello User Space con la dimensione corrente
var initialParameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // Nome dello User Space
new NTiProgramParameter("QUSLOBJ", 10), // Nome estensione
new NTiProgramParameter(initialSize), // Dimensione iniziale
new NTiProgramParameter(new byte[] { 0x00 }), // Valore di inizializzazione
new NTiProgramParameter("*ALL", 10), // Attributi
new NTiProgramParameter("List Object Information Userspace", 50) // Descrizione
};
conn.CallProgram("QSYS", "QUSCRTUS", initialParameters);
4️⃣ Chiamata all’API QUSLOBJ dopo la creazione dello User Space
Una volta creato lo User Space, possiamo chiamare l’API QUSLOBJ per recuperare l’elenco degli oggetti. L’API richiede diversi parametri di input, che devono essere formattati correttamente per evitare errori.
L’API QUSLOBJ si aspetta parametri sotto forma di stringhe Char a lunghezza fissa:
- Lo User Space è una stringa di 20 caratteri (10 per il nome, 10 per la libreria), da cui l’uso di
.Append("QTEMP", 10)
per concatenarli - Il formato dei dati è una stringa di 8 caratteri (es. "OBJL0400")
- Il nome dell’oggetto e la libreria sono una stringa di 20 caratteri (
.Append(libraryName, 10)
). - Il tipo di oggetto è una stringa di 10 caratteri (*PGM, *FILE, ecc.)
- Infine, la struttura di errore è una stringa di 16 caratteri, qui lasciata vuota
Una volta definiti questi parametri, si procede con l’esecuzione dell’API:
var parameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10), // User Space
new NTiProgramParameter("OBJL0400", 8), // Formato dei dati
new NTiProgramParameter(objectName, 10).Append(libraryName, 10), // Oggetto e libreria
new NTiProgramParameter(objectType, 10), // Tipo di oggetto
new NTiProgramParameter("", 16) // Struttura di errore (opzionale)
};
conn.CallProgram("QSYS", "QUSLOBJ", parameters);
5️⃣ Recupero dei risultati
Una volta eseguita la chiamata a QUSLOBJ, i risultati vengono memorizzati nello User Space. Per recuperarli, si utilizza l’API QUSRTVUS, che consente di leggerne il contenuto.
- Il primo parametro si riferisce sempre allo User Space (in questo caso NTILOBJ nella libreria QTEMP)
- Il secondo parametro è un valore fisso (1), che indica il formato di recupero
- Il terzo parametro rappresenta la dimensione dello User Space allocato, definita dinamicamente in precedenza
- L’ultimo parametro è una stringa vuota della stessa dimensione dello User Space, marcata come output tramite
(.AsOutput())
, dove verranno scritti i dati
var finalParameters = new List<NTiProgramParameter>
{
new NTiProgramParameter("NTILOBJ", 10).Append("QTEMP", 10),
new NTiProgramParameter(1),
new NTiProgramParameter(initialSize),
new NTiProgramParameter("", initialSize).AsOutput() // Recupero dei dati
};
conn.CallProgram("QSYS", "QUSRTVUS", finalParameters);
6️⃣ Analisi dei dati restituiti
Ora dobbiamo estrarre e analizzare i dati contenuti nello User Space. I risultati si trovano nell’ultimo parametro, quello definito come output durante la chiamata a QUSRTVUS. Recuperiamo quindi le informazioni generali: l’offset dei dati, il numero di voci restituite e la dimensione di ogni voce.
Questi valori ci permettono di capire dove e come scorrere l’elenco degli oggetti restituiti. La struttura generale dei dati per le API di tipo “lista” è documentata qui.
- L’offset dell’inizio dei dati
offsetToData
indica dove inizia realmente l’elenco degli oggetti. - Il numero di voci restituite
numberOfEntries
indica quanti oggetti sono elencati. - La dimensione di una voce
entrySize
ci permette di sapere quanti byte sono assegnati a ogni oggetto. - La dimensione totale dei dati
listSize
corrisponde allo spazio occupato nello User Space.
L’API QUSLOBJ può restituire un codice di errore del tipo CPFxxxx se si verifica un problema. Questo codice viene memorizzato in
parameters[4]
, ossia nel quinto parametro della chiamata. È quindi necessario estrarlo e pulirlo, per rimuovere eventuali caratteri di controllo.
string error = new string(parameters[4].GetString(0, 16)
.Where(c => !char.IsControl(c))
.ToArray())
.Trim();
var listOfParameters = finalParameters[3]; // 4° parametro (indice a partire da 0)
var offsetToData = listOfParameters.GetInt(0x7C); // Offset dell’inizio dei dati
var numberOfEntries = listOfParameters.GetInt(0x84); // Numero di voci restituite
var entrySize = listOfParameters.GetInt(0x88); // Dimensione di ogni voce
var listSize = listOfParameters.GetInt(0x80); // Dimensione totale dei dati
Successivamente, verifichiamo che lo spazio allocato nello User Space sia sufficiente. Se la dimensione totale dei dati listSize
+ offsetToData
supera la dimensione iniziale allocata, è necessario eliminare lo User Space, aumentare la dimensione e rilanciare la chiamata a QUSLOBJ.
var totalSize = listSize + offsetToData;
if (totalSize > initialSize)
{
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
initialSize = totalSize;
}
Se lo spazio è sufficiente, si scorre l’elenco degli oggetti per estrarre le loro informazioni. Ogni oggetto restituito dall’API è memorizzato sotto forma di blocchi di dati in una posizione ben precisa. La struttura è definita da IBM e segue un layout rigido, in cui ogni campo inizia a un offset specifico e ha una lunghezza fissa.
L’approccio consiste nel scorrere l’elenco degli oggetti in un ciclo, basandosi sul numero di voci restituite. A ogni iterazione, si calcola la posizione esatta dell’oggetto corrente sommando con l’indice dell’oggetto moltiplicato per la dimensione di una voce. Questo ci permette di puntare direttamente all’oggetto da analizzare.
Per estrarre le informazioni, utilizziamo metodi adattati in base al tipo di dato:
GetString(offset, length).Trim()
per i campi testuali, estrae una stringa a lunghezza fissa rimuovendo gli spazi inutiliGetInt(offset)
per valori numerici binari memorizzati in 4 o 8 byte, senza specificare la lunghezzaGetDTSTimestamp(offset)
è un metodo specifico di NTi che consente di convertire un timestamp IBM i in una data utilizzabile in .NET
Una volta estratti i valori, vengono memorizzati in un oggetto ListObjectsInformation
, quindi aggiunti alla lista dei risultati.
var result = new List<ListObjectsInformation>();
if (numberOfEntries > 0)
{
for (int i = 0; i < numberOfEntries; i++)
{
// Calcolo dell'offset dell'elemento corrente nello User Space
int currentOffset = offsetToData + (i * entrySize);
result.Add(new ListObjectsInformation
{
ObjectName = listOfParameters.GetString(currentOffset, 10).Trim(), // All'offset 0, lunghezza 10
LibraryName = listOfParameters.GetString(currentOffset + 10, 10).Trim(), // All'offset 10, lunghezza 10
ObjectType = listOfParameters.GetString(currentOffset + 20, 10).Trim(), // All'offset 20, lunghezza 10
InformationStatus = listOfParameters.GetString(currentOffset + 30, 1).Trim(), // All'offset 30, lunghezza 1
ExtendedAttribute = listOfParameters.GetString(currentOffset + 31, 10).Trim(), // All'offset 31, lunghezza 10
TextDescription = listOfParameters.GetString(currentOffset + 41, 50).Trim(), // All'offset 41, lunghezza 50
UserDefinedAttribute = listOfParameters.GetString(currentOffset + 91, 10).Trim(), // All'offset 91, lunghezza 10
AspNumber = listOfParameters.GetInt(currentOffset + 108), // All'offset 108, valore binario (4 byte)
Owner = listOfParameters.GetString(currentOffset + 112, 10).Trim(), // All'offset 112, lunghezza 10
ObjectDomain = listOfParameters.GetString(currentOffset + 122, 2).Trim(), // All'offset 122, lunghezza 2
CreationDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 124), // All'offset 124, timestamp specifico NTi
ChangeDateTime = listOfParameters.GetDTSTimestamp(currentOffset + 132), // All'offset 132, timestamp specifico NTi
StorageStatus = listOfParameters.GetString(currentOffset + 140, 10).Trim() // All'offset 140, lunghezza 10
});
}
}
Una volta che tutti i dati sono stati estratti e memorizzati sotto forma di oggetti C#, è importante liberare la memoria eliminando lo User Space temporaneo. Anche se lo User Space si trova in QTEMP (e quindi viene eliminato automaticamente alla fine della sessione), è consigliabile eliminarlo subito dopo l’utilizzo, per evitare un sovraccarico inutile nel caso di chiamate ripetute.
Per eliminare lo User Space specificato, utilizziamo il comando CL DLTUSRSPC. Una volta eseguita l’eliminazione, il metodo restituisce l’elenco degli oggetti estratti. Se non è stato trovato alcun oggetto, viene restituita una lista vuota, per garantire una gestione pulita dei risultati ed evitare possibili errori nei processi successivi.
conn.ExecuteClCommand("DLTUSRSPC QTEMP/NTILOBJ");
return result;
Se non è stata recuperata alcuna voce, restituiamo un elenco vuoto:
return new List<ListObjectsInformation>();
Conclusione
Con quest’ultimo passaggio, il nostro metodo è completamente operativo. Segue un processo strutturato che permette di:
- Creare dinamicamente uno User Space
- Chiamare QUSLOBJ per recuperare l’elenco degli oggetti
- Estrarre i dati rispettando la struttura definita da IBM
- Gestire dinamicamente la memoria, adattando la dimensione dello User Space se necessario
- Trasformare i risultati in oggetti C# utilizzabili
- Eliminare sistematicamente lo User Space dopo l’utilizzo
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- Creazione dello 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- Chiamata all’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. Recupero dei dati
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>();
}
Quentin Destrade