EPICS v4 Working Group, Working Draft, 19-Dec-2011

This version:
Latest version:
Marty Kraimer, BNL


A pvIOC is a network accessable smart real time database. Each record contains a top level pvData structure. Each field of the structure can optionally have support attached to it. One of the methods of Support is named process. Support is what makes the database a "smart" database. A record can be processed, which results in calls to the attached support. A pvAccess server is provided so that the records can be accesed via the network.

pvAccess is an implementation of Channel Access, i. e. network support for transmitting Process Variable Data, that fully supports pvData.

pvData (Process Variable Data) defines and implements an efficent way to store, access, and transmit memory resident structured data

This document describes the C++ implementation of EPICS v4 pvIOC.

For more information about the EPICS, please refer to the home page of the Experimental Physics and Industrial Control System.

Status of this Document

This is the 19-Dec-2011 version of the C++ implementation of pvIOC. Only a partial C++ implementation of pvIOC is implemented. This document only describes what is implemented.



This product is available via an open source license

This document overview for pvIOCDataCPP. Doxygen documentation is available at doxygenDoc

NOTE: service developers only need to understand the next section. The remaining sections describe implementation.

pvIOC is one of a set of related projects:

Describes and implements structured data.
Provides networking for pvData.
Provides a database based on pvData together with record processing, record scanning, and extensible support.

For pvData and pvAccess both Java and C++ implementations are available.

At the present time the only parts of pvIOCCP that are implemented are:

Support for implementing services.
This provides pvAccess for V3 records.

These are described in this document. Everything else that appears in this project is work in progress.

A complete Java implementation of pvIOC is available but it can not be used inside a V3 IOC. It is currently named javaIOC.


Currently the only C++ support for services is support for channelRPC, i. e. support for services where the client and server communicate via pvAccess::channelRPC. In the near future support is planned for for channelPutGet.

There is an example service in testApp/exampleServiceRPC. This example can be run either as a main program or as part of V3 IOC using the v3Channel support described in the next section. To run it as a main program just execute the command:


To execute it as part of a V3 IOC do the following:

cd iocBoot/testV3Channel
 ../../bin/<arch>/testV3Channel st.cmd

The service support is described in service.h

class ServiceRPC
    virtual void destroy() = 0;
    virtual void request(
        ChannelRPCRequester::shared_pointer const & channelRPCRequester,
        PVStructure::shared_pointer const & pvArgument) = 0;

class ServiceChannelRPC :
    public std::tr1::enable_shared_from_this<ServiceChannelRPC>
    ServiceChannelRPC(String channelName,ServiceRPC::shared_pointer const & serviceRPC);
    virtual ~ServiceChannelRPC();
    virtual void destroy();

ServiceRPC is the class the the service must implement. It has the methods:

Called when service is being destroyed.
This is called when a client issues a channelRPC request.

ServiceChannelRPC is a class that implements the server side of channelRPC. It will call ServiceRPC when a client issues a channelRPC request. It takes care of all other pvAccess requirements. It has the methods:

ServiceChannelRPC(String channelName,ServiceRPC::shared_pointer const & serviceRPC)
The service must call this constructor. channelName is the name of the service. Note that the ServiceRPC implemention must be passed to this constructor.
Can be called to destroy the service.

The example is in testApp/exampleServiceRPC.

exampleServiceRPC.h contains:

class ExampleServiceRPC :
  public virtual ServiceRPC,
  public std::tr1::enable_shared_from_this<ExampleServiceRPC>
    virtual ~ExampleServiceRPC();
    virtual void destroy();
    virtual void request(
        epics::pvAccess::ChannelRPCRequester::shared_pointer const & channelRPCRequester,
        epics::pvData::PVStructure::shared_pointer const & pvArgument);
    ExampleServiceRPC::shared_pointer getPtrSelf()
        return shared_from_this();

This just shows how the example extends ServiceRPC. All services should have a similar definition except for the name, i. e. replace ExampleService with some other name.

The implementation is in exampleServiceRPC.cpp:



void ExampleServiceRPC::destroy()

