Next: Building and Installing the Up: C++ plug-in source code Previous: Introduction Contents

##
A minimal example

Below is a minimal C++ plug-in for the function XY2Polar described by the plug-in template in Fig.2, followed by a line-by-line explanation:

    /*************************************************************************
    * PROJECT:      Cluster UK CDHF
    * COMPONENT:    Plug In library
    * MODULE:	XY2Polar.cc
    * LANGUAGE:	C++
    * FUNCTION:     QSAS XY2Polar Plug In
    * PURPOSE:      Calculate radius and phase of two scalar series.
    * ARGUMENTS:
    *    IN:    Scalar series X.
    *           Scalar series Y.
    *   OUT:    Radius = SQRT(X^2+Y^2) Scalar series
    *           Phase  = ATAN2(Y,X) Scalar series
    * RETURN:   QplugReturnStatus
    ***********************************************************************/
    #include <stdio.h>
    #include <math.h>
    #include <string.h>
    #include <stdlib.h>

    #include "qplug_if.h"
    #include "QdUtils.h"
    #include "Qdos.h"
    #include "qar.h"
    #include "Xrefs.h"

    using namespace QSAS;

    extern "C" QplugReturnStatus  XYtoPolar( QplugArgList *);

    QplugReturnStatus	XY2Polar(QplugArgList *call_list )
    {

    // unpack the input data objects
    QdObject_var X_in = (* call_list)[0];
    QdObject_var Y_in = (* call_list)[1];

    // narrow to a scalar sequence object
    QdRScalarSeq_var xx = QdRScalarSeq_var::narrow(X_in);
    if(xx.is_nil() ){
      QplugAppendTextDisplay("XY2Polar ERROR: X seq type not handled\n");
      return QPLUG_FAILURE;
    }
    QdRScalarSeq_var yy = QdRScalarSeq_var::narrow(Y_in);
    if(yy.is_nil() ){
      QplugAppendTextDisplay("XY2Polar ERROR: Y seq type not handled\n");
      return QPLUG_FAILURE;
    }

    long Ndata = xx->sequence_size();

    // check whether input conformal and Time series joined
    qar_status =  QarTestConformal(X_in, Y_in);
    if(qar_status == QAR_ERR)  return QPLUG_FAILURE;

     // confirm units are equivalent
     qar_status = QarAreUnitsSame(X_in, Y_in);
     if(qar_status == QAR_ERR) {
       QplugAppendTextDisplay (
         "Arithmetic>> Operation requires inputs in same units\n");
      return QPLUG_FAILURE;
     }
     else if(qar_status == QAR_WARN){
       QplugAppendTextDisplay (
         "Arithmetic>>  units unknown for at least one input\n");
       QplugAppendTextDisplay ("Proceed with caution!\n");
     }

    //define output data objects
    QdRScalarSeq_var out_rad_series;
    QdRScalarSeq_var out_phas_series;

    //calculate radius and phase

    try{
      out_rad_series = QdRScalarSeq_var::narrow(sqrt(xx*xx + yy*yy));
      out_phas_series = QdRScalarSeq_var::narrow(atan2(xx,yy));
    }
    catch(Exception &e){
       e.print_msg();
       return QPLUG_FAILURE;
    }

    //set minimal attributes by copying from input objects
    out_rad_series->copy_xrefs_from(X_in);
    QuSetTxtAttr("Frame", "scalar>na", (QdObject_var) out_rad_series);
    sprintf(str_out,"r(%s)", QuGetAttrText("FIELDNAM", X_in));
    QuSetTxtAttr("LABLAXIS", str_out, (QdObject_var) out_rad_series);
    QuSetTxtAttr("FIELDNAM", str_out, (QdObject_var) out_rad_series);

    QuSetTxtAttr("SI_conversion", "1>rad", (QdObject_var) out_phas_series);
    sprintf(str_out,"Phase(%s)", QuGetAttrText("FIELDNAM", X_in));
    QuSetTxtAttr("LABLAXIS", str_out, (QdObject_var) out_phas_series);
    QuSetTxtAttr("FIELDNAM", str_out, (QdObject_var) out_phas_series);
    QuSetTxtAttr("Frame", "scalar>na", (QdObject_var) out_phas_series);
    QuSetTxtAttr("UNITS", "rad", (QdObject_var) out_phas_series);

    //get timetags from X or Y and add to output objects
    QdTimeSeq_var tags = get_timetags(X_in);
    if (!tags.is_nil()) {
       set_timetags(out_rad_series, tags);
       set_timetags(out_phas_series, tags);
    }

    //add output objects to  QSAS working list
    call_list->push_back((QdObject_var) out_rad_series);
    call_list->push_back((QdObject_var) out_phas_series);
    return QPLUG_SUCCESS;
    }

