How to run AS400 QMHQRDQD API from ASP.NET?

I am building an ASP.NET C# application that should run the QMHQRDQD API (Retrieve Data Queue Description) from a local AS400 server and return the results to a table in the ASP.NET page.  There are many articles on the internet that describe how to run a program from .NET, but they do not address existing AS400 system APIs such as QMHQRDQD.  I have attempted to apply cwbx.dll which is a library provided by IBM for this purpose. I've tested it on a non-system API in a console application successfully, but cannot seem to get it to run this system API.  

 Here is the relevant code from a test console application used to test the use of the cwbx library with QMHQRDQD:

static void Main(string[] args)
{
    string connectionStringODBC = "Driver={
         Client Access ODBC Driver (32-bit)};
         System=servername;
         UID=username;
         PWD=pwd;
         NAM=*SYS";

         GetQueuesCWBX(connectionStringODBC);
         Console.ReadLine();

}

private static void GetQueuesCWBX(string connectionString)
 {
     string result = String.Empty;
     cwbx.StringConverter stringConverter = new cwbx.StringConverter();

     //Define an AS400 system and connect to it
     AS400System system = new AS400System();
     system.Define("SERVERNAME");
     system.UserID = "USRNAME";
     system.Password = "password";
     system.IPAddress = "10.100.1.100";
     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 = "QSYS";
         program.ProgramName = "QMHQRDQD";
         program.system = system;

         //Get list of dataqueues to display

         string strSQL = "select * from DQLIBR.DQLIST";
               
         using (OdbcConnection conn = new OdbcConnection(connectionString))
         {
             OdbcCommand cmd = new OdbcCommand(strSQL, conn);
             try
             {
                 conn.Open();
                 OdbcDataReader reader = cmd.ExecuteReader();
                 while (reader.Read())    
                 {
                     string sDQName = (string)reader["DQNAME"];
                     sDQName.PadRight(10, ' ');
                     //Parameter data
                     string param0 = "";
                     string param1 = "112";
                     string param2 = "RDQD0100";   //contains number of msgs on the queue
                     string param3 = sDQName + "INVLIBR   ";

          //Create a collection of parameters associated with the program
          ProgramParameters parameters = new ProgramParameters();
          parameters.Append("RECVAR", cwbrcParameterTypeEnum.cwbrcOutput, 112);
          parameters.Append("RECVARLEN", cwbrcParameterTypeEnum.cwbrcInput, 4);
          parameters.Append("FMTNAME", cwbrcParameterTypeEnum.cwbrcInput, 8);
          parameters.Append("DTAQNAME", cwbrcParameterTypeEnum.cwbrcInput, 20);
          parameters["RECVAR"].Value = stringConverter.ToBytes(param0.PadRight(112, ' '));
          parameters["RECVARLEN"].Value = stringConverter.ToBytes(param1);
          parameters["FMTNAME"].Value = stringConverter.ToBytes(param2);
          parameters["DTAQNAME"].Value = stringConverter.ToBytes(param3);
                           

                     //Finally call the program
                     try
                     {
                         program.Call(parameters);
                     }
                     catch (Exception)
                     {
                         if (system.Errors.Count > 0)
                         {
                             foreach (cwbx.Error error in system.Errors)
                             {
                                 //Console.WriteLine(error.Text);
                                 result = error.Text;
                             }
                         }
                         if (program.Errors.Count > 0)
                         {
                             foreach (cwbx.Error error in program.Errors)
                             {
                                //Console.WriteLine(error.Text);
                                 result += error.Text + "\r\n";
                             }
                        }
                     }
                     if (result.Length < 1)
                     {
                        result = stringConverter.FromBytes(parameters["RECVAR"]);
                        string DTAQMsgs = result.Substring(72, 4);
                        Console.WriteLine(param3 + ", " + DTAQMsgs);
                     }
                    else
                     {
                         Console.WriteLine("Error: " + param3 + "- " + result);
                     }

                 }
             }
             catch (Exception ex)
             {
                 Console.WriteLine("Error: " + ex.StackTrace.ToString());
             }
             finally
             {
                 conn.Close();
             }
         }
     }
 }

