Per non cadere nelle “trappole” del sistema operativo
Il passaggio parametri ad un programma è un’operazione semplice ma che può nascondere delle insidie se non si conosce nei dettagli il comportamento del sistema operativo IBM i nei diversi contesti.
Il passaggio parametri può verificarsi appunto in contesti diversi:
- Esecuzione di una CALL da riga comandi
- Esecuzione di una CALL all’interno del parametro CMD del comando SBMJOB
- Esecuzione di una CALL all’interno di un programma e anche in questo caso bisognerebbe distinguere se i programmi in oggetto sono CL o RPG ed anche se ci troviamo in ambiente ILE o OPM
Cerchiamo di mettere in luce in questo articolo i lati oscuri del passaggio parametri ed alcuni trucchi per evitare di cadere nelle trappole del sistema operativo.
Esaminiamo le CALL eseguite da riga comandi o tramite il comando SBMJOB, in quanto le due situazioni hanno molti punti in comune.
Riga comandi e sottomissione in batch con parametri numerici
Eseguendo una CALL da riga comandi i parametri possono essere specificati solo come costanti.
Bisogna quindi subito chiarire un concetto importante: qualsiasi costante numerica viene convertita nel formato di tipo *DEC con lunghezza 15, 5 (15 interi di cui 5 decimali) e poi passata al programma chiamato.
Un esempio può chiarire meglio. Prendiamo il programma PGM1 che riceve come parametro una variabile di tipo packed 3, 0.
**free // ESEMPIO PROGRAMMA CHE RICEVE IN INGRESSO UN PARAMETRO NUMERICO // (c) MK1 MarkOneTools 2025 www.markonetools.it //--- MAIN INTERFACE --- dcl-pi ESPNUM; campo1 packed(3); end-pi; dcl-s msg char(52); msg = 'Il parametro è ' + %char(campo1); dsply msg; *inlr = *on;
Richiamando questo programma da riga comandi
CALL ESPNUM PARM(23)
si presenterà un eccezione non gestita non appena si tenterà di usare il parametro campo1
che conterrà quasi sicuramente dati decimali non validi
ID messaggio . . . . . : RNQ0907 Gravità . . . . . . . : 99 Tipo di messaggio . . : Interrogazione Data invio . . . . . . : 09/09/25 Ora invio . . . . . . : 22:03:45 Messaggio . . . : Si è verificato un errore di dati decimali (C G D F). Causa . . . . . : La procedura RPG ESPNUM nel programma MK1ILE/ESPNUM ha rilevato un errore di dati decimali nell'istruzione 13. Un valore compresso o a zonatura non contiene dati numerici validi. Una cifra e/o un segno non è valido.
Un trucco per ovviare a questo problema è specificare il parametro come costante esadecimale:
CALL ESPNUM PARM(X'023F')
DSPLY Il parametro è 23
invece se si volesse passare un numero negativo
CALL ESPNUM PARM(X'023D')
DSPLY Il parametro è -23
Da IBM i 7.4 nel parametro PARM del comando CALL è possibile specificare tipo e lunghezza del parametro, semplificando notevolmente il passaggio parametri da riga comando. Per esempio:
CALL PGM(ESPNUM) PARM((23 (*DEC 3 0)))
DSPLY Il parametro è 23
CALL PGM(ESPNUM) PARM((-23 (*DEC 3 0)))
DSPLY Il parametro è -23
Ma se lo stesso ESPNUM
dovessimo inserirlo in un programma CL e sottometterlo in batch?
Ovviamente nel programma CL non scriveremo di certo una costante nella PARM, bensì una variabile e quindi potremmo pensare di dormire sonni tranquilli, ma invece….
/* ESEMPIO SOTTOMISSIONE IN BATCH CALL CON PARAMETRO NUMERICO */ /* (c) MK1 MarkOneTools 2025 www.markonetools.it */ PGM DCL VAR(&CAMPO1) TYPE(*DEC) LEN(3 0) VALUE(23) /* N.B. una variabile packed definita 3, 0 occupa 2 byte di memoria, */ /* quindi la variabile alfanumerica da sovrapporre è definita lunga 2 */ /* ATTENZIONE: il parametro STG è disponibile da V5R4 */ DCL VAR(&CAMPO1CHR) TYPE(*CHAR) STG(*DEFINED) LEN(2) + DEFVAR(&CAMPO1) /* genera un errore */ SBMJOB CMD(CALL PGM(ESPNUM) PARM((&CAMPO1))) JOB(ES1ERR) /* soluzione corretta da IBM i 7.4 */ SBMJOB CMD(CALL PGM(ESPNUM) PARM((&CAMPO1 (*DEC 3 0)))) JOB(ES2) /* soluzione corretta prima di IBM i 7.4 */ SBMJOB CMD(CALL PGM(ESPNUM) PARM((&CAMPO1CHR))) JOB(ES3) ENDPGM
Se lo eseguiamo e controlliamo i lavori attivi batch, troveremo il JOB ES1ERR fermo in stato MSGW a causa di errore dati decimali. Arg! Come mai? Cosa è successo?
Semplice se si conosce bene come funziona il comando SBMJOB.
Quando si esegue CLESPNUM il comando SBMJOB fa sì che venga sottomesso un comando CALL come se fosse scritto così:
CALL PGM1 PARM(23)
Guarda caso è proprio lo stesso comando che abbiamo immesso da riga comandi all’inizio del nostro esempio e così come in quel caso abbiamo ricevuto un errore dati decimali, lo stesso errore (ed è bene che alla stessa causa corrisponda lo stesso effetto) lo riceviamo quando il comando SBMJOB immette una CALL con il medesimo principio.
Invece il job ES2 grazie alla possibilità disponibile da 7.4 di definire tipo e lunghezza del parametro verrà eseguito senza errori.
Prima della versione 7.4 (quando non si poteva specifica in PARM tipo e lunghezza del parametro) un trucco per bypassare questo problema è utilizzare una variabile alfanumerica la cui area di memoria si sovrappone esattamente alla variabile numerica che si desidera passare come parametro. Anche in questo caso la sottomissione del job ES3 ha esito positivo.
Riga comandi e sottomissione in batch con parametri alfanumerici
Se si pensa che i grattacapi derivano solo dall’uso dei campi numerici nel passaggio parametri, ci si sbaglia di grosso. Anche i campi alfanumerici posso causare forti mal di testa ai programmatori. Prima però è necessario spiegare come vengono passati i valori alfanumerici, per capire dove si origina il problema.
- Se il parametro è più piccolo di 32 bytes, verrà memorizzato in ogni caso in uno spazio di memoria di 32 bytes e riempito con dei blanks finali
- Ma (e qua bisogna porre attenzione!!!) se il parametro è maggiore di 32 bytes, viene allocato SOLTANTO lo spazio di memoria necessario a contenere la stringa “piena” di caratteri
Facciamo un esempio: il programma chiamato riceve un parametro alfanumerico definito char(20)
.
Se lo chiamiamo passando il valore 'ABCDEFG'
, non avrò nessun problema, poiché il sistema automaticamente riempirà di blanks la stringa fino ad arrivare ad occupare 32 bytes di memoria. Il programma chiamato userà di questa area di memoria solo i primi 20 bytes ed i restanti 12 bytes verranno ignorati.
Ma se il programma chiamato ricevesse un parametro alfanumerico definito char(40)
potremmo cominciare ad incorrere in qualche problema di sovrapposizione del buffer di memoria.
Chiamiamo questo programma passando sempre il valore 'ABCDEFG'
. Poiché il parametro è più lungo di 32 bytes verrà allocata solo la memoria necessaria a contenere il testo 'ABCDEFG'
con uno slot minimo di 32 bytes, ma poiché il programma chiamato legge 40 bytes dall’area di memoria dei parametri, ciò che si può trovare nel buffer dalla posizione 33 alla 40 è imprevedibile, in quanto il sistema ha occupato solo i primi 32 bytes.
Vediamo un piccolo esempio di un banale programma RPG che riceve un parametro alfanumerico di 40 bytes.
**free // ESEMPIO PROGRAMMA CHE RICEVE IN INGRESSO UN PARAMETRO ALFANUMERICO // (c) MK1 MarkOneTools 2025 www.markonetools.it //--- MAIN INTERFACE --- dcl-pi ESPALF; campo1 char(40); end-pi; dcl-s msg char(52); msg = 'Il parametro è ' + campo1; dsply msg; *inlr = *on;
Eseguito con una sottomissione in batch da un programma CL in 3 modi diversi:
/* ESEMPIO SOTTOMISSIONE IN BATCH CALL CON PARAMETRO ALFANUMERICO */ /* (c) MK1 MarkOneTools 2025 www.markonetools.it */ PGM DCL VAR(&CAMPO1) TYPE(*CHAR) LEN(40) VALUE('ABCDEFG') /* definisco una variabile più lunga di 1 bytes rispetto al parametro */ DCL VAR(&CAMPO1X) TYPE(*CHAR) LEN(41) /* genera un risultato imprevedibile nella porzione del parametro 33-40 bytes */ SBMJOB CMD(CALL PGM(ESPALF) PARM((&CAMPO1))) JOB(ES1) /* soluzione corretta da IBM i 7.4 */ SBMJOB CMD(CALL PGM(ESPALF) PARM((&CAMPO1 (*CHAR 40)))) JOB(ES2) /* soluzione corretta prima di IBM i 7.4 */ CHGVAR VAR(%SST(&CAMPO1X 1 40)) VALUE(&CAMPO1) /* riempio l'ultimo byte della variabile da 41 bytes così che il sistema */ /* sia costretto ad occupare tutti i bytes del buffer di memoria */ CHGVAR VAR(%SST(&CAMPO1X 41 1)) VALUE('*') /* sostituisco nella call il parametro originale da 40 bytes */ /* con la nuova variabile da 41 bytes */ /* il chiamato ne userà solo i primi 40 che saranno sicuramente riempiti */ /* di blanks fino al 40º byte */ SBMJOB CMD(CALL PGM(ESPALF) PARM((&CAMPO1X))) JOB(ES3) ENDPGM
Il risultato sarà il seguente
Il primo job ES1 mostra una porzione “sporca” nell’area di memoria del parametro, invece i job ES2 e ES3 espongono il risultato corretto.
Anche in questo caso da IBM i 7.4 la possibilità di specificare tipo e lunghezza del parametro evita qualsiasi tipo di problema. Prima della 7.4 si può ricorrere al trucco di definire nel chiamante la variabile da passare come parametro più lunga di 1 byte e valorizzare l’ultimo byte.