Difference between revisions of "V4 Data Interface"

From EPICSWIKI
 
(DA code example)
 
(2 intermediate revisions by the same user not shown)
Line 8: Line 8:


Would it work, but performance would be unacceptable?
Would it work, but performance would be unacceptable?
[[Image:V4DataInter.jpg]]


=== Data Interface Classes ===
=== Data Interface Classes ===
Line 16: Line 18:
     Octet,    // Raw bits,known to a server/client pair, but no one else
     Octet,    // Raw bits,known to a server/client pair, but no one else
     Bool,
     Bool,
     Integer,  // Discrete
     Integer,  // Discrete, available with 16, 32, 64 bits
     Real,      // Floating-point
     Real,      // Floating-point, available with 32 and 64 bits
     String,    // UTF-8
     String,    // UTF-8
     Enum,      // >=0 Integer with state names
     Enum,      // >=0 Integer with state names
     Struct,
     Structure,
     TimeStamp  // For efficiency?
     TimeStamp  // For efficiency?
  };
  };
   
   
  // Full description of the data
  // Full description of the data
// This could be a pure interface,
// while Ben suggests to use a struct/class
// so that the user can easier get it as a whole.
// Ben also suggests that getNumDims throws an
// exception for scalars.
// I think a scalar has dimension 1, size 1,
// no exception.
  class EpicsDataDescriptor
  class EpicsDataDescriptor
  {
  {
Line 31: Line 40:
     virtual int  getNumDims() = 0;      // Scalar, vector: 1, matrix: >1
     virtual int  getNumDims() = 0;      // Scalar, vector: 1, matrix: >1
     virtual int  getSize(int dim) = 0;  // Scalar: 1
     virtual int  getSize(int dim) = 0;  // Scalar: 1
     virtual int  getBitsize() = 0;      // bits of integer or real
     virtual int  getBitsize() = 0;      // bits of integer or real. 0 for rest?
     virtual bool isSigned() = 0;        // Integer might be unsigned
     virtual bool isSigned() = 0;        // Integer might be unsigned?
  };
  };
   
   
  // Access to the data. Two options:
  // When the exact size-locked types are needed,
  // 1) Via basic types of the language, contents will differ for Java, C++.
// use these integer types from stdint.h:
  //    (shown here)
//    int8_t, int16_t, int32_t, int64_t
  // 2) Locked types:
// and the following:
  //   C++ will probably define the full set
typedef bool          bool_t;
  //     int_8_t, int16_t, ...
typedef float        real32_t;
  //    and Java can only handle a sub-set.
typedef double        real64_t;
 
// Read-Access to the data.
  // This one tries to allow as many conversions as possible.
// Given for example an underlying Real, 64bit, scalar,
// one can ask for a real64 but also int, string, ...
// You can also ask for the 5th element of a double array
// or try to access the structure detail,
// but then you have to expect a failed response.
// So a simple client uses
  //    if (! getDouble()) { .. error .. }
// and a more sopisticated one uses
  // getType() == Real or Integer  --> getDouble()
//
// The alternative would be individual reader interfaces
  // for numerics, strings, structures, numeric arrays, string arrays, ...
  // or even separate ones for int8, int16, ...
  // Marty's "network accessable data" email to core talk did the latter.
// JCA also currently goes that way:
// getDBR() == DOUBLE ?  -->  ( (DBR_Double)getValue() ).getDoubleValue()
// getDBR() == INT   ?  -->  ( (DBR_Int)getValue() ).getIntValue()
  class EpicsDataReader : public EpicsDataDescriptor
  class EpicsDataReader : public EpicsDataDescriptor
  {
  {
  public:
  public:
     // Get data as type XX as best as possible.
     // Get data as type XX as best as possible.
     // Example: getDouble returns true for Bool (d=0.0, 1.0), Integer, Real, Enum.
     // Example: getDouble returns true for
    //          Bool (d=0.0, 1.0), Integer, Real, Enum.
    virtual bool getReal64(real64_t &r) = 0;
     virtual bool getDouble(double &d) = 0;
     virtual bool getDouble(double &d) = 0;
    virtual bool getArrayDouble(int element, double &d) = 0;
    virtual bool getMatrixDouble(int dim, int element, double &d) = 0;
     virtual bool getInt(int &d) = 0;
     virtual bool getInt(int &d) = 0;
    // String
     virtual bool getString(char *buf, int buf_len) = 0;
     virtual bool getString(char *buf, int buf_len) = 0;
    virtual int  getStringLength() = 0;
     virtual bool getCstring(const char *&c_ptr) = 0;
     virtual bool getCstring(const char *&c_ptr) = 0;
     /// many more, at least one for each supported type.
     /// many more, at least one for each supported type.
    // A structure is accessed via a (sub-)catalog.
    // See below for PropertyCatalog.
    virtual class EpicsPropertyCatalog *getStructure() = 0;
   
    // Get array element. getDouble(x) == getArrayDouble(0, x)
    virtual bool getArrayDouble(int element, double &d) = 0;
    virtual bool getMatrixDouble(int dim, int element, double &d) = 0;
    virtual class EpicsPropertyCatalog *getArrayStructure(int element) = 0;
     /// For arrays, it could include per-element access
     /// For arrays, it could include per-element access
     /// as well as accessors that copy pieces into client buffers.
     /// as well as accessors that copy sub-arrays into client buffers.
  };    
  };
   
   
  // "Property" has a name and data reader interface
  // "Property" has a name and data reader interface