Definition of QMHQRDQD located at: http://pic.dhe.ibm.com/infocenter/iseries/v7r1m0/index.jsp?topic=%2Fapis%2Fqmhqrdqd.htm

Error message received:  CPF3C24 - Length of the receiver variable is not valid.  

This error would seem to suggest that I've miscalculated the length of the receiver variable format that I've chosen.  I've tried everything from 1 to 120 and nothing seems to work.  This would suggest to me that I have a numeric type mismatch of some kind, but I'm not sure how to resolve it or if that's even the problem.

Has anyone out there done this?  Any ideas are welcome.  Thanks in advance.
manchesteraAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Gary PattersonVP Technology / Senior Consultant Commented:
QMHQRDQD Receiver Variable Length parameter is probably causing the problem.  This is defined in the API documentation as BINARY(4).

Binary number format has a sign bit in the leftmost bit (0=Positive, 1=Negative), and the remaining positions contain the value in left-to-right binary format.

Looks to me like you are converting the string value of "112" to a byte array and assigning the result to that parameter.  That's probably not going to give you the desired results.  The error message is what you get when you pass an invalid length in this parameter.

The API is expecting 4 bytes in IBM i BINARY numeric format.

For a 112-byte receiver variable, pass:

x00 00 00 70
b0000 0000 0000 0000 0000 0000 0111 0000

- Gary Patterson
0
tliottaCommented:
Gary's correct. In short, the parameter is a 32-bit signed integer.

Tom
0
Gary PattersonVP Technology / Senior Consultant Commented:
@Tom:  I hesitated to say 32-bit signed integer due to the differences in integer representation between platforms.  IBM i on Power uses big-endian integer representation, and Intel x86 systems use little-endian integers, so I wasn't sure if a direct integer-to-integer transform would apply.  

Didn't have time to write a little test program, so I just spelled out the correct target format.

http://msdn.microsoft.com/en-us/library/system.bitconverter.aspx

- Gary
0
Learn SQL Server Core 2016

This course will introduce you to SQL Server Core 2016, as well as teach you about SSMS, data tools, installation, server configuration, using Management Studio, and writing and executing queries.

tliottaCommented:
@Gary:

The big-/little-endian distinction is something that should be known by the developer, so I agree it's a good idea to have it stated. But I'd think the basic code change should be something like replacing this line:
          parameters["RECVARLEN"].Value = stringConverter.ToBytes(param1);
perhaps with:
          cwbx.LongConverter longCvt = new cwbx.LongConverter();
and
          parameters["RECVARLEN"].Value = longCvt.ToBytes(param0.Length);

If the Length of param0 is 112, then the byte-order should be handled by cwbx.LongConverter(). It should help eliminate the internal representation stuff.

The code in the question looked pretty good. The OP seems fairly well grounded. Maybe the discussion so far is enough.

Tom
0
Gary PattersonVP Technology / Senior Consultant Commented:
Found an IBM VB example (different API, but same technical challenge) that might provide some useful guidance.  The link has been moved to a place that requires an IBM logon with appropriate rights to view the article, but I was able to view the original content from Google's cache of the site.  Link may not work forever, so I posted a relevant excerpt of the code below as a teaching example:

http://www-912.ibm.com/s_dir/slkbase.NSF/0/dd5b61ff2f3626e98625678a006942f0

Cached:

