do more with less
news, esperienze, esempi da condividere e qualcosa su di me

venerdì 20 aprile 2012

Call RPG Subrutine from .NET

Oggi ho provato a verificare come fosse possibile chiamare una subrutine, scritta in RPG in AS/400 da un programma scritto in C#. Il risultato del test è stato positivo anche se, per il momento, mi sono limitato a una chiamata a un metodo che prevedeva un parametro di input e uno di output (entrambi di tipo stringa). Partendo da un esempio presente in rete (Calling AS/400 (AS400) RPG Programs From ASP.NET) ho realizzato un banale programma in RPG chiamato HELLORPG. Il listato del programma è il seguente:

   1: *-------------------------------------------------------------------------
   2: *                                                                        -
   3: *  Nome Programma . : HELLORPG - Ritorna Hello + string passata          -
   4: *                                                                        -
   5: *-------------------------------------------------------------------------
   6: *?INDICATORI:
   7: *-------------------------------------------------------------------------
   8:  PJNAME          S            128A
   9:  PJRESULT        S            128A
  10:  
  11:                    ExSr      DEFCAM
  12:                    ExSr      ROUT01
  13:                    SetOn                                        LR
  14: *-------------------------------------------------------------------------
  15: ** Flusso principale
  16: *-------------------------------------------------------------------------
  17:      ROUT01        BegSr
  18: *
  19:                    Eval      PJRESULT = 'Hello ' + %Trim(PJNAME)
  20: *
  21:                    EndSr
  22: *-------------------------------------------------------------------------
  23: * Definizione campi base
  24: *-------------------------------------------------------------------------
  25:      DEFCAM        BegSr
  26: *
  27:      *Entry        PList
  28:                    Parm                    PJNAME
  29:                    Parm                    PJRESULT
  30: *
  31:                    Clear                   PJRESULT
  32:                    EndSr
  33:  

Lato .NET ho creato una console application per invocare la subrutine RPG. Il contenuto della classe Program.cs, creata dal template del progetto di tipo Console Application in C# è il seguente:



   1: static void Main(string[] args)
   2: {
   3:     Console.WriteLine(InvokeRPG("from AS/400"));
   4:     Console.ReadLine();
   5: }

Internamente alla classe è presente anche un metodo InvokeRPG che si occupa di predisporre la chiamata al verso AS/400. Il codice del metodo prevede la creazione di un’istanza a una classe di help che si occupa di configurare il sistema di riferimento, predisporre la chiamata, eseguirla e tornare gli eventuali valori di ritorno.


Il metodo InvokeRPG è riportato di seguito:



   1: static string InvokeRPG(string name)
   2: {
   3:     //Istanza alla classe di helper
   4:     AS400Program program = new AS400Program(RPGConsole.Properties.Settings.Default.DVConnection);
   5:  
   6:     //Istanza al converter per le stringhe
   7:     cwbx.StringConverter stringConverter = new cwbx.StringConverter();
   8:  
   9:     //Creazione collezione dei parametri e loro valorizzazione
  10:     cwbx.ProgramParameters parameters = new cwbx.ProgramParameters();
  11:  
  12:     parameters.Append("PGNAME", cwbx.cwbrcParameterTypeEnum.cwbrcInput, 128);
  13:     stringConverter.Length = 128;
  14:     parameters["PGNAME"].Value = stringConverter.ToBytes(name);
  15:  
  16:     parameters.Append("PGRESULT", cwbx.cwbrcParameterTypeEnum.cwbrcOutput, 128);
  17:  
  18:     //Esecuzione della chiamata
  19:     program.Invoke(true, ref parameters);
  20:  
  21:     //Conversione del valore di ritorno
  22:     string result = stringConverter.FromBytes(parameters["PGRESULT"].Value);
  23:  
  24:     program.Close();
  25:  
  26:     return result;
  27: }

