V4 Data Interface
From EPICSWIKI
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 }