Web Design that taps into the haromny and vision of your dreams.

Calling IBM iSeries RPG from C# using API Calls

Calling IMB RPG via the CWBX Library

Written By on in C#

1,031 words, estimated reading time 5 minutes.

This tutorial will show you how to use the IBM supplied CWBX library to communicate with an iSeries using API calls for calling RPG from C#

In a previous tutorial we looked at how we can query an IBM iSeries (AS400) DB2 database directly using C# and ADO.Net. In some circumstances though, you may need to call a program on the AS400 which may include some form of business logic rather than a direct database query, and for this we are going to use CWBX for calling RPG from C#.

The first thing you will need to do is make sure you have the latest version of IBM Client Access (V5R3 or later) installed an make sure that you install the optional programmers toolkit.

Next, you will need to add a reference to the CWBX library from your application. This library provides the means by which we are going to be calling RPG from C#. You can add a reference to this file from the project menu and selecting "Add Reference". You need to browse to "C:Program FilesIBM Client AccessSharedcwbx.dll". This will give you access to the IBM API's within the cwbx namespace. You can add cwbx to you using statements if you wish.

I have to say now that I know absolutely nothing about RPG or AS400 programming, all I know is you need a program that accepts parameters in and out.

I'm going to create a console application is going to be calling RPG from C# and returns a value to the user. This example is very basic and does not perform much in the way of error handling.

using System;
using System.Collections.Generic;
using System.Text;
using cwbx;
 
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string result = string.Empty;
 
            StringConverter stringConverter = new StringConverterClass();
 
            // Define an AS400 system and connect to it
            AS400System system = new AS400System();
            system.Define("AS400");
            system.UserID = "USERNAME";
            system.Password = "PASSWORD";
            system.IPAddress = "127.0.0.1";
            system.Connect(cwbcoServiceEnum.cwbcoServiceRemoteCmd);
 
            // Check the connection
            if (system.IsConnected(cwbcoServiceEnum.cwbcoServiceRemoteCmd) == 1)
            {
                // Create a program object and link to a system                
                cwbx.Program program = new cwbx.Program();
                program.LibraryName = "LIBRARY";
                program.ProgramName = "RPGPROG";
                program.system = system;
 
                // Sample parameter data
                string param = "Example"; 
                int paramLength = 15; // Will be defined in the RGP program, so check with the programmer.
 
                // Create a collection of parameters associated with the program
                ProgramParameters parameters = new ProgramParameters();
                parameters.Append("in", cwbrcParameterTypeEnum.cwbrcInout, paramLength);
                parameters.Append("out", cwbrcParameterTypeEnum.cwbrcInout, paramLength);
                parameters["in"].Value = stringConverter.ToBytes(param.PadRight(paramLength, ' '));
 
                // Finally call the program 
                try
                {
                    program.Call(parameters);
                }
                catch (Exception ex)
                {
                    if (system.Errors.Count > 0)
                    {
                        foreach (cwbx.Error error in as400.Errors)
                        {
                            Console.WriteLine(error.Text);
                        }
                    }
 
                    if (program.Errors.Count > 0)
                    {
                        foreach (cwbx.Error error in program.Errors)
                        {
                            Console.WriteLine(error.Text);
                        }
                    }
                }
 
                result = stringConverter.FromBytes(parameters["out"].Value);
            }
 
            system.Disconnect(cwbcoServiceEnum.cwbcoServiceAll);
            Console.WriteLine(result);
            Console.ReadKey();
        }
    }
}

If a program falls over on the AS400 for whatever reason, the exception message contained within Ex.Message will contain the AS400 error code and message, so you can also use that to trap errors. For example, ex.Message may contain "CPA3138 - Member BLAH file BLAHBLAH at maximum size." which you can handle.

While this method does work, I found the performance very slow if you are using multiple calls. It takes an age to create a new AS400System object and connect, and it takes a while for the stringConverter to convert a string to a EBCDIC byte array.

Here is my solution to the performance issues surrounding the stringConverter class. You can use this in place of stringConverter to shave seconds off parameter string conversion times.

/// <summary>
/// Convert an IBM EBCDIC string into ASCII
/// </summary>
/// <param name="strEBCDICString">IBM AS400 EBCDIC string</param>
/// <returns>ASCII String</returns>
public static string ConvertEBCDICtoASCII(byte[] strEBCDICString)
{
    StringBuilder sb = new StringBuilder();
    char newc = '';
 
    strEBCDICString = TrimByteArray(strEBCDICString);
 
    for (int i = 0; i < strEBCDICString.Length; i++)
    {
        if (strEBCDICString[i] != '')
        {
            newc = Convert.ToChar(e2a[(int)strEBCDICString[i]]);
            sb.Append(newc);
        }
    }
    string result = sb.ToString();
    sb = null;
 
    return result;
}
 
/// <summary>
/// Convert an ASCII string to IBM EBCDIC
/// </summary>
/// <param name="strASCIIString">The ASCII string to convert</param>
/// <returns>IBM EBCDIC array</returns>
public static byte[] ConvertASCIItoEBCDIC(string strASCIIString)
{
    UTF8Encoding encoding = new UTF8Encoding();
    byte[] result = encoding.GetBytes(strASCIIString);
 
    for (int i = 0; i < result.Length; i++)
    {
        result[i] = a2e[(int)result[i]];
    }
 
    return result;
}
 