The example above includes the C++-standard libraries used by the plug-in:
`<stdio.h>, <math.h>, <string.h>, <stdlib.h>`. Then follows a minimal set of QSAS specific header files:
`"qplug_if.h", "QdUtils.h", "Qdos.h", "qar.h", "Xrefs.h"`. See below for functions available through QSAS libraries. `using namespace QSAS;` is necessary to use data object attributes (see section 7).

The 'extern' line is required by plugins for QSAS version 2.0 and later, it allows QSAS to locate the entry name in the .so library reliably...

extern "C" QplugReturnStatus XYtoPolar( QplugArgList *);

`QplugReturnStatus` can have the (self-explaining) values `QPLUG_SUCCESS,QPLUG_WARNING,` and `QPLUG_FAILURE`. The only argument of a plug-in is always a QSAS data object of type `QplugArgList`; it holds pointers to the data objects used by the function in the order they appear in the template file.

First we have to unpack the two input objects defined by the template (Fig.2):

    QdObject_var X_in = (* call_list)[0];
    QdObject_var Y_in = (* call_list)[1];

All objects are passed to the plug-in as the top level QdObject_var class (the _var means it is a safe pointer to this class). We have to convert the `QdObject_var` class var pointer to a derived class var pointer using the `narrow` method:

    QdRScalarSeq_var xx = QdRScalarSeq_var::narrow(X_in);
    if(xx.is_nil() ){
      QplugAppendTextDisplay("XY2Polar ERROR: X seq type not handled\n");
      return QPLUG_FAILURE;
    }
    QdRScalarSeq_var yy = QdRScalarSeq_var::narrow(Y_in);
    if(yy.is_nil() ){
      QplugAppendTextDisplay("XY2Polar ERROR: Y seq type not handled\n");
      return QPLUG_FAILURE;
    }

If the input object cannot be narrowed to the desired type the returned _var contains a NULL pointer, and can be tested using `is_nil()`.