http://webcache.googleusercontent.com/search?q=cache:F7CeAXcn-LgJ:www-912.ibm.com/s_dir/slkbase.NSF/0/dd5b61ff2f3626e98625678a006942f0+&cd=1&hl=en&ct=clnk&gl=us&client=firefox-a
 Dim adcnAS400 As New ADODB.Connection
  Dim cmdQWCRDTAA As New ADODB.Command      'QWCRDTAA AS/400 System API reads Data Area
 
  Dim I As Long
  Dim S As String
 
  'Data area params
  Dim OutDataLen As Long
  Dim DAName As String * 20
  Dim nStart As Long
  Dim DataLen As Long
  Dim ErrCode() As Byte
 
  'Set the input parameters
  DAName = "ADODA     QGPL      "
  nStart = 1
  DataLen = 10
   'OutDataLen is total size (in bytes) of the AS/400 receiver variable structure
  OutDataLen = DataLen + 36 'Length of data (10 in this example) + Length of fixed portion of output structure (36)

   On Error GoTo DspErr
  adcnAS400.Provider = "IBMDA400"
  adcnAS400.Open "Data Source=RCHASK60"

   With cmdQWCRDTAA
     Set .ActiveConnection = adcnAS400
     .CommandText = "{{call QWCRDTAA(?, ?, ?, ?, ?, ?)}}"
     .Prepared = True
      'Note:  The output parameter type must be adbinary to hold the structure
     .Parameters.Append .CreateParameter("OutData", adBinary, adParamOutput, OutDataLen)
     .Parameters.Append .CreateParameter("LenOutData", adInteger, adParamInput)
     .Parameters.Append .CreateParameter("Name", adChar, adParamInput, 20)
     .Parameters.Append .CreateParameter("Start", adInteger, adParamInput)
     .Parameters.Append .CreateParameter("LenOfData", adInteger, adParamInput)
     .Parameters.Append .CreateParameter("ErrorCode", adInteger, adParamInputOutput)
  End With

   With cmdQWCRDTAA
     .Parameters(1).Value = OutDataLen
     .Parameters(2).Value = DAName
     .Parameters(3).Value = nStart
     .Parameters(4).Value = DataLen
     .Parameters(5).Value = 0
     .Execute
      'View the raw data (if you're interested)
     For I = LBound(.Parameters(0).Value) To UBound(.Parameters(0).Value)
        S = S & " " & Str(.Parameters(0).Value(I))
     Next I
     Debug.Print S

      '*************************** Parse Data using the data manipulation objects  ******************************
     ' Declare variables
     Dim DataReturned As New cwbx.Structure
     Dim stringCvtr As New cwbx.StringConverter
      Dim longCvtr As New cwbx.LongConverter
     
     ' Define layout of "Data Returned" structure (see AS/400 System API Reference)
     DataReturned.Fields.Append "BytesAvailable", 4          'BINARY(4)
     DataReturned.Fields.Append "BytesReturned", 4           'BINARY(4)
     DataReturned.Fields.Append "TypeOfValueReturned", 10    'CHAR(10)
     DataReturned.Fields.Append "ReturnedLibrary", 10        'BINARY(4)
     DataReturned.Fields.Append "LengthOfValueReturned", 4   'BINARY(4)
     DataReturned.Fields.Append "NumberOfDecimalPos", 4      'BINARY(4)
     DataReturned.Fields.Append "Value", DataLen             'CHAR(*)  Variable length data
     
     ' Process output data
     ' Set the data values for the DataReturned structure
     DataReturned.Bytes = .Parameters(0).Value
     ' Strip trailing blanks from retrieved strings
     stringCvtr.Strip = True
     ' Use all bytes in source byte array to build string
     stringCvtr.Length = 0
     ' Retrieve field values from the "Data Returned" structure and display
     Debug.Print "  Bytes Provided = " & longCvtr.FromBytes(DataReturned("BytesAvailable").Value)
      Debug.Print "  Bytes Returned = " & longCvtr.FromBytes(DataReturned("BytesReturned").Value)
     Debug.Print "  Value Type = " & stringCvtr.FromBytes(DataReturned("TypeOfValueReturned").Value)
     Debug.Print "  Library = " & stringCvtr.FromBytes(DataReturned("ReturnedLibrary").Value)
     Debug.Print "  Length of Value Returned = " & longCvtr.FromBytes(DataReturned("LengthOfValueReturned").Value)
     Debug.Print "  Number of Decimal Pos = " & longCvtr.FromBytes(DataReturned("NumberOfDecimalPos").Value)
     Debug.Print "  Value = " & stringCvtr.FromBytes(DataReturned("Value").Value)
           
  End With

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
manchesteraAuthor Commented:
This worked perfectly.  Thanks to everyone...it was an education.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
IBM System i

From novice to tech pro — start learning today.