/// <summary>
/// Trim empty or null values from a byte array
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private static byte[] TrimByteArray(byte[] input)
{
  int i = input.Length - 1;
  while (input[i] == 0)
    --i;
 
  byte[] trimmed = new byte[i + 1];
  Array.Copy(input, trimmed, i + 1);
 
  return trimmed;
}
 
/// <summary>
/// Character lookup for EBCDIC ASCII Conversion
/// </summary>
private static int[] e2a = new int[256]{
0, 1, 2, 3,156, 9,134,127,151,141,142, 11, 12, 13, 14, 15,
16, 17, 18, 19,157,133, 8,135, 24, 25,146,143, 28, 29, 30, 31,
128,129,130,131,132, 10, 23, 27,136,137,138,139,140, 5, 6, 7,
144,145, 22,147,148,149,150, 4,152,153,154,155, 20, 21,158, 26,
32,160,161,162,163,164,165,166,167,168, 91, 46, 60, 40, 43, 33,
38,169,170,171,172,173,174,175,176,177, 93, 36, 42, 41, 59, 94,
45, 47,178,179,180,181,182,183,184,185,124, 44, 37, 95, 62, 63,
186,187,188,189,190,191,192,193,194, 96, 58, 35, 64, 39, 61, 34,
195, 97, 98, 99,100,101,102,103,104,105,196,197,198,199,200,201,
202,106,107,108,109,110,111,112,113,114,203,204,205,206,207,208,
209,126,115,116,117,118,119,120,121,122,210,211,212,213,214,215,
216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,
123, 65, 66, 67, 68, 69, 70, 71, 72, 73,232,233,234,235,236,237,
125, 74, 75, 76, 77, 78, 79, 80, 81, 82,238,239,240,241,242,243,
92,159, 83, 84, 85, 86, 87, 88, 89, 90,244,245,246,247,248,249,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57,250,251,252,253,254,255
};
 
/// <summary>
/// Character lookup for EBCDIC ASCII Conversion
/// </summary>
private static byte[] a2e = new byte[256]{
0, 1, 2, 3, 55, 45, 46, 47, 22, 5, 37, 11, 12, 13, 14, 15,
16, 17, 18, 19, 60, 61, 50, 38, 24, 25, 63, 39, 28, 29, 30, 31,
64, 79,127,123, 91,108, 80,125, 77, 93, 92, 78,107, 96, 75, 97,
240,241,242,243,244,245,246,247,248,249,122, 94, 76,126,110,111,
124,193,194,195,196,197,198,199,200,201,209,210,211,212,213,214,
215,216,217,226,227,228,229,230,231,232,233, 74,224, 90, 95,109,
121,129,130,131,132,133,134,135,136,137,145,146,147,148,149,150,
151,152,153,162,163,164,165,166,167,168,169,192,106,208,161, 7,
32, 33, 34, 35, 36, 21, 6, 23, 40, 41, 42, 43, 44, 9, 10, 27,
48, 49, 26, 51, 52, 53, 54, 8, 56, 57, 58, 59, 4, 20, 62,225,
65, 66, 67, 68, 69, 70, 71, 72, 73, 81, 82, 83, 84, 85, 86, 87,
88, 89, 98, 99,100,101,102,103,104,105,112,113,114,115,116,117,
118,119,120,128,138,139,140,141,142,143,144,154,155,156,157,158,
159,160,170,171,172,173,174,175,176,177,178,179,180,181,182,183,
184,185,186,187,188,189,190,191,202,203,204,205,206,207,218,219,
220,221,222,223,234,235,236,237,238,239,250,251,252,253,254,255
};

All this does is simply convert one format to another using a lookup table. Much, much faster than whatever the IBM library is doing. To use it simply replace:

parameters["in"].Value = stringConverter.ToBytes(param.PadRight(paramLength, ' '));

with

parameters["in"].Value = ConvertASCIItoEBCDIC(param.PadRight(paramLength, ' '))

and

result = stringConverter.FromBytes(parameters["out"].Value);

with

result = ConvertEBCDICtoASCII(parameters["out"].Value);

Other performance issues surround the use of the AS400System object. It seems to take around 3-5 seconds to create a new object and connect to the system. There are a few ways around this, but the one I prefer is to use an object factory class or singleton to dispense connections. If you are working on Windows Forms then you can get away with creating a "global" variable to hold the AS400System, while on ASP.Net you can use a pool of objects and a singleton dispenser.

Last updated on: Wednesday 21st June 2017

 

Comments
Henrik

Henrik

Could you send me the TrimByteArray method that you use? Also, do you know the difference between running command and program in AS400?

Reply to Henrik

 

Leave a Reply

Your email address will not be published.





If you find something abusive or that does not comply with our terms or guidelines please flag it as inappropriate.

Copyright © 2001-2018 Tim Trott, all rights reserved. Web Design by Azulia Designs

This web page is licensed for your personal, private, non-commercial use only.

Disclaimer, Privacy & LegalSitemapContact Me