The var pointers `xx` and `yy` can now be used for further operations. We may find the size of the sequence, using
`long Ndata = xx->sequence_size();`. This can be used to construct `for' loops over the sequence, but is not necessary for the generic methods used here.

A single utility, QarTestConformal, is provided that checks that the sequences have the same number of elements unless one of them is a constant value (it is possible to perform most binary operations between a sequence and a constant value which is used for each entry in the sequence). If the input objects are time series, this utility also tests whether they are joined onto a common timeline.

     qar_status =  QarTestConformal(X_in, Y_in);
     if(qar_status == QAR_ERR)  return QPLUG_FAILURE;

A further check is performed since the function is only meaningful if the two input objects have the same units.

     qar_status = QarAreUnitsSame(X_in, Y_in);
     if(qar_status == QAR_ERR) {
       QplugAppendTextDisplay (
         "Arithmetic>>  Operation requires inputs in same units\n");
      return QPLUG_FAILURE;
     }
     else if(qar_status == QAR_WARN){
       QplugAppendTextDisplay (
         "Arithmetic>>  units unknown for at least one input\n");
       QplugAppendTextDisplay ("Proceed with caution!\n");
     }

This test uses the `SI_conversion` xref (attribute in cdf terminology) and reduces the unit to base SI components before comparison. It ensures that the units match as well as the scale factor (see detailed function description). Note that if the SI_conversion xref is not available for one input a warning is issued, but the operation is completed.

We could now create two output series of the same size as the input series, and fill the series with the radius and phase of the two input series using a loop over the individual elements of the input sequences,

    QdRScalarSeq_var out_rad_series  = new QdRScalarSeq(Ndata);
    QdRScalarSeq_var out_phas_series = new QdRScalarSeq(Ndata);
    try{
      for (long i =0; i<Ndata; i++) {
        out_rad_series[i] =  sqrt(xx[i]*xx[i]+yy[i]*yy[i]);
        out_phas_series[i] = atan2(xx[i],yy[i]);
      }
    }
    catch(Exception &e){
       e.print_msg();
       return QPLUG_FAILURE;
    }

but since the sqrt and atan2 methods are available for all qdos numeric types we may simply use,

    QdRScalarSeq_var out_rad_series;
    QdRScalarSeq_var out_phas_series;
    try{
      out_rad_series = QdRScalarSeq_var::narrow(sqrt(xx*xx + yy*yy));
      out_phas_series = QdRScalarSeq_var::narrow(atan2(xx,yy));
    }
    catch(Exception &e){
       e.print_msg();
       return QPLUG_FAILURE;
    }

Note that instances of the output objects are created outside of the try-catch construction to ensure they persist when the try block is exited.

The `try` and `catch` statements allow qdos to safely throw an exception if the requested operation is not permissable. For example, if the sequences xx and yy were of different types qdos would reject the operation and the returned error message would be printed to the standard output.

Also we should set the output object attributes (see section 7). In the case of the radial component most attributes are the same as for the input series and we can copy all xrefs from the input series:

     out_rad_series->copy_xrefs_from(X_in);
     QuSetTxtAttr("Frame", "scalar>na", (QdObject_var) out_rad_series);

We explicitly replaced the "Frame" attribute with the scalar entry to be certain, since the input came from a vector component.

Note that certain standard attribute names are defined by QSAS, so we could have specified
QuSetTxtAttr(FRAME, "scalar>na", (QdObject_var) out_rad_series);
in place of "Frame". This allows QSAS to take care of the case sensitivity of the ISTP/CSDS attribute names.

An alternative would have been to set just the minimal attributes directly, copying the SI_CONVERSION and UNITS explicitly from the input object.

     QuSetTxtAttr(SI_CONVERSION, QuGetAttrText("SI_conversion", X_in),
                  (QdObject_var) out_rad_series);
     QuSetTxtAttr(UNITS, QuGetAttrText("UNITS", X_in),
                  (QdObject_var) out_rad_series);
     QuSetTxtAttr(FRAME, "scalar>na", (QdObject_var) out_rad_series);

The attributes for the phase output must be set explicitly as they differ from those of the input object.

     QuSetTxtAttr(SI_CONVERSION, "1>rad", (QdObject_var) out_phas_series);
     QuSetTxtAttr(FRAME, "scalar>na", (QdObject_var) out_phas_series);
     QuSetTxtAttr(UNITS, "rad", (QdObject_var) out_phas_series);

If the input series are timeseries we would like to add the same timetags to the output series:

    QdTimeSeq_var tags = get_timetags(X_in);
    if (!tags.is_nil()) {
       set_timetags(out_rad_series, tags);
       set_timetags(out_phas_series, tags);
    }

Note that the time tags are also Xrefs for the objects, so the earlier copy_xrefs_from command will have set them. Repeating this is safe, and is shown here for completeness.

Finally we are ready to put our objects on the QSAS working list by:

     call_list->push_back((QdObject_var) out_rad_series);
     call_list->push_back((QdObject_var) out_phas_series);
     return QPLUG_SUCCESS;

The `push_back` operation puts the output objects on the argument list to be returned to QSAS. Output objects must be pushed back in the same order that they appear in the plug-in template (.qtpl) file. On successful return, the Plug-in interface adds the new data objects to the working list.

Next: Building and Installing the Up: C++ plug-in source code Previous: Introduction Contents