Difference between revisions of "V4 Data Interface"
From EPICSWIKI
KayKasemir (talk | contribs) |
KayKasemir (talk | contribs) (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 | ||
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. | // 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 | 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 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 | /// 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, | // 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; | |||
return true; | |||
} | } | ||
bool getInt(int &i) | bool getInt(int &i) | ||
{ | { | ||
double d; | double d; | ||
if (getDouble(d)) | if (!getDouble(d)) | ||
return false; | |||
i = (int)d; | |||
return true; | |||
} | } | ||
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); | |||
return true; | |||
} | |||
int getStringLength() | |||
{ return 0; } | |||
bool getCstring(const char *&c_ptr) | 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 | // EpicsProperty | ||
const char *getName() | |||
{ 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) | |||
: EpicsPropertyBase(name) | : EpicsPropertyBase(name) | ||
{ this->p = p; } | { this->p = p; } | ||
// EpicsDataDescriptor | |||
EpicsDataCharacter getType() | |||
{ return Real; } | |||
int getBitsize() | int getBitsize() | ||
{ return sizeof(double)*8; } | { return sizeof(double)*8; } | ||
// EpicsDataReader | // EpicsDataReader | ||
bool getDouble(double &d) | bool getDouble(double &d) | ||
{ d = *p; } | { d = *p; | ||
return true; | |||
} | |||
private: | private: | ||
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) | |||
: EpicsPropertyBase(name) | : EpicsPropertyBase(name) | ||
{ this->p = p; } | { this->p = p; } | ||
// EpicsDataDescriptor | |||
EpicsDataCharacter getType() | |||
{ return String; } | |||
// 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 | class TestRecord : public EpicsPropertyCatalogBase | ||
{ | { | ||
public: | public: | ||
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; | ||
struct | |||
{ | |||
double x, y; | |||
EpicsProperty * | } point; | ||
private: | |||
EpicsProperty * | 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 === | === Usage Example === | ||
Line 237: | Line 391: | ||
int main() | int main() | ||
{ | { | ||
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 * | EpicsProperty *p = pc->findProperty("value"); | ||
double d; | double d; | ||
if ( | 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 | ||
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; | |||
} | } | ||
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?
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