Difference between revisions of "V4 Data Interface"

From EPICSWIKI
 
(added structure handling to example)
Line 16: Line 16:
     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?
  };
  };
Line 31: Line 31:
     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 an underlying Real, 64bit, scalar,
// one can ask for a real64 but also int, string, ...
  //
  // 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).
  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 90:
  {
  {
  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 99:
=== 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 105:
  #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 112:
     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; }
  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 212:
     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 271:
     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 370:
  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 389:
   
   
     // Or inspect unknown property catalogs
     // Or inspect unknown property catalogs
     int i;
     EpicsPropertyDumper::dump(pc);
    for (i=0; i<pc->getNumProperties(); ++i)
      
     {
    return 0;
        printf("%-10s = ", pc->getProperty(i)->getName());
}
        switch (pc->getProperty(i)->getType())
 
        {
The output of this test:
            case String:
Value = 42.30
                const char *s;
value (Real, 1 element, 64 bits) = 42.30
                if (pc->getProperty(i)->getCstring(s))
units (String, 1 element, 0 bits) = 'a.u.' (4 chars)
                    printf("'%s'\n", s);
point (Structure, 1 element, 0 bits) =
                else
{
                    printf("<cannot get string>\n");
    x (Real, 1 element, 64 bits) = 10.00
                break;
    y (Real, 1 element, 64 bits) = 15.00
            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 0;
  }
  }

Revision as of 20:21, 4 August 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?

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
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 an underlying Real, 64bit, scalar,
// one can ask for a real64 but also int, string, ...
//
// 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).
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; }
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;
}

The output of this test:

Value = 42.30
value (Real, 1 element, 64 bits) = 42.30
units (String, 1 element, 0 bits) = 'a.u.' (4 chars)
point (Structure, 1 element, 0 bits) = 
{
   x (Real, 1 element, 64 bits) = 10.00
   y (Real, 1 element, 64 bits) = 15.00
}