Generated Streaming C++ Decode Method Format and Calling Parameters

Generated C streaming decode functions are invoked through the C++ class interface by calling the generated DecodeFrom method. The calling sequence for this method is as follows:

   status = <object>.DecodeFrom (<inputStream>);

In this definition, <object> is an instance of the control class (i.e., ASN1C_<prodName>) generated for the given production.

The <inputStream> placeholder represents an input stream object type. This is an object derived from an ASN1DecodeStream class.

The function result variable stat returns the completion status. Error status codes are negative. Return status values are defined in the rtxErrCodes.h include file.

Another way to decode message using the C++ class interface is to use the >> stream operator:

   <inputStream> >> <object>;

Exceptions are not used in ASN1C C++, therefore, the user must fetch the status value following a call such as this in order to determine if it was successful. The getStatus method in the ASN1DecodeStream class is used for this purpose.

Also, the method Decode without parameters is supported for backward compatibility. In this case it is necessary to create a control class object (i.e., ASN1C_<prodName>) using an input stream reference as the first parameter and a reference to a variable of the generated type as the second parameter of the constructor.

Procedure for Using the Streaming C++ Control Class Decode Method

Normally the receiving message can be one of several different message types. It is therefore necessary to determine the type of message that was received so that the appropriate decode function can be called to decode it. The ASN1BERDecodeStream class has standard methods for parsing the initial tag/length from a message to determine the type of message received. These calls are used in conjunction with a switch statement on generated tag constants for the known message set. Each switch case statement contains logic to create an object instance of a specific ASN1C generated control class and to invoke and then to invoke that object’s decode method.

A program fragment that could be used to decode an employee record is as follows:

   #include "Employee.h"         // include file generated by ASN1C
   #include "rtbersrc/ASN1BERDecodeStream.h"
   #include "rtxsrc/OSRTFileInputStream.h"

   main ()
   {
      ASN1TAG tag;
      int i, len;
      const char* filename = "message.dat";
      OSBOOL trace = TRUE;

      // Decode

      ASN1BERDecodeStream in (new OSRTFileInputStream (filename));
      if (in.getStatus () != 0) {
         in.printErrorInfo ();
         return -1;
      }

      if (in.peekTagAndLen (tag, len) != 0) {
         printf ("peekTagAndLen failed\n");
         in.printErrorInfo ();
         return -1;
      }

      // Now switch on initial tag value to determine what
      // type of message was received..

      switch (msgtag)
      {
         case TV_PersonnelRecord: // compiler generated
                                  // constant
         {
            ASN1T_PersonnelRecord msgData;
            ASN1C_PersonnelRecord employee (msgData);

            in >> employee;
            if (in.getStatus () != 0) {
               printf ("decode of PersonnelRecord failed\n");
               in.printErrorInfo ();
               return -1;
            }

            // or employee.DecodeFrom (in);
            break;
         }

         case TV_ ...// handle other known messages
            ...
         }
      }

      return 0;
   }

Note that the call to free memory and the stream close method are not required to release dynamic memory when using the C++ interface. This is because the control class hides all of the details of managing the context and releasing dynamic memory. The memory is automatically released when both the input stream object (ASN1BERDecodeStream and derived classes) and the control class object (ASN1C_<ProdName>) are deleted or go out of scope. Reference counting of a context variable shared by both interfaces is used to accomplish this.

Decoding a Series of Messages Using the C++ Control Class Interface

The above example is fine as a sample for decoding a single message, but what happens in the more typical scenario of having a long-running loop that continuously decodes messages? The logic shown above would not be optimal from a performance standpoint because of the constant creation and destruction of the message processing objects. It would be much better to create all of the required objects outside of the loop and then reuse them to decode and process each message.

A code fragment showing a way to do this is as follows:

   #include "Employee.h"      // include file generated by ASN1C
   #include "rtbersrc/ASN1BERDecodeStream.h"
   #include "rtxsrc/OSRTFileInputStream.h"

   int main ()
   {
      ASN1TAG tag;
      int i, len;
      const char* filename = "message.dat";
      OSBOOL trace = TRUE;

      // Decode

      ASN1BERDecodeStream in (new OSRTFileInputStream (filename));
      if (in.getStatus () != 0) {
         in.printErrorInfo ();
         return -1;
      }

      ASN1T_PersonnelRecord msgData;
      ASN1C_PersonnelRecord employee (msgData);

      for (;;) {
         if (in.peekTagAndLen (tag, len) != 0) {
            printf ("peekTagAndLen failed\n");
            in.printErrorInfo ();
            return -1;
         }

         // Now switch on initial tag value to determine what
         // type of message was received..

         switch (msgtag)
         {
            case TV_PersonnelRecord: // compiler generated
                                     // constant
         {
            in >> employee;

            if (in.getStatus () != 0) {
               printf ("decode of PersonnelRecord failed\n");
               in.printErrorInfo ();
               return -1;
            }

               // or employee.DecodeFrom (in);
            
            }

            case TV_ ...// handle other known messages
               ...
         }

         // Need to reinitialize objects for next iteration

         employee.memFreeAll ();

      } // end of loop

      return 0;
   }

This is quite similar to the first example. Note that we have pulled the ASN1T_Employee and ASN1C_Employee object creation logic out of the switch statement and moved it above the loop. These objects can now be reused to process each received message.

The only other change was the call to employee.memFreeAll that was added at the bottom of the loop. Since the objects are not deleted to automatically release allocated memory, we need to do it manually. This call will free all memory held within the decoding context. This will allow the loop to start again with no outstanding memory allocations for the next pass.