La classe di helper è la seguente:



   1: public class AS400Program
   2: {
   3:     private bool as400Configured = false;
   4:     private cwbx.AS400System as400;
   5:     private cwbx.Program program;
   6:  
   7:     //Formato della configurazione: as400;userid;password;library;program
   8:     public AS400Program(string configuration)
   9:     {
  10:         if (!as400Configured)
  11:         {
  12:             string[] settings = configuration.Split(';');
  13:             if (settings.Length == 5)
  14:             {
  15:                 as400 = new cwbx.AS400System();
  16:                 program = new cwbx.Program();
  17:  
  18:                 as400.Define(settings[0]);
  19:                 program.system = as400;
  20:                 program.system.UserID = settings[1];
  21:                 program.system.Password = settings[2];
  22:                 program.LibraryName = settings[3];
  23:                 program.ProgramName = settings[4];
  24:                 as400Configured = true;
  25:             }
  26:             else
  27:             {
  28:                 throw (new Exception(
  29:                     string.Format("Invalid AS400Program configuration string : [{0}]", configuration)));
  30:             }
  31:         }
  32:     }
  33:  
  34:     //Predisposizione e invocazione della subroutine
  35:     public bool Invoke(bool throwInsteadOfReturn, ref cwbx.ProgramParameters parameters)
  36:     {
  37:         bool success = false;
  38:  
  39:         try
  40:         {
  41:             //Gestione della connessione
  42:             if (as400.IsConnected(cwbx.cwbcoServiceEnum.cwbcoServiceRemoteCmd) == 0)
  43:             {
  44:                 //Disconnesione e riconnesione
  45:                 as400.Disconnect(cwbx.cwbcoServiceEnum.cwbcoServiceAll);
  46:                 as400.Connect(cwbx.cwbcoServiceEnum.cwbcoServiceRemoteCmd);
  47:  
  48:                 if (as400.IsConnected(cwbx.cwbcoServiceEnum.cwbcoServiceRemoteCmd) == 0)
  49:                 {
  50:                     //TODO: da gestire
  51:                 }
  52:             }
  53:  
  54:             program.Call(parameters);
  55:  
  56:             success = true;
  57:         }
  58:         catch (Exception e)
  59:         {
  60:             //Gestione errori AS/400
  61:             if (as400.Errors.Count > 0)
  62:             {
  63:                 foreach (cwbx.Error error in as400.Errors)
  64:                 {
  65:                     //TODO: da gestire
  66:                 }
  67:             }
  68:  
  69:             if (program.Errors.Count > 0)
  70:             {
  71:                 foreach (cwbx.Error error in program.Errors)
  72:                 {
  73:                     //TODO: da gestire
  74:                 }
  75:             }
  76:  
  77:             if (throwInsteadOfReturn)
  78:             {
  79:                 throw (e);
  80:             }
  81:         }
  82:  
  83:         return (success);
  84:     }
  85:  
  86:     //Chiusura della connessione
  87:     public void Close()
  88:     {
  89:         as400.Disconnect(cwbx.cwbcoServiceEnum.cwbcoServiceAll);
  90:     }
  91: }

Per poter comunicare con il server AS/400 è necessario mettere in piedi un canale di comunicazione. Per farlo servono alcune informazioni:



  • Nome o indirizzo IP del server AS/400
  • Utente
  • Password dell’utente
  • Libreria
  • Programma da invocare

Le informazioni possono essere gestite nel file di configurazione dell’applicazione. Basterà quindi crearsi una voce nei settings che contenga le informazioni nel seguente formato:


serverAS400;userid;password;library;program


Questa è la formattazione attesa dalla libreria di helper. Ovviamente può essere rivista secondo le proprie esigenze; questo però deve produrre anche una conseguente modifica nel file di helper o nel metodo che istanzia la classe di helper.


Altro punto importante per poter eseguire la connessione è referenziare l’oggetto COM definito nella libreria cwbx.dll; libreria che viene installata dall’IBM Client Access. La libreria si trova, se avete eseguito l’installazione di default, in questa directory:


C:\Program Files (x86)\IBM\Client Access\Shared


I limiti di questa tecnica non mi sono ancora completamente noti e se necessario cercherò di esplorarli più avanti. Al momento posso affermare che potrebbe essere la soluzione ideale per riutilizzare logica di business presente in AS/400 esponendola con dei web services sviluppati utilizzando Windows Communication Server (WCF) per poter fruire delle informazioni virtualmente da qualsiasi dispositivo/interfaccia utente.