V4 Design: Assembling Record Support
I have been asked to present an example of how record type definitions and the corresponding record support can be assembled from small general purpose elements. My example is the IVOA/IVOV block of functionality for output records; the example record type is the aoRecord.
I am leaving out all the non-essential fields and methods. Nevertheless, all example code compiles (with gcc -pedantic) and is (at least rudimentarily) tested.
The dbd definition for the aoRecord:
record(ao) extends iocRecord { field(value, float64) { asl(0) dynamic(yes) } #... field(invalidOutput,struct(invalidOutput)<float64>) #... }
It uses the following menu and struct definitions:
menu(invalidAction) { choice(invalidActionContinue_normally,"Continue normally") choice(invalidActionDon_t_drive_outputs,"Don't drive outputs") choice(invalidActionSet_output_to_IVOV,"Set output to IVOV") } struct(invalidOutput)<Value> { field(invalidAction,menu(menuIvoa)) field(invalidValue,Value) }
Note the type argument Value in angle brackets. he notation is similar to the one in C++.
From these two dbd files, we generate the following header files. Note: I do not consider generated DA interfaces here.
First aoRecord.h:
// header file generated from aoRecord.dbd #ifndef aoRecordH #define aoRecordH struct aoRecord // : iocBaseRecord { //... alarmSeverityMenu newAlarmSeverity; //... float64 value; //... invalidOutputStruct<float64> invalidOutput; //... // virtual method overrides void process(); }; #endif
We see that the sub structure invalidOutputStruct gets the value type as template argument. No to the struct itself:
// header file generated from InvalidOutput.dbd #ifndef invalidOutputStructH #define invalidOutputStructH enum invalidActionMenu { invalidActionContinue_normally, invalidActionDon_t_drive_outputs, invalidActionSet_output_to_IVOV }; // String invalidActionMenuStrings[3] = { // "Continue normally", // "Don't drive outputs", // "Set output to IVOV" // }; template <typename Value> struct invalidOutputStruct { invalidActionMenu invalidAction; Value invalidValue; }; #endif
The type parameter in the dbd definition gets translated as a template parameter for the struct.
No to the interesting part: the struct and record support. For the struct support for invalidOutputStruct I present two variants. The first one uses function objects, whereas the second one doesn't. In both cases, the struct suport consists of exactly one C function:
// support routines for invalidOutputStruct // definitions are inline only for (my) convenience #ifndef invalidOutputStructSupportH #define invalidOutputStructSupportH // #include <stdexcept> #include "invalidOutputStruct.h" template <typename Value, typename WriteOutput> inline void processInvalidOutput( invalidOutputStruct<Value>& ios, alarmSeverityMenu sevr, Value& value, WriteOutput write) { if (sevr < alarmSeverityINVALID) { write(value); return; } switch (ios.invalidAction) { case invalidActionContinue_normally: write(value); break; case invalidActionDon_t_drive_outputs: break; case invalidActionSet_output_to_IVOV: write(ios.invalidValue); break; // not really necessary, since C++ checks this statically // default: // throw std::out_of_range("invalidAction"); } } // alternative interface without functor: // return the value that should be written or // a null pointer to indicate "don't write" template <typename Value> inline Value *processInvalidOutput_alt( invalidOutputStruct<Value>& ios, alarmSeverityMenu sevr, Value& value) { if (sevr < alarmSeverityINVALID) { return &value; } switch (ios.invalidAction) { case invalidActionContinue_normally: return &value; case invalidActionDon_t_drive_outputs: return 0; case invalidActionSet_output_to_IVOV: return &ios.invalidValue; } } #endif
Now, aoRecord support can use this. Note that process() is now a method of struct aoRecord, see generated header file above.
#include <stdio.h> #include <functional> #include "epicsTypes.h" #include "alarmSeverityMenu.h" #include "invalidOutputStruct.h" #include "aoRecord.h" #include "invalidOutputStructSupport.h" void writeValue(aoRecord *prec, float64 value) { prec->value = value; //...write output... // test: printf("writing %lf\n", value); } void aoRecord::process() { //... // handle invalidOutput processInvalidOutput( invalidOutput, newAlarmSeverity, value, std::bind1st( std::ptr_fun(writeValue), this ) ); // alternative for those who don't like functors: // float64 *pvalue = processInvalidOutput_alt( // invalidOutput, newAlarmSeverity, value ); // if (pvalue) writeValue(this, *pvalue); //... }