• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1869
  • Last Modified:

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)};



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.UserID = "USRNAME";
     system.Password = "password";
     system.IPAddress = "";

     //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);
                 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
                     catch (Exception)
                         if (system.Errors.Count > 0)
                             foreach (cwbx.Error error in system.Errors)
                                 result = error.Text;
                         if (program.Errors.Count > 0)
                             foreach (cwbx.Error error in program.Errors)
                                 result += error.Text + "\r\n";
                     if (result.Length < 1)
                        result = stringConverter.FromBytes(parameters["RECVAR"]);
                        string DTAQMsgs = result.Substring(72, 4);
                        Console.WriteLine(param3 + ", " + DTAQMsgs);
                         Console.WriteLine("Error: " + param3 + "- " + result);

             catch (Exception ex)
                 Console.WriteLine("Error: " + ex.StackTrace.ToString());

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.
  • 3
  • 2
1 Solution
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
Gary's correct. In short, the parameter is a 32-bit signed integer.

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.


- Gary
Free Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.


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();
          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.

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:



 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
      '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

manchesteraAuthor Commented:
This worked perfectly.  Thanks to everyone...it was an education.
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

  • 3
  • 2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now