Line 69: Line 109:
  {
  {
  public:
  public:
     virtual int getNumProperties() = 0;
     virtual int           getNumProperties() = 0;
     virtual EpicsProperty *findProperty(const char *name) = 0;
     virtual EpicsProperty *findProperty(const char *name) = 0;
     virtual EpicsProperty *getProperty(int index) = 0;
     virtual EpicsProperty *getProperty(int index) = 0;
Line 78: Line 118:
=== C++ Implementation ===
=== C++ Implementation ===
  // On the first read, skip to the 'Usage Example' below.
  // On the first read, skip to the 'Usage Example' below.
  // This code isn't the greatest, it simply here to get
  // This code isn't the greatest, but simply here to get
  // example code that actually compiles & runs.
  // example code that actually compiles & runs.
  #include <string>
  #include <string>
Line 84: Line 124:
  #include <string.h>
  #include <string.h>
   
   
// Base class for the following EpicsProperty implementations.
// Provides default handlers for everything.
  class EpicsPropertyBase : public EpicsProperty
  class EpicsPropertyBase : public EpicsProperty
  {
  {
Line 89: Line 131:
     EpicsPropertyBase(const char *name)
     EpicsPropertyBase(const char *name)
     {  this->name = name; }
     {  this->name = name; }
    // EpicsDataDescriptor
    // defaults for scalar
    int getNumDims()
    {  return 1; }
    int getSize(int dim)
    {  return dim==0 ? 1 : 0; }
    int getBitsize()
    {  return 0; }
    bool isSigned()
    {  return true; }
     // DataReader
     // DataReader
    bool getReal64(real64_t &r)
    {  return getDouble(r); }
     bool getDouble(double &d)
     bool getDouble(double &d)
     {
     {
         int i;
         int i;
         if (getInt(i))
         if (!getInt(i))
         {
            return false;
            d = i;
         d = i;
            return true;
        return true;
        }
        return false;
     }
     }
    bool getArrayDouble(int element, double &d)
    {  return (element == 0) ? getDouble(d) : false; }
    bool getMatrixDouble(int dim, int element, double &d)
    {  return (dim == 0) ? getArrayDouble(element, d) : false; }
     bool getInt(int &i)
     bool getInt(int &i)
     {
     {
         double d;
         double d;
         if (getDouble(d))
         if (!getDouble(d))
         {
            return false;
            i = (int)d;
         i = (int)d;
            return true;
        return true;
        }
        return false;
     }     
     }     
     bool getString(char *buf, int buf_len)
     bool getString(char *buf, int buf_len)
     {
     {
         const char *ptr;
         const char *ptr;
         if (getCstring(ptr))
         if (!getCstring(ptr))
         {
            return false;
            strncpy(buf, ptr, buf_len-1);
         strncpy(buf, ptr, buf_len-1);
            return true;
        return true;
        }
    }
        return false;
    int  getStringLength()
    }  
    {  return 0; }
     bool getCstring(const char *&c_ptr)
     bool getCstring(const char *&c_ptr)
     {
     {   return false; }
        return false;
    EpicsPropertyCatalog *getStructure()
     }  
    {  return 0; }
    bool getArrayDouble(int element, double &d)
    {  return (element == 0) ? getDouble(d) : false; }
    bool getMatrixDouble(int dim, int element, double &d)
    {  return (dim == 0) ? getArrayDouble(element, d) : false; }
    EpicsPropertyCatalog *getArrayStructure(int element)
     {  return element==0 ? getStructure() : 0; }
 
     // EpicsProperty
     // EpicsProperty
        const char *getName()
    const char *getName()
        {  return name.c_str(); }
    {  return name.c_str(); }
  private:
  private:
         string name;
         string name;
  };
  };
   
   
// EpicsProperty for 'double'. Actual data is outside,
// the EpicsDoubleProperty keeps a pointer to it.
  class EpicsDoubleProperty : public EpicsPropertyBase
  class EpicsDoubleProperty : public EpicsPropertyBase
  {
  {
  public:
  public:
        EpicsDoubleProperty(const char *name, const double *p)
    EpicsDoubleProperty(const char *name, const double *p)
             : EpicsPropertyBase(name)
             : EpicsPropertyBase(name)
     {  this->p = p; }
     {  this->p = p; }
        // EpicsDataDescriptor
    // EpicsDataDescriptor
        EpicsDataCharacter getType()
    EpicsDataCharacter getType()
        {  return Real; }
    {  return Real; }
    int getNumDims()
    {  return 1; }
    int getSize(int dim)
    {  return dim==1 ? 1 : 0; }
     int getBitsize()
     int getBitsize()
     {  return sizeof(double)*8; }
     {  return sizeof(double)*8; }
    bool isSigned()
    {  return true; }
     // EpicsDataReader
     // EpicsDataReader
     bool getDouble(double &d)
     bool getDouble(double &d)
     {  d = *p; }
     {  d = *p;
        return true;
    }
  private:
  private:
        const double *p;
    const double *p;
  };
  };
   
 
  // Handler for string. Data is not owned by this class.
  class EpicsStringProperty : public EpicsPropertyBase
  class EpicsStringProperty : public EpicsPropertyBase
  {
  {
  public:
  public:
        EpicsStringProperty(const char *name, const string *p)
    EpicsStringProperty(const char *name, const string *p)
             : EpicsPropertyBase(name)
             : EpicsPropertyBase(name)
     {  this->p = p; }
     {  this->p = p; }
        // EpicsDataDescriptor
    // EpicsDataDescriptor
        EpicsDataCharacter getType()
    EpicsDataCharacter getType()
        {  return String; }
    {  return String; }
    int getNumDims()
    {  return 1; }
    int getSize(int dim)
    {  return dim==1 ? p->length() : 0; }
    int getBitsize()
    {  return p->length()*8; }
    bool isSigned()
    {  return false; }
     // EpicsDataReader
     // EpicsDataReader
    int  getStringLength()
    {  return p->length(); }
     virtual bool getCstring(const char *&c_ptr)
     virtual bool getCstring(const char *&c_ptr)
     {
     {
Line 185: Line 233:
     const string *p;
     const string *p;
  };
  };
   
 
  class EpicsPropertyCatalogBase : public EpicsPropertyCatalog
{
public:
    EpicsPropertyCatalogBase(int num_properties, EpicsProperty **properties)
            : num_properties(num_properties), properties(properties)
    {}
    // EpicsPropertyCatalog
    int getNumProperties()
    {  return num_properties; }
    EpicsProperty *findProperty(const char *name)
    {
        int i;
        for (i=0; i<num_properties; ++i)
            if (!strcmp(properties[i]->getName(), name))
                return properties[i];
        return 0;
    }
    EpicsProperty *getProperty(int index)
    {
        return (index >=0  &&  index < num_properties) ? properties[index] : 0;
    }
private:
    int            num_properties;
    EpicsProperty **properties;
};
 
// Handler for struct. Keeps pointers to EpicsProperty
// interfaces for the struct's fields.
class EpicsStructureProperty : public EpicsPropertyBase
{
public:
    EpicsStructureProperty(const char *name, int num_properties, EpicsProperty **properties)
            : EpicsPropertyBase(name), pc(num_properties, properties)
    {}
    // EpicsDataDescriptor
    EpicsDataCharacter getType()
    {  return Structure; }
    // EpicsDataReader
    EpicsPropertyCatalog *getStructure()
    {  return &pc; }
private:
    EpicsPropertyCatalogBase pc;
};
 
=== Example for data known at compile-time ===
=== Example for data known at compile-time ===
  // This is the code that provides a property catalog.
  // This is the code that provides a property catalog.
  // Again skip on the first read.
  // Again skip on the first read.
  //
  //
  // All of this could be generated from DBD
  // All of this could be generated from DBD information.
  class AiRecord : public EpicsPropertyCatalog
  class TestRecord : public EpicsPropertyCatalogBase
  {
  {
  public:
  public:
     AiRecord();
     TestRecord();
     // Unsure if database code with pointer to the record
     // Unsure if database code with pointer to the record
     // should still directly access the data,
     // should still directly access the data,
Line 200: Line 292:
     double value;
     double value;
     string units;
     string units;
     // EpicsPropertyCatalog
     struct
     int getNumProperties();
     {
     EpicsProperty *findProperty(const char *name);  
        double x, y;
     EpicsProperty *getProperty(int index);
     }  point;
private:
  private:
     EpicsProperty *property[2];
     EpicsProperty *properties[3];
     EpicsProperty *point_properties[2];
  };
  };
   
   
  AiRecord::AiRecord()
  TestRecord::TestRecord()
        : EpicsPropertyCatalogBase(3, properties)
  {
  {
     property[0] = new EpicsDoubleProperty("value", &this->value);
     point_properties[0] = new EpicsDoubleProperty("x", &point.x);
     property[1] = new EpicsStringProperty("units", &this->units);
     point_properties[1] = new EpicsDoubleProperty("y", &point.y);
}
    properties[0] = new EpicsDoubleProperty("value", &value);
     properties[1] = new EpicsStringProperty("units", &units);
int AiRecord::getNumProperties()
    properties[2] = new EpicsStructureProperty("point", 2, point_properties);
{  return sizeof(property)/sizeof(EpicsProperty *); }
EpicsProperty *AiRecord::findProperty(const char *name)
{  // could support hashed numeric IDs instead of strings
     int i;
    for (i=0; i<getNumProperties(); ++i)
        if (!strcmp(property[i]->getName(), name))
            return property[i];
    return 0;
  }
  }
 
  EpicsProperty *AiRecord::getProperty(int index)
=== Example Generic 'Dumper' ===
 
  class EpicsPropertyDumper
  {
  {
     return (index >=0  && index <sizeof(property)/sizeof(EpicsProperty *)) ?
public:
         property[index] : 0;
     static const char *type2string(EpicsDataCharacter c)
  }
    {
        const char *strings[] =
        {
            "Octet",
            "Bool",
            "Integer",
            "Real",
            "String",
            "Enum",
            "Structure",
            "TimeStamp"
        };
       
        if (c < || c >= sizeof(strings)/sizeof(const char *))
            return "<unknown>";
         return strings[c];
    }
   
    static void dump(EpicsPropertyCatalog *pc)
    {  dump(0, pc); }
private:
    static void indent(int level)
    {  for (int i=0; i<level; ++i)  printf("    "); }
    static void dump(int level, EpicsPropertyCatalog *pc)
    {
        double d;
        int i;
        for (i=0; i<pc->getNumProperties(); ++i)
        {
            indent(level);
            EpicsProperty *p = pc->getProperty(i);
            EpicsDataCharacter type = p->getType();
            printf("%s (%s, %d %s, %d bits) = ",
                  p->getName(), type2string(type),
                  p->getSize(0),
                  (p->getSize(0) == 1 ? "element" : "elements"),
                  p->getBitsize());
            switch (type)
            {
            case String:
                const char *s;
                if (p->getCstring(s))
                    printf("'%s' (%d chars)\n", s, p->getStringLength());
                else
                    printf("<cannot get string>\n");
                break;
            case Bool:
            case Integer:
            case Real:
                double d;
                if (p->getDouble(d))
                    printf("%.2f\n", d);
                else
                    printf("<cannot get double>\n");
                break;
            case Structure:
                indent(level);
                printf("\n{\n");
                dump(level+1, p->getStructure());
                indent(level);
                printf("}\n");
                break;
            default:
                printf("<unhandled type %d>\n", p->getType());
            }
        }
    }
  };
   
   
=== Usage Example ===
=== Usage Example ===
Line 237: Line 391:
  int main()
  int main()
  {
  {
     AiRecord fred;
     TestRecord fred;
     // Record/device support might have direct access to the fields:
     // Record/device support might have direct access to the fields:
     fred.value = 42.3;
     fred.value = 42.3;
     fred.units = "a.u.";
     fred.units = "a.u.";
    fred.point.x = 10;
    fred.point.y = 15;
   
     // Other code uses PropertyCatalog
     // Other code uses PropertyCatalog
     EpicsPropertyCatalog *pc = &fred;
     EpicsPropertyCatalog *pc = &fred;
   
   
     // It can access known properties in chosen order
     // It can access known properties in chosen order
     EpicsProperty *vp = pc->findProperty("value");
     EpicsProperty *p = pc->findProperty("value");
     double d;
     double d;
     if (vp && vp->getDouble(d))
     if (p && p->getDouble(d))
         printf("Value = %.2f\n", d);
         printf("Value = %.2f\n", d);
     else
     else
Line 254: Line 410:
   
   
     // Or inspect unknown property catalogs
     // Or inspect unknown property catalogs
     int i;
     EpicsPropertyDumper::dump(pc);
     for (i=0; i<pc->getNumProperties(); ++i)
      
    return 0;
}
 
== Comparable DataAccess Example? ==
This is the result of my attempt to implement the TestRecord
and main() code based on the August 23 snapshot of DataAccess.
 
#include <iostream>
#include <string>
#include "dataAccess.h"
#include "daPropertyId.h"
 
using namespace da;
 
static const propertyId pi_value("value");
static const propertyId pi_units("units");
static const propertyId pi_point("point");
static const propertyId pi_x("x");
static const propertyId pi_y("y");
 
class TestRecord : public propertyCatalog
{
public:
    virtual ~TestRecord() {};
    // Unsure if database code with pointer to the record
    // should still directly access the data,
    // or if the data should be protected/private.
    double value;
    string units;
    struct
     {
     {
         printf("%-10s = ", pc->getProperty(i)->getName());
         double x, y;
         switch (pc->getProperty(i)->getType())
    }  point;
 
    // propertyCatalog
    void traverse ( propertyViewer &v) const
    {
        v.reveal(pi_value, this->value);
        // Don't know how to turn a std::string into a stringSegment
        // v.reveal(pi_units, this->units); ??
        // Don't know how to reveal the 'point'
        // without changing the struct point itself
        // into a class derived from propertyCatalog
        v.reveal(pi_x,    this->point.x);
        v.reveal(pi_y,     this->point.y);
    }
    // Basically the same, supposedly no longer necessary with latest DA
    traverseModifyStatus traverse ( propertyManipulator & m)
    {
        m.reveal(pi_value, this->value);
        // m.reveal(pi_units, this->units); ??
        m.reveal(pi_x,    this->point.x);
         m.reveal(pi_y,    this->point.y);
        return tmsSuccess;
    }
    bool find ( const propertyId & id, propertyViewer & v) const
    {  // Naive, not using the 'locator' helper, but less code
        if (id == pi_value)
        {
            v.reveal(pi_value, this->value);
            return true;
        }
        if (id == pi_x)
        {
            v.reveal(pi_value, this->point.x);
            return true;
        }
        if (id == pi_y)
         {
         {
             case String:
             v.reveal(pi_value, this->point.y);
                const char *s;
             return true;
                if (pc->getProperty(i)->getCstring(s))
                    printf("'%s'\n", s);
                else
                    printf("<cannot get string>\n");
                break;
            case Bool:
            case Integer:
            case Real:
                double d;
                if (pc->getProperty(i)->getDouble(d))
                    printf("%.2f\n", d);
                else
                    printf("<cannot get double>\n");
                break;
             default:
                printf("<unhandled type>\n");
         }
         }
        return false;
     }
     }
         return 0;
};
 
class Viewer : public propertyViewer
{
public:
    void reveal ( const propertyId &id, const double &d, const propertyCatalog &x)
    {  // Don't know how to print the id.getName() stringSegment.
         cout << "?? = " << d << "\n";
    }
    void reveal ( const propertyId &, const int &, const propertyCatalog &x)
    {
    }
    void reveal ( const propertyId &, const unsigned int &, const propertyCatalog &x)
    {
    }
    void reveal ( const propertyId &, const long &, const propertyCatalog &x)
    {
    }
    void reveal ( const propertyId &, const unsigned long &, const propertyCatalog &x)
    {
    }
    void reveal ( const propertyId &, const epicsTime &, const propertyCatalog &x)
    {
    }
    void reveal ( const propertyId &, const stringSegment &, const propertyCatalog &x)
    {
    }
    void reveal ( const propertyId &, const enumStateSet & )
    {
    }
    void reveal ( const propertyId &, const arraySegment &, const propertyCatalog &x)
    {
    }
    void reveal ( const propertyId &, const propertyCatalog &x)
    {
    }
};
 
int main ()
{
    TestRecord fred;
    // Record/device support might have direct access to the fields:
    fred.value = 42.3;
    fred.units = "a.u.";
    fred.point.x = 10;
    fred.point.y = 15;
 
    // Assume other code that uses PropertyCatalog
    // without knowing about TestRecord
    propertyCatalog *pc = &fred;
    // It can access known properties in chosen order
    // Unclear how to get the 'value' as a 'double'.
    // This will 'reveal' whatever's there.
    // I assume one would have to implement a propertyCatalog
    // for target = 'double value' and then assign the given pc
    // to that target.
    Viewer dumper;
    pc->find(pi_value, dumper);
    // Or inspect unknown property catalogs
    pc->traverse(dumper);
 
    return 0;
  }
  }
== Thoughts ==
DataAccess
- does not provide type information
+ property ID handling already optimized
+ allows segmented strings and arrays
- unclear how to interface a std::string or char [] string
- cannot interface the example record without turning 'struct point' into its own class, derived from propertyCatalog?
* might be more complete but doesn't all compile
This Interface
+ does provide type information
- property IDs only strings
- requires contiguous C-String
+ supports C-string
+ can interface the struct point as is
* is incomplete but compiles

Latest revision as of 20:21, 2 September 2005

Issue

I think the following data interface is easier to understand than the data access approach.

Am I wrong, because Data Access is really much easier?

Am I right, but the approach shown here will never work?

Would it work, but performance would be unacceptable?

V4DataInter.jpg

Data Interface Classes

// Description of basic data character
enum EpicsDataCharacter
{
    Octet,     // Raw bits,known to a server/client pair, but no one else
    Bool,
    Integer,   // Discrete, available with 16, 32, 64 bits
    Real,      // Floating-point, available with 32 and 64 bits
    String,    // UTF-8
    Enum,      // >=0 Integer with state names
    Structure,
    TimeStamp  // For efficiency?
};

// Full description of the data
// This could be a pure interface,
// while Ben suggests to use a struct/class
// so that the user can easier get it as a whole.
// Ben also suggests that getNumDims throws an
// exception for scalars.
// I think a scalar has dimension 1, size 1,
// no exception.
class EpicsDataDescriptor
{
public:
    virtual EpicsDataCharacter getType() = 0;
    virtual int  getNumDims() = 0;      // Scalar, vector: 1, matrix: >1
    virtual int  getSize(int dim) = 0;  // Scalar: 1
    virtual int  getBitsize() = 0;      // bits of integer or real. 0 for rest?
    virtual bool isSigned() = 0;        // Integer might be unsigned?
};

// When the exact size-locked types are needed,
// use these integer types from stdint.h:
//     int8_t, int16_t, int32_t, int64_t
// and the following:
typedef bool          bool_t;
typedef float         real32_t;
typedef double        real64_t;
// Read-Access to the data.
// This one tries to allow as many conversions as possible.
// Given for example an underlying Real, 64bit, scalar,
// one can ask for a real64 but also int, string, ...
// You can also ask for the 5th element of a double array
// or try to access the structure detail,
// but then you have to expect a failed response.
// So a simple client uses
//    if (! getDouble()) { .. error .. }
// and a more sopisticated one uses
// getType() == Real or Integer  --> getDouble()
//
// The alternative would be individual reader interfaces
// for numerics, strings, structures, numeric arrays, string arrays, ...
// or even separate ones for int8, int16, ...
// Marty's "network accessable data" email to core talk did the latter.
// JCA also currently goes that way:
// getDBR() == DOUBLE ?   -->   ( (DBR_Double)getValue() ).getDoubleValue()
// getDBR() == INT    ?   -->   ( (DBR_Int)getValue() ).getIntValue()
class EpicsDataReader : public EpicsDataDescriptor
{
public:
    // Get data as type XX as best as possible.
    // Example: getDouble returns true for
    //          Bool (d=0.0, 1.0), Integer, Real, Enum.
    virtual bool getReal64(real64_t &r) = 0;
    virtual bool getDouble(double &d) = 0;
    virtual bool getInt(int &d) = 0;
    // String
    virtual bool getString(char *buf, int buf_len) = 0;
    virtual int  getStringLength() = 0;
    virtual bool getCstring(const char *&c_ptr) = 0;
    /// many more, at least one for each supported type.
    // A structure is accessed via a (sub-)catalog.
    // See below for PropertyCatalog.
    virtual class EpicsPropertyCatalog *getStructure() = 0;
    
    // Get array element. getDouble(x) == getArrayDouble(0, x)
    virtual bool getArrayDouble(int element, double &d) = 0;
    virtual bool getMatrixDouble(int dim, int element, double &d) = 0;
    virtual class EpicsPropertyCatalog *getArrayStructure(int element) = 0;
    /// For arrays, it could include per-element access
    /// as well as accessors that copy sub-arrays into client buffers.
};

// "Property" has a name and data reader interface
class EpicsProperty : public EpicsDataReader
{
public:
    virtual const char *getName() = 0;
};

// A catalog of properties
class EpicsPropertyCatalog
{
public:
    virtual int            getNumProperties() = 0;
    virtual EpicsProperty *findProperty(const char *name) = 0;
    virtual EpicsProperty *getProperty(int index) = 0;
    /// Could also support name <-> ID conversions and
    /// access via numeric ID.
};

C++ Implementation

// On the first read, skip to the 'Usage Example' below.
// This code isn't the greatest, but simply here to get
// example code that actually compiles & runs.
#include <string>
using std::string;
#include <string.h>

// Base class for the following EpicsProperty implementations.
// Provides default handlers for everything.
class EpicsPropertyBase : public EpicsProperty
{
public:
    EpicsPropertyBase(const char *name)
    {   this->name = name; }
    // EpicsDataDescriptor
    // defaults for scalar
    int getNumDims()
    {   return 1; }
    int getSize(int dim)
    {   return dim==0 ? 1 : 0; }
    int getBitsize()
    {   return 0; }
    bool isSigned()
    {   return true; }
    // DataReader
    bool getReal64(real64_t &r)
    {   return getDouble(r); }
    bool getDouble(double &d)
    {
        int i;
        if (!getInt(i))
            return false;
        d = i;
        return true;
    }
    bool getInt(int &i)
    {
        double d;
        if (!getDouble(d))
            return false;
        i = (int)d;
        return true;
    }    
    bool getString(char *buf, int buf_len)
    {
        const char *ptr;
        if (!getCstring(ptr))
            return false;
        strncpy(buf, ptr, buf_len-1);
        return true;
    }
    int  getStringLength()
    {   return 0; }
    bool getCstring(const char *&c_ptr)
    {   return false; }
    EpicsPropertyCatalog *getStructure()
    {   return 0; }
    bool getArrayDouble(int element, double &d)
    {   return (element == 0) ? getDouble(d) : false; }
    bool getMatrixDouble(int dim, int element, double &d)
    {   return (dim == 0) ? getArrayDouble(element, d) : false; }
    EpicsPropertyCatalog *getArrayStructure(int element)
    {   return element==0 ? getStructure() : 0; }
    // EpicsProperty
    const char *getName()
    {   return name.c_str(); }
private:
       string name;
};

// EpicsProperty for 'double'. Actual data is outside,
// the EpicsDoubleProperty keeps a pointer to it.
class EpicsDoubleProperty : public EpicsPropertyBase
{
public:
    EpicsDoubleProperty(const char *name, const double *p)
            : EpicsPropertyBase(name)
    {   this->p = p; }
    // EpicsDataDescriptor
    EpicsDataCharacter getType()
    {   return Real; }
    int getBitsize()
    {   return sizeof(double)*8; }
    // EpicsDataReader
    bool getDouble(double &d)
    {   d = *p;
        return true;
    }
private:
    const double *p;
};
// Handler for string. Data is not owned by this class.
class EpicsStringProperty : public EpicsPropertyBase
{
public:
    EpicsStringProperty(const char *name, const string *p)
            : EpicsPropertyBase(name)
    {   this->p = p; }
    // EpicsDataDescriptor
    EpicsDataCharacter getType()
    {   return String; }
    // EpicsDataReader
    int  getStringLength()
    {   return p->length(); }
    virtual bool getCstring(const char *&c_ptr)
    {
        c_ptr = p->c_str();
        return true;
    }
private:
    const string *p;
};
class EpicsPropertyCatalogBase : public EpicsPropertyCatalog
{
public:
    EpicsPropertyCatalogBase(int num_properties, EpicsProperty **properties)
            : num_properties(num_properties), properties(properties)
    {}
    // EpicsPropertyCatalog
    int getNumProperties()
    {   return num_properties; }
    EpicsProperty *findProperty(const char *name)
    {
        int i;
        for (i=0; i<num_properties; ++i)
            if (!strcmp(properties[i]->getName(), name))
                return properties[i];
        return 0;
    }
    EpicsProperty *getProperty(int index)
    {
        return (index >=0  &&  index < num_properties) ? properties[index] : 0;
    }
private:
    int            num_properties;
    EpicsProperty **properties;
};
// Handler for struct. Keeps pointers to EpicsProperty
// interfaces for the struct's fields.
class EpicsStructureProperty : public EpicsPropertyBase
{
public:
    EpicsStructureProperty(const char *name, int num_properties, EpicsProperty **properties)
            : EpicsPropertyBase(name), pc(num_properties, properties)
    {}
    // EpicsDataDescriptor
    EpicsDataCharacter getType()
    {   return Structure; }
    // EpicsDataReader
    EpicsPropertyCatalog *getStructure()
    {   return &pc; }
private:
    EpicsPropertyCatalogBase pc;
};

Example for data known at compile-time

// This is the code that provides a property catalog.
// Again skip on the first read.
//
// All of this could be generated from DBD information.
class TestRecord : public EpicsPropertyCatalogBase
{
public:
    TestRecord();
    // Unsure if database code with pointer to the record
    // should still directly access the data,
    // or if the data should be protected/private.
    double value;
    string units;
    struct
    {
        double x, y;
    }   point;
 private:
    EpicsProperty *properties[3];
    EpicsProperty *point_properties[2];
};

TestRecord::TestRecord() 
        : EpicsPropertyCatalogBase(3, properties)
{
    point_properties[0] = new EpicsDoubleProperty("x", &point.x);
    point_properties[1] = new EpicsDoubleProperty("y", &point.y);
    properties[0] = new EpicsDoubleProperty("value", &value);
    properties[1] = new EpicsStringProperty("units", &units);
    properties[2] = new EpicsStructureProperty("point", 2, point_properties);
}

Example Generic 'Dumper'

class EpicsPropertyDumper
{
public:
    static const char *type2string(EpicsDataCharacter c)
    {
        const char *strings[] =
        {
            "Octet",
            "Bool",
            "Integer",
            "Real",
            "String",
            "Enum",
            "Structure",
            "TimeStamp"
        };
        
        if (c < 0  ||  c >= sizeof(strings)/sizeof(const char *))
            return "<unknown>";
        return strings[c];
    }
    
    static void dump(EpicsPropertyCatalog *pc)
    {   dump(0, pc); }
private:
    static void indent(int level)
    {   for (int i=0; i<level; ++i)  printf("    "); }
    static void dump(int level, EpicsPropertyCatalog *pc)
    {
        double d;
        int i;
        for (i=0; i<pc->getNumProperties(); ++i)
        { 
           indent(level);
           EpicsProperty *p = pc->getProperty(i);
           EpicsDataCharacter type = p->getType();
           printf("%s (%s, %d %s, %d bits) = ",
                  p->getName(), type2string(type),
                  p->getSize(0),
                  (p->getSize(0) == 1 ? "element" : "elements"),
                  p->getBitsize());
           switch (type)
           {
           case String:
               const char *s;
               if (p->getCstring(s))
                   printf("'%s' (%d chars)\n", s, p->getStringLength());
               else
                   printf("<cannot get string>\n");
               break;
           case Bool:
           case Integer:
           case Real:
               double d;
               if (p->getDouble(d))
                   printf("%.2f\n", d);
               else
                   printf("<cannot get double>\n");
               break;
           case Structure:
               indent(level);
               printf("\n{\n");
               dump(level+1, p->getStructure());
               indent(level);
               printf("}\n");
               break;
           default:
               printf("<unhandled type %d>\n", p->getType());
           }
        }
    }
}; 

Usage Example

#include <stdio.h>

int main()
{
    TestRecord fred;
    // Record/device support might have direct access to the fields:
    fred.value = 42.3;
    fred.units = "a.u.";
    fred.point.x = 10;
    fred.point.y = 15;
    
    // Other code uses PropertyCatalog
    EpicsPropertyCatalog *pc = &fred;

    // It can access known properties in chosen order
    EpicsProperty *p = pc->findProperty("value");
    double d;
    if (p && p->getDouble(d))
        printf("Value = %.2f\n", d);
    else
        printf("Cannot get value as double\n");

    // Or inspect unknown property catalogs
    EpicsPropertyDumper::dump(pc);
    
    return 0;
}

Comparable DataAccess Example?

This is the result of my attempt to implement the TestRecord and main() code based on the August 23 snapshot of DataAccess.

#include <iostream>
#include <string>
#include "dataAccess.h"
#include "daPropertyId.h"
using namespace da;
static const propertyId pi_value("value");
static const propertyId pi_units("units");
static const propertyId pi_point("point");
static const propertyId pi_x("x");
static const propertyId pi_y("y");
class TestRecord : public propertyCatalog
{
public:
    virtual ~TestRecord() {};
    // Unsure if database code with pointer to the record
    // should still directly access the data,
    // or if the data should be protected/private.
    double value;
    string units;
    struct
    {
        double x, y;
    }   point;
    // propertyCatalog
    void traverse ( propertyViewer &v) const
    {
        v.reveal(pi_value, this->value);
        // Don't know how to turn a std::string into a stringSegment
        // v.reveal(pi_units, this->units); ??
        // Don't know how to reveal the 'point'
        // without changing the struct point itself
        // into a class derived from propertyCatalog
        v.reveal(pi_x,     this->point.x);
        v.reveal(pi_y,     this->point.y);
    }
    // Basically the same, supposedly no longer necessary with latest DA
    traverseModifyStatus traverse ( propertyManipulator & m)
    {
        m.reveal(pi_value, this->value);
        // m.reveal(pi_units, this->units); ??
        m.reveal(pi_x,     this->point.x);
        m.reveal(pi_y,     this->point.y);
        return tmsSuccess;
    }
    bool find ( const propertyId & id, propertyViewer & v) const
    {   // Naive, not using the 'locator' helper, but less code
        if (id == pi_value)
        {
            v.reveal(pi_value, this->value);
            return true;
        }
        if (id == pi_x)
        {
            v.reveal(pi_value, this->point.x);
            return true;
        }
        if (id == pi_y)
        {
            v.reveal(pi_value, this->point.y);
            return true;
        }
        return false;
    }
};
class Viewer : public propertyViewer
{
public:
   void reveal ( const propertyId &id, const double &d, const propertyCatalog &x)
   {   // Don't know how to print the id.getName() stringSegment.
       cout << "?? = " << d << "\n";
   }
   void reveal ( const propertyId &, const int &, const propertyCatalog &x)
   {
   }
   void reveal ( const propertyId &, const unsigned int &, const propertyCatalog &x)
   {
   }
   void reveal ( const propertyId &, const long &, const propertyCatalog &x)
   {
   }
   void reveal ( const propertyId &, const unsigned long &, const propertyCatalog &x)
   {
   }
   void reveal ( const propertyId &, const epicsTime &, const propertyCatalog &x)
   {
   }
   void reveal ( const propertyId &, const stringSegment &, const propertyCatalog &x)
   {
   }
   void reveal ( const propertyId &, const enumStateSet & )
   {
   }
   void reveal ( const propertyId &, const arraySegment &, const propertyCatalog &x)
   {
   }
   void reveal ( const propertyId &, const propertyCatalog &x)
   {
   }
};
int main ()
{
   TestRecord fred;
    // Record/device support might have direct access to the fields:
    fred.value = 42.3;
    fred.units = "a.u.";
    fred.point.x = 10;
    fred.point.y = 15;
    // Assume other code that uses PropertyCatalog
    // without knowing about TestRecord
    propertyCatalog *pc = &fred;
    // It can access known properties in chosen order
    // Unclear how to get the 'value' as a 'double'.
    // This will 'reveal' whatever's there.
    // I assume one would have to implement a propertyCatalog
    // for target = 'double value' and then assign the given pc
    // to that target.
    Viewer dumper;
    pc->find(pi_value, dumper);
    // Or inspect unknown property catalogs
    pc->traverse(dumper);
   return 0;
}


Thoughts

DataAccess

- does not provide type information
+ property ID handling already optimized
+ allows segmented strings and arrays
- unclear how to interface a std::string or char [] string
- cannot interface the example record without turning 'struct point' into its own class, derived from propertyCatalog?
* might be more complete but doesn't all compile

This Interface

+ does provide type information
- property IDs only strings
- requires contiguous C-String
+ supports C-string
+ can interface the struct point as is
* is incomplete but compiles