void ExampleServiceRPC::request(
    ChannelRPCRequester::shared_pointer const & channelRPCRequester,
    epics::pvData::PVStructure::shared_pointer const & pvArgument)
    String builder;
    builder += "pvArgument ";
    bool is = NTNameValuePair::isNTNameValuePair(pvArgument.get());
    if(is) {
        builder += "is a NTNameValuePair\n";
    } else {
        builder += "is not a NTNameValuePair\n ";
    FieldCreate * fieldCreate = getFieldCreate();
    NTField *ntField = NTField::get();
    PVNTField *pvntField = PVNTField::get();
    int n = 2;
    FieldConstPtr fields[2];
    fields[0] = fieldCreate->createScalarArray("position",pvDouble);
    fields[1] = ntField->createAlarmArray("alarms");
    PVStructure::shared_pointer pvStructure = NTTable::create(
    NTTable ntTable(pvStructure);
    PVDoubleArray *pvPositions
        = static_cast<PVDoubleArray *>(ntTable.getPVField(0));
    double positions[2];
    positions[0] = 1.0;
    positions[1] = 2.0;
    PVStructureArray *pvAlarms
        = static_cast<PVStructureArray *>(ntTable.getPVField(1));
    PVAlarm pvAlarm;
    Alarm alarm;
    PVStructurePtr palarms[n];
    for(int i=0; i<n; i++) {
        palarms[i] = pvntField->createAlarm(0);
    String labels[n];
    labels[0] = pvPositions->getField()->getFieldName();
    labels[1] = pvAlarms->getField()->getFieldName();
    PVStringArray *label = ntTable.getLabel();

The only interesting method is request. The example shows how to see if the argument is a NTNameValuePair. It creates an NTTable to return to the client. Note that channelRPCRequester->requestDone(Status::OK,pvStructure) must be called.

The Make file shows how to build as both a standalone application as as part of a V3 IOC.\

exampleServiceRPCMain.cpp is the main program for a standalone application.

int main(int argc,char *argv[])
    PVServiceChannelCTX::shared_pointer myCTX
        = PVServiceChannelCTX::shared_pointer(new PVServiceChannelCTX());
    ExampleServiceRPC::shared_pointer example
        = ExampleServiceRPC::shared_pointer(new ExampleServiceRPC());
    ServiceChannelRPC::shared_pointer serviceChannelRPC
        = ServiceChannelRPC::shared_pointer(
            new ServiceChannelRPC("serviceRPC",example));
    cout << "serviceRPC\n";
    string str;
    while(true) {
        cout << "Type exit to stop: \n";
        if(str.compare("exit")==0) break;


The first statement MUST be present. It starts the pvAccess server. The second statement creates the service. The next statement is what installs the service. The remaining statements just wait for the request to terminate the main program. Except for "ExampleService" any service should have similar code.

exampleServiceRegister.dbd and exampleServiceRegister.cpp are required to run the service as part os a V3 IOC. The dbd file is:


This is the dbd statement that will have the V3 IOC register the code desctribed in the next file:

The cpp file is:

/* define arguments for the following command:
 * startExampleService channelName
static const iocshArg startExampleServiceArg0 = {"channelName", iocshArgString};
static const iocshArg *const startExampleServiceArgs[] = {
static const iocshFuncDef startExampleServiceFuncDef = {
    "startExampleService", 1, startExampleServiceArgs};
static void startExampleService(const iocshArgBuf *args)
    // the channel name is the single argument which is a string
    const char * channelName = args[0].sval;
    if(channelName==0 || channelName[0]==0) {
        printf("illegal channelName\n");
    ExampleServiceRPC::shared_pointer example
        = ExampleServiceRPC::shared_pointer(new ExampleServiceRPC());
    ServiceChannelRPC *serviceChannelRPC
        = new ServiceChannelRPC("serviceRPC",example);

static void startExampleServiceRegister(void)
    static int firstTime = 1;
    if (firstTime) {
        firstTime = 0;
        iocshRegister(&startExampleServiceFuncDef, startExampleService);


Again any service can provide similar code. Just change ExampleService to something else.


Service provides three classes that can be used by service support:

A class that implements most of ChannelProvider.
A class that implements Channel. It implements all create methods by issuing an error to the client. A service can extend it and implement only the methods required by the service.
A class that is a complete implementation of ChannelProvider. It extends ChannelBaseProvider. It can be used by services that create their own channelNames with an associated top level structure.

The code that implements the pvAccess support for V3 records uses the first two classes but since it is providing access to database the third class is not used. Instead it implements ChannelProvider .

The interfaces for ChannelBase and ChannelBaseProvider are described in channelBase.h. PVServiceProvider is described in pvServiceProvider.h. The following provides a brief description. Look at the header files for complete details.


channelBaseProvider takes care of "lifetime" issues. It registers the provider, keeps a list of all channels the service provider creates and destroys all channels when the provider is destroyed.

class ChannelBaseProvider : public ChannelProvider {
        String providerName
    virtual ~ChannelBaseProvider();
    virtual String getProviderName();
    virtual void destroy();
    virtual ChannelFind::shared_pointer channelFind(
        String channelName,
        ChannelFindRequester::shared_pointer const &
            channelFindRequester) = 0;
    virtual Channel::shared_pointer createChannel(
        String channelName,
        ChannelRequester::shared_pointer const &requester,
        short priority);
    virtual Channel::shared_pointer createChannel(
        String channelName,
        ChannelRequester::shared_pointer  const &
        short priority,
        String address) = 0;
    ChannelBaseProvider::shared_pointer getChannelProvider()
        return channelProviderPtr;
    // following called by derived class
    void channelFound(
        bool found,
        ChannelFindRequester::shared_pointer const & requester);
    void channelNotCreated(
        ChannelRequester::shared_pointer const & requester);
    void channelCreated(ChannelBase::shared_pointer channel);
    void removeChannel(ChannelBase &channel);

The service must implement the following two ChannelProvider methods:

The service must keep a list or directory the channel names it supports. channelFind looks at the list or directory to see if the requested channelName is one that it supports. It calls channelFound to report the result.
This creates a new Channel if the requested channelName is a name that the service supports. createChannel calls either channelNotCreated or channelCreated.

channelBaseProvider implements the following methods:

This is called to report the channelFind request.
This is called if createChannel fails.
This is called when a new channel is created. The channel is put on a list so that when channelBaseProvider is destroyed it can destroy each channel.
This must be called when a channel is destroyed. Note that channels are normally destroyed by remote pvAccess rather than because channelBaseProvider is destroyed.


This implements all the Channel methods. This a service only needs the implement the methods it supports. For example a service may only implement ChannelGet and ChannelPut. In that case it only needs to implement the createChannelGet and createChannelPut methods of Channel as well as ChannelGet and ChannelPut.

class ChannelBase :
  public virtual Channel,
  public std::tr1::enable_shared_from_this<ChannelBase>
        std::tr1::shared_ptr<ChannelBaseProvider> const &channelProvider,
        ChannelRequester::shared_pointer const & requester,
        String channelName
    virtual ~ChannelBase();
    virtual void destroy();
    virtual String getRequesterName();
    virtual void message(
        String message,
        MessageType messageType);
    virtual ChannelProvider::shared_pointer const & getProvider();
    virtual String getRemoteAddress();
    virtual Channel::ConnectionState getConnectionState();
    virtual String getChannelName();
    virtual ChannelRequester::shared_pointer const &
    virtual bool isConnected();
    virtual void getField(
        GetFieldRequester::shared_pointer const &requester,
        String subField);
    virtual AccessRights getAccessRights(
        PVField::shared_pointer const &pvField);
    virtual ChannelProcess::shared_pointer createChannelProcess(
        ChannelProcessRequester::shared_pointer const &requester,
        PVStructure::shared_pointer const &pvRequest);
    virtual ChannelGet::shared_pointer createChannelGet(
        ChannelGetRequester::shared_pointer const &requester,
        PVStructure::shared_pointer const &pvRequest);
    virtual ChannelPut::shared_pointer createChannelPut(
        ChannelPutRequester::shared_pointer const &requester,
        PVStructure::shared_pointer const &pvRequest);
    virtual ChannelPutGet::shared_pointer createChannelPutGet(
        ChannelPutGetRequester::shared_pointer const &requester,
        PVStructure::shared_pointer const &pvRequest);
    virtual ChannelRPC::shared_pointer createChannelRPC(
        ChannelRPCRequester::shared_pointer const &requester,
        PVStructure::shared_pointer const &pvRequest);
    virtual Monitor::shared_pointer createMonitor(
        MonitorRequester::shared_pointer const &requester,
        PVStructure::shared_pointer const &pvRequest);
    virtual ChannelArray::shared_pointer createChannelArray(
        ChannelArrayRequester::shared_pointer const &requester,
        PVStructure::shared_pointer const &pvRequest);
    virtual void printInfo();
    virtual void printInfo(StringBuilder out);
    // following called by derived classes
    void addChannelProcess(ChannelProcess &);
    void addChannelGet(ChannelGet &);
    void addChannelPut(ChannelPut &);
    void addChannelPutGet(ChannelPutGet &);
    void addChannelMonitor(Monitor &);
    void addChannelRPC(ChannelRPC &);
    void addChannelArray(ChannelArray &);
    void removeChannelProcess(ChannelProcess &);
    void removeChannelGet(ChannelGet &);
    void removeChannelPut(ChannelPut &);
    void removeChannelPutGet(ChannelPutGet &);
    void removeChannelMonitor(Monitor &);
    void removeChannelRPC(ChannelRPC &);
    void removeChannelArray(ChannelArray &);

The service only needs to implement the set of create methods that it supports. For each the service must also implement the associated ChannelXXX interfaces. The service may also want to implement the printInfo methods.

ChannelBase implements the following methods:

If channelProcess is supported then this must be called whenever createChannelProcess is successful.
If channelGet is supported then this must be called whenever createChannelGet is successful.
If channelPut is supported then this must be called whenever createChannelPut is successful.
If channelPutGet is supported then this must be called whenever createChannelPutGet is successful.
If channelMonitor is supported then this must be called whenever createChannelMonitor is successful.
If channelRPC is supported then this must be called whenever createChannelRPC is successful.
If channelArray is supported then this must be called whenever createChannelArray is successful.
This is called when a channelProcess is destroyed.
This is called when a channelGet is destroyed.
This is called when a channelPut is destroyed.
This is called when a channelPutGet is destroyed.
This is called when a channelMonitor is destroyed.
This is called when a channelRPC is destroyed.
This is called when a channelArray is destroyed.


This is a complete implementation of ChannelProvider. The code that uses it must implement interface ServicePVTop. The code must also create a top level PVStructure, which is what the pvAccess client will access. ServiceTop is defined as:

class ServicePVTop
    virtual ~ServicePVTop(){}
    virtual epics::pvData::String getName() = 0;
    virtual ChannelBase::shared_pointer createChannel(
        epics::pvAccess::ChannelRequester::shared_pointer const &requester,
        std::tr1::shared_ptr<PVServiceProvider> const &provider) = 0;
    virtual void destroy() = 0;


Get the channel name.
Create a channel.
This is called when the serviceTop itself is being destroyed.



V3Channel provides access to V3 records. It can be used accesss V3 records by other code in the same IOC or by pvAccess network clients via remote pvAccess. It implements channelProcess, channelGet, channelPut, channelArray, and Monitor. It does not implement channelPutGet or channelRPC since, at least for now, these do not apply to V3 records.

The basic idea is to map what V3 records provide into a PVStruructure that has the same information. For example if a client asks for a ChannelGet for everything for an analog type channel what the client gets is something like:

record double01
    double value 2.0
    structure alarm
        severity major
        message LOLO
    timeStamp 2011-04-13 06:56:06.932
    structure display
        string description 
        string format %f
        string units Counts
        structure limit
            double low 0.0
            double high 10.0
    structure control
        structure limit
            double low -0.1
            double high 9.9
        double minStep 0.0

The channelName can be either just a record name or a "recordName.XXX" where XXX is the V3 field name, e. g. "SCAN".

Remaining tasks

The main remaining tasks are:
Access Security
Since V3Channnel is not using any of the remote part of V3CA, it "sidesteps" access security. The V3 access security system is independent of CAV3 so V3Channel will be able to implement access security without going through V3CA but this has not been done.
Monitor Algorithm
The javaIOC supports an extensible set of monitor algorithms. It provides support for onChange and onPercentChange but allows additional algorithms. This same support will be implemented by pvIOCCP.
Alarm Limits
Currently it is not possible to get alarmLimits via V3Channel.
This creates the introspection interface that is returned by getField. Currently it always has property fields alarm, timeStamp, and display. If the field is not an array it also has a property field control. It might be better to look at the record support entry table to see if display and control should be included.

Getting started

NOTE: pvIOCCP has a zip file example/example.zip that provides an example IOC application that provides V3Channel support. To use it copy and unzip and read the README file.

pvIOCCPP runs in a regular V3 IOC as defined by EPICS base. It uses the base build system and is built as an EPICS application. To use V3Channel do the following:

Create an epics application
Look at the example to see how to get started or use MakeBaseApp that comes with EPICS base.
Add the location of pvDataCPP, pvAccessCPP, and PVIOCCP. For example:
In the src directory where your application is being built add an include statement for pvIOC.dbd. For example:
include "base.dbd"
include "pvIOC.dbd"
After the iocInit command in the st.cmd file add:


The implementation consists of the following header files:

The V3Channel interface. It defines extensions for both ChannelProvider and Channel. This is what client code including remote pvAccess sees.
The interace for transfering data between V3 records and pvData, i. e. the code that converts between the V3 data and pvData.
Because arrays are more complex than scalar data they are handled by a separate interface instead of by v3Util itself.
db_post_event is implement in code (dbEvent) that passes events directly to CAV3. Thus the only way V3Channel can trap monitors is via CAV3. V3Channel only uses CAV3 for scalar numeric data, timeStamp, and alarms. All other monitored data is obtained from the V3 record itself.
This is code that interfaces between pvAccess threads and CAV3 context.

Associated with each header file is a corresponding implementation file which has the same file name as the header but with an extension of ".cpp". The remainder of this section gives a brief description of each of these software components with an emphasis on how data is converted between pvData and data in V3 records.

Main Concepts

In epics V3 there are two interfaces for accessing data:

This defines all the DBR_XXX types and associated C structures as seen by CAV3 clients. V3Channel, with the exception of V3CAMonitor does not use this. Even for monitors only a few of the DBR_XXX types are used.
This and some additional interfaces like the RSET (record support entry table) and the DBADDR are what V3Channel uses to get/put data from the V3 records. Wherever possible data is accessed via the DBADDR and RSET. pvAccess method pvPutField is used to put data to fields that have "side effects". For example writing to the SCAN field has side effects.


This defines two interfaces:

The implementation of ChannelProvider.
The implementation of Channel.

V3ChannelProvider is defined as:

class V3ChannelProvider : publicChannelProvider {
    static V3ChannelProvider &getChannelProvider();
    virtual String getProviderName();
    virtual void destroy();
    virtual ChannelFind *channelFind(String channelName,ChannelFindRequester *channelFindRequester);
    virtual Channel *createChannel(String channelName,ChannelRequester *channelRequester,short priority);
    virtual Channel *createChannel(String channelName,ChannelRequester *channelRequester,short priority,String address);
    static void removeChannel(ChannelListNode &node);


This returns the single instance od the V3ChannelProvider.
This returns "v3Channel"
Calls dbNameToAddr to see if the requested channelName is in the V3 database. If calls channelFindRequester->channelFindResult to report the result. It always returns null.
Calls dbNameToAddr to see if the requested channelName is in the V3 database. If not it calls channelRequester->channelCreated(notFoundStatus,0) and returns null. If it is in the V3 database it creates a new V3Channel, calls init, calls channelRequester->channelCreated(Status::OK,v3Channel) and returns the newly created V3Channel.

V3Channel is defined as:

class V3Channel : public virtual Channel {
        V3ChannelProvider &provider,ChannelRequester &requester,String name,
        std::auto_ptr<DbAddr> addr);
    ChannelListNode& init();
    virtual void destroy();
    virtual String getRequesterName();
    virtual void message(String message,MessageType messageType);
    virtual ChannelProvider *getProvider();
    virtual String getRemoteAddress();
    virtual Channel::ConnectionState getConnectionState();
    virtual String getChannelName();
    virtual ChannelRequester *getChannelRequester();
    virtual bool isConnected();
    virtual void getField(GetFieldRequester *requester,String subField);
    virtual AccessRights getAccessRights(PVField *pvField);
    virtual ChannelProcess *createChannelProcess(ChannelProcessRequester *channelProcessRequester,PVStructure *pvRequest);
    virtual ChannelGet *createChannelGet(ChannelGetRequester *channelGetRequester,PVStructure *pvRequest);
    virtual ChannelPut *createChannelPut(ChannelPutRequester *channelPutRequester,PVStructure *pvRequest);
    virtual ChannelPutGet *createChannelPutGet(ChannelPutGetRequester *channelPutGetRequester,PVStructure *pvRequest);
    virtual ChannelRPC *createChannelRPC(ChannelRPCRequester *channelRPCRequester,PVStructure *pvRequest);
    virtual Monitor *createMonitor(MonitorRequester *monitorRequester,PVStructure *pvRequest);
    virtual ChannelArray *createChannelArray(ChannelArrayRequester *channelArrayRequester,PVStructure *pvRequest);
    virtual void printInfo();
    virtual void printInfo(StringBuilder out);
    // following only called by other V3ChannelXXX code
    void removeChannelProcess(ChannelProcessListNode &);
    void removeChannelGet(ChannelGetListNode &);
    void removeChannelPut(ChannelPutListNode &);
    void removeChannelMonitor(ChannelMonitorListNode &);
    void removeChannelArray(ChannelArrayListNode &);


This is the constructor called by V3ChannelProvider.
This is called by V3ChannelProvider after it has created the V3Channel. It's primary function is to create a FieldConstPtr that provides the introspection interface for the field being accessed. This is what is returned by getField. It is always a structure with a value field that has a type determined by the V3 field being accessed. It always has property fields value, timeStamp, alarm, and display. In addition if the value field is not an array a control property field. NOTE: It might be better to look at the record support entry table to determine what property fields to include.
This is called by whoever accesses the V3Channel. For example remote PVAccess calls this when the client disconnects from the channel.
Get the requester that created the V3Channel.
This is a method that can be called by any code associated with the V3Channel. It just passes the message to the requester.
This returns the provider which is V3ChannelProvider.
This returns String("local").
This returns Channel::CONNECTED;
This gets the requester that asked to create the channel.
Always returns true.
This returns the introspection interface created by init.
This is currently throws an exception. As stated in the TODO list this should interact with the V3 access security system.
This creates a V3ChannelProcess and then call V3ChannelProcess::init. Process is implemented via a V3 dbPutNotify to the .PROC field of the record.
This creates a V3ChannelGet and then calls V3ChannelGet::init. The implementation uses V3Util to do most of the work. See V3Util below for details. If an option to process is given then the record is processed via a dbPutNotify to the PROC field before data is fetched. Note that display and control data is only returned for the first get, i.e. display and control data do not change.
This creates a V3ChannelPut and then calls V3ChannelPut::init. The implementation uses V3Util to do most of the work. See V3Util below for details. It is responsible for creating the PVStructure that holds the data and for getting/putting data between the V3 record and the PVStructure. Although properties alarm, timeStamp, control, and display can be requested they can not be modified because V3 does not allow them to be changed by a client. If a V3 dbPut to a field has side effects than a put results in a call to the V3 dbPut. Examples are a put to the SCAN field. It actually changes the scanning mechanism for the record. If a V3 dbPut does not have side effects other than causing a record to process a put does NOT result in a call to the V3 dbPut. Instead processing is determined by the pvAccess client. If the client requests processing the normal pvAccess semantics are used, i. e. the record is processed via a dbPutNotify to the PROC field after the data is written into the V3 record.
This is not implemented since it does not make sense for V3 records.
This is not implemented since it does not make sense for V3 records.
This is implemented via a monitior request call to CAV3. See V3CaMonitor below for details.
This creates a V3ChannelArray that implements the normal ChannelArray semantics. It uses DBADDR and the RSET to direcly access the array in the V3 record.
This just prints a short message.


This does most of the work related to transfering data between a PVStructure and a V3 record. All methods are static and are called by V3ChannelGet, V3ChannelPut, etc. Each first calls getProperty which returns a mask that is passed to the other methods. The mask bits show a combination of what the requester requested and characteristics of the V3 field being accessed. The mask has the following bits:

The client has requested that the record be processed as part of a ChannelGet::get or a ChannelPut::put.
The client has specified that array data be shared, e. g. has set record[shareData=true], In this case no copy of the array data is created. Instead data is transfered directly between network buffers and the data array in the V3 record. If this is specified the client must understand that if an array is split between network buffers the array may change between buffers.
The requester has requsted the timeStamp.
The requester has requested alarm info.
The requseter has requested display info.
The requester has requested control info.
The V3 field is a scalar.
The V3 field is an array.
The V3 field is a DBF_ENUM, DBF_MENU, or DBF_DEVICE.
The V3 field is a no access field. The client will not be allowed any access to the field.
The V3 field is a no mod field. The client will not be allowed to modify the field.
Side effects occur if a dbPut is issued to the field. ChannelPut will issue a dbPutField for such fields instead of directly modifying the field..
Is the field a link field? Link fields require special handling for both get and put.

V3Util is defined as:

class V3Util {
    static int getProperties(Requester &requester,PVStructure &pvRequest,DbAddr &dbAddr);
    static PVStructure *createPVStructure(Requester &requester,int mask,DbAddr &dbAddr);
    static void getPropertyData(Requester &requester,int mask,DbAddr &dbAddr,PVStructure &pvStructure);
    static Status get(Requester &requester,int mask,DbAddr &dbAddr,PVStructure &pvStructure,BitSet &bitSet,CAV3Data *caV3Data);
    static Status put(Requester &requester,int mask,DbAddr &dbAddr,PVField *pvField);
    static Status putField(Requester &requester,int mask,DbAddr &dbAddr,PVField *pvField);
    static ScalarType getScalarType(Requester &requester,DbAddr &dbAddr);


As mentioned that computes the mask discussed above.
This method creates a PVStructure that holds data that is transfered between pvAccess and the V3 record. It uses a combination of the mask computed by gerProperties and the V3 DbAddr to determine the contents of the PVStructure.
This gets display and control info from the V3 record. Note that this is called only once by V3ChannelGet, V3ChannelPut, and V3ChannelMonitor. This only the first get or monitor will cause the property data to be sent to the client.
This gets data from the V3 record and puts it into the PVStructure.
This takes data from the PVStructure and puts it into the V3 record. Only the value field can be changed.
This transfers data from the PVfield to the V3 field via a call to dbPutField.
This gets the scalar type associated for the V3 field being accessed.


This creates special implementations of PVByteArray, ... , PVStringArray that access the corresponding array types in the V3 record. Except for PVStringArray it provides the ability to share data instead of making copies. It gets and puts array lengths via the V3 record support entry table methods. Note that it has speciaized methods for all of get, put, serialize, and deserialize that are not provided by PVDataCreate.

V3Array is defined as:

class V3ValueArrayCreate {
    PVByteArray *createByteArray(PVStructure *parent,ScalarArrayConstPtr scalar,DbAddr &dbAddr,bool shareData);
    PVShortArray *createShortArray(PVStructure *parent,ScalarArrayConstPtr scalar,DbAddr &dbAddr,bool shareData);
    PVIntArray *createIntArray(PVStructure *parent,ScalarArrayConstPtr scalar,DbAddr &dbAddr,bool shareData);
    PVFloatArray *createFloatArray(PVStructure *parent,ScalarArrayConstPtr scalar,DbAddr &dbAddr,bool shareData);
    PVDoubleArray *createDoubleArray(PVStructure *parent,ScalarArrayConstPtr scalar,DbAddr &dbAddr,bool shareData);
    // Note that V3StringArray can not share
    PVStringArray *createStringArray(PVStructure *parent,ScalarArrayConstPtr scalar,DbAddr &dbAddr);

extern V3ValueArrayCreate *getV3ValueArrayCreate();


Create a PVByteArray attached to a DBF_CHAR or DBF_UCHAR array field.
Create a PVShortArray attached to a DBF_SHORT or DBF_USHORT array field.
Create a PVIntArray attached to a DBF_LONG or DBF_ULONG array field.
Create a PVFloatArray attached to a DBF_FLOATarray field.
Create a PVDoubleArray attached to a DBF_DOUBLE array field.
Create a PVFloatArray attached to a DBF_STRING array field. Note that it has special semantics to handle the fixed size strings that V3 supports.


In V3 dbEvent is what implements db_post_event, which is what is called when a field value changes in a V3 record. But dbEvent just passes this to V3CA. Thus the only way to trap V3 monitors is via a call to ca_create_subscription. The implementation takes only the following from the CAV3 event callback: scalar value, alarm status and sererity, and the timeStamp. All other data, e.g. array values, are taken from the record as a result of the CAV3 event callback.

V3CAMonitor provides an enum that describes the data type of a V3 field as seen by pvData.

enum V3Type {


A pvData enumerated structure is created for this. The monitor request will be a DBR_TIME_ENUM. For an enum a DBF_ENUM is an index. For pvData it is the index field on an enumerated structure.
The monitor request will be a DBR_TIME_CHAR.
The monitor request will be a DBR_TIME_SHORT.
The monitor request will be a DBR_TIME_LONG.
The monitor request will be a DBR_TIME_FLOAT.
The monitor request will be a DBR_TIME_DOUBLE.
The monitor request will be a DBR_TIME_STRING. Note that for strings data is taken from the record not from CAV3.

Next a C structure is defined in which data from CAV3 is put. Each time CAV3 calls the event callback the CAV3 data is copied to this structure.

struct CAV3Data {
    /* The following have new values after each data event*/
    union { //only used for scalar values.
        int8   byteValue;
        int16  shortValue;
        int32  intValue;
        float  floatValue;
        double doubleValue;
    epicsTimeStamp  timeStamp;
    int             sevr;
    int             stat;
    const char *    status;


This holds a scalar numeric value obtained from CAV3. If the field is an array or a DBF_STRING then the actual data is taken from the record via the DBADDR and RSET.
The epics timestamp obtained from CAV3.
The severity obtained from CAV3.
The status obtained from CAV3.
The string value associated with stat. It is taken from epicsAlarmConditionStrings, which is an extrernal that is part of dbAccess.

Next is an interface that provides c++ callbacks that can be called by the C callbacks that CAV3 calls:

class CAV3MonitorRequester : public virtual epics::pvData::Requester {
    virtual ~CAV3MonitorRequester(){}
    virtual void exceptionCallback(long status,long op) = 0;
    virtual void connectionCallback() = 0;
    virtual void accessRightsCallback() = 0;
    virtual void eventCallback(long status) = 0;


Called when CAV3 calls the exception callback.
Called when CAV3 calls the connection callback.
Called when CAV3 calls the access rights callback,
Called when CAV3 calls the event callback. The data in CAV3Data is updated before this is called,

Finally the interface for CAV3Monitor:

class CAV3Monitor : private epics::pvData::NoDefaultMethods {
    CAV3Monitor(CAV3MonitorRequester &requester,String pvName,V3Type v3Type);
    CAV3Data & getData();
    void connect();
    void start();
    void stop();
    const char * getStatusString(long status);
    bool hasReadAccess();
    bool hasWriteAccess();
    bool isConnected();
    class CAV3MonitorPvt *pImpl;


Ask to connect. This calls ca_create_channel and ca_replace_access_rights_event.
This calls ca_create_subscription.
This calls ca_clear_subscription.
This returns ca_message(status),
This calls ca_read_access. This will always be true because CAV3 treats this a local rather than remote client.
This calls ca_write_access. This will always be true because CAV3 treats this a local rather than remote client.
This calls ca_state to determine connection state. If connected this should always return true.


CAV3Monitor calls CAV3 in order to monitor changes to the V3 records. In order to do this it must call ca_context_create(ca_enable_preemptive_callback). It must call this from the thread that wil make calls to CAV3. But it must also call ca_attach_context from any other threads associated with the thread that calls ca_context_create. The putpose of V3CAContext is to manage this interaction with CAV3.

For each thread that V3Channel uses and that makes a CreateMonitor V3CAContext keeps a reference count. This is decremented when CAV3Contect::release is called, which is done when a V3CAMonitor is deleted. Only when the thread that originally resulted in a V3CAContext exits is the V3CAContext deleted. Thus if a single client connects to several V3 records each instance of V3Channel and all ChannelGets, etc. for that channel share the same context.:q

V4CAContext is defined as:

class CAV3Context {
    void release();
    void stop();
    void exception(String message);
    void checkContext();


This does nothing.
This is called by CAV3Monitor when a monitor is being destroyed.
This is called when the thread that created the CAV3Context exits. That is when ca_context_destroy is called.
This is called when CAV3 calls the exception callback that V3CAContext registers.
This is called before any call to CAV3. It checks to see if the calling thread is aleady known to CAV3. If not ca_attach_contex is called.

There is a separate class for creating a context:

class CAV3ContextCreate {
    static CAV3Context &get(Requester &requester);


This checks to see if the thread has already known to CAV3, i. e. if a CAV3Context altready exists for the calling thread. If so it is returned. If not a new CAV3Context is created and returned.