|
[Next] [Previous] [Top] [Contents] [Index] Channel Access Client Tutorial 2. Reading PV ValuesA Simple Program
ezee that reads the value of a PV and prints its value to standard output. When the program is run, the user should pass the name of a valid PV to the program from the command line as in
%ezee myPVBasically, the program establishes a connection to the PV, reads its value, prints its value, and then exits. Although simple, it should demonstrate some basic features of Channel Access. Here's the code:
/* 1. Include appropriate files. */
#include <stdio.h>
#include <cadef.h> /* EPICS specific */
#define USAGE "usage: ezee <PV name>"
int main(int argc, char **argv)
{
chid chan;
dbr_string string;
int status;
if(argc != 2){
printf("%s\n", USAGE);
exit(-1);
}
/* 2. Initialize Channel Access. */
status = ca_task_initialize();
SEVCHK(status, "Unable to initialize");
if (status != ECA_NORMAL)
exit(-1);
/* 3. Make search request. */
status = ca_search(argv[1], &chan);
if (status != ECA_NORMAL){
printf("%s: problem establishing connection to %s.", camessage(status), argv[1]);
exit(-1);
}
/* 4. Send the request and wait for channel to be found. */
status = ca_pend_io(1.0);
if (status != ECA_NORMAL){
printf("%s: Not Found\n", argv[1]);
exit(-1);
}
/* 5. Make get request. */
status = ca_bget(chan, string);
SEVCHK(status, "Error in call to ca_bget()" );
status = ca_pend_io(1.0);
if (status != ECA_NORMAL){
printf("%s: Get Timed Out\n", argv[1]);
exit(-1);
}
printf("%s\n", string);
/* 6. Free resources and close channel access. */
ca_task_exit();
return(0);
}
Any client application uses one or more functions from the client library. Most client library calls follow the format ca_xxx() as in ca_task_exit(). An exception is SEVCHK(), which is really just a macro-envelope around another routine called ca_signal(). Like SEVCHK(), many client calls are macros or aliases of other calls. For instance, the call in this program that is used to read a PV's value is called ca_bget(), which is really an alias of ca_array_get().There are six parts to our program, each numbered in the comment that precedes it. Each of these parts is discussed below along with some basic Channel Access concepts. 1. Include Files and Program CompilationAll programs that are going to use one or more functions from the Channel Access client library must include thecadef.h header file which can be found in the $EPICS/base/include directory. The header file cadef.h causes db_access.h, caerr.h, and caeventmask.h to be included also. These EPICS header files are necessary in order to use the client library.
/* 1. Include appropriate files. */ #include <stdio.h> #include <cadef.h> /* EPICS specific */To compile a client program, it must be linked with the libraries libca.a and libCom.a. These libraries are in $EPICS/base/lib/$ARCH. Assuming our C compiler was cc, we could compile ezee with the following command:
cc ezee.c -o ezee -L$EPICS/base/lib/$ARCH -lca -lCom -I$EPICS/base/includewhere $EPICS is a variable that indicates the top level of the EPICS build on the system and $ARCH is a variable representing the name of the target architecture, sun4, for instance.2. Initializing Channel AccessEach client application should callca_task_initialize() before calling any other functions in the client library. Calling ca_task_initialize() initializes the Channel Access library for a process, or a group of tasks in a multi-threaded OS. If a program or task group does not call this function, it is automatically invoked at the first call to any routine from the client library and is thus optional, but still considered good practice.
Our program calls
/* 2. Initialize Channel Access. */ status = ca_task_initialize(); SEVCHK(status, "Unable to initialize"); if (status != ECA_NORMAL) exit(-1);Notice that the variable status is used to hold the value returned from ca_task_initialize(). Nearly all client library routines return an integer that represents one of a number of possible status codes. For example, ca_task_initialize() returns one of two possible status codes, ECA_NORMAL and ECA_ALLOCMEM. If ca_task_initialize() returns ECA_NORMAL, then Channel Access client library was successfully initialized. If it returns the other status code, ECA_ALLOCMEM, then Channel Access wasn't successfully initialized due to lack of available memory.
All calls that return a status code will return ECA_NORMAL to indicate success. To indicate an error, each returns one of a number of possible status codes. The possible error codes for each call may differ. For example, the possible error indicators for
Normal successful completion.The header file caerr.h lists all status codes and their associated messages. In addition, the routine ca_message(), when passed a status code, will return a string containing the message associated with the code.All status codes have an associated severity level, which can be one of five: SUCCESS, INFO, WARNING, ERROR, and FATAL. ECA_NORMAL is the only code which has SUCCESS as its severity. All other codes have one of the other severities, where INFO is the least severe and FATAL is the most.
A convenient way to check error status codes returned by client calls is the
SEVCHK(status, "Unable to initialize");If the status code is other than ECA_NORMAL, SEVCHK() will print the status code, its associated message, and the string passed as the second argument, and if the severity of the error is ERROR or FATAL, SEVCHK() will abort the program. Since ECA_ALLOCMEM has the associated severity WARNING, SEVCHK() will merely print a warning if ca_task_initialize() returns ECA_ALLOCMEM. The program exits if the returned status code is anything other than ECA_NORMAL.
For convenience, Appendix A presents a table of status codes, their severity level, and their associated messages. This information can also be gleaned from the 3. Establishing a Connection to a PVAfter Channel Access is initialized, our program calls the routineca_search() to establish a connection to the PV that was specified on the command line. A string identifier containing the name of the PV should be passed as the first argument to ca_search() and the address of a channel ID should be passed as the second argument. The channel ID should be declared to be of type chid. Our ID is called chan and is declared at the beginning of main():
chid chan;A chid is a defined type for pointer to a structure. Channel Access uses the structure to store information about the channel, such as the network address, the PV's name, etc. An application should never have to access the contents of a chid directly. if ca_search() finds a PV on the network with the specified name, it will write the necessary information into the chid structure and the program can then use the chid in all subsequent operations on that PV.
Our program checks the status returned by
/* 3. Make search request. */
status = ca_search(argv[1], &chan);
if(status != ECA_NORMAL){
printf("%s: problem with search request for %s.", camessage(status), argv[1]);
exit(-1);
}
You might assume that if ca_search() returns ECA_NORMAL, it means the PV was found and the information about the channel connection has been written into chan. In most cases, this assumption is not correct! Operations such as ca_search() or ca_get() are requests, since they are requesting an action from a server. For example, when a client application calls ca_get(), it is requesting that the server get the value of a PV and send it back to the client. When a client application issues a request, the client library checks to see if the request is valid. If the request is valid, the routine returns ECA_NORMAL. If the request is invalid, the routine returns the appropriate error code. An instance of an invalid request would be a ca_get() request that is passed a chid for a PV which the client program has not yet connected to. Since the client library cannot identify the channel connection, it returns ECA_BADCHID.
For most client calls, when the request is valid, the client library places the request in a request buffer. The request buffer is flushed and all the requests in it are sent over the network to the servers when the buffer is full or when a client program specifically flushes the buffer using any one of several routines. However, this is not the case with
It should be noted that if the PV is located on the same host, the status code returned by the request indicates not only if the request was valid or not, but also if the request was successfully performed. For example, suppose a client application is located on the same host as an EPICS database that has a record called calc1. If a client makes a request to read the record's value using
In the example program if the call to 4. Checking Success/Failure of RequestsThe question then arises: how does a client application check the success/failure of a request such asca_get()or ca_search(). First, it's important to note that not all client calls request the services of a Channel Access server; therefore they don't have to check for success/failure the way requests do. For example, when a client calls ca_message(), the client library simply returns the appropriate character string, so a Channel Access server is not involved. When a client application requests the services of a server, it should check to see if the server successfully performed the operation.
Our program checks the success/failure of
For each query, Channel Access increments a counter. The call to
Thus, in our program, we pass a value of 1.0 or one second to
/* 4. Send the request and wait for channel to be found. */
status = ca_pend_io(1.0);
if(status != ECA_NORMAL){
printf("%s: Not Found\n", argv[1]);
exit(-1);
}
Since only one query has been generated--by the call to ca_search()-- ca_pend_io() will wait until a server sends back a response to the client indicating that it has found the PV or until one second has elapsed. If ca_pend_io() returns ECA_TIMEOUT, our program will assume that the PV cannot be found and will exit after printing a message.
If ECA_NORMAL is returned, then the PV was found and a connection was established; Another way of checking the success/failure of remote operations involves event handler functions. By supplying a function address and specifying it as an event handler for an operation, the function will be called when the operation completes. Event handlers and how they can be used to indicate success/failure will be explained later in this chapter. 5. Reading a PV's ValueAfter a channel is established to the PV, our program then reads and prints its value. It does this by callingca_bget(), which is an alias of ca_array_get(). Our program checks the validity of the request by calling SEVCHK(). Then we flush the request by calling ca_pend_io() with a value of one second. The ca_pend_io() call will wait either until its query is answered and the PV's value is returned to the client by the server, or until one second has elapsed. If ca_pend_io() returns ECA_NORMAL, it means that the PV's value was successfully read. Then the value is printed. If ca_pend_io() times out and returns ECA_TIMEOUT, our program assumes the value was not successfully read.
/* 5. Make get request. */
status = ca_bget(chan, string);
SEVCHK(status, "Error in call to ca_bget()" );
status = ca_pend_io(1.0);
if (status != ECA_NORMAL){
printf("%s: Get Timed Out\n", argv[1]);
exit(-1);
}
printf("%s\n", string);
ca_bget() was designed to retrieve the string associated with the current state of an EPICS binary record such as a multiple-bit binary record. In EPICS binary records, each discrete state has (or should have) a corresponding string. When ca_bget() is called for a binary PV, the server will attempt to fulfill the request by returning the string corresponding to the current state of the binary PV. The prototype for ca_bget() is
int ca_bget(chid CHID, char *PVALUE);which shows that it accepts two arguments: a valid channel ID structure and a string pointer. If and when the query generated by ca_bget() is answered, the value string is written into the location specified by the pointer. Though designed for EPICS binary records, ca_bget() can be used to retrieve the value of any PV as a string. If the native type of the PV is a floating point number, for instance, the number will be converted to its string representation.
The
6. Closing Channel AccessNext, our program callsca_task_exit() which frees any resources allocated by the client library for the client application. Calling ca_task_exit() is optional since whenever a client program exits, the allocated resources are freed in the same way. For our program, calling ca_task_exit() is a bit superfluous since our program is about to exit anyway. Nonetheless, it's still good practice. Calling ca_task_exit() is most useful for programs that want to free any resources allocated by the library on behalf of the process or group of tasks.
The next section discusses data types, data conversions, the Callback Functions
ca_array_get(), ca_array_get_callback(), and ca_sg_get()--each of which has at least one and up to three aliases. Calls whose name follows the format ca_sg_nnn() are known as synchronous group functions and synchronously perform requests placed in a certain group.The following table lists the routines available for retrieving PV values, writing to PVs, and monitoring PVs, and categorizes them in three groups: callback functions, non-call back functions, and synchronous-group functions.
Monitor functions such as
Let's look at another simple program called
Here is a listing of the main part of the program including the preprocessor directives and the prototype for the event handler
#include <stdio.h>
#include <alarmString.h> /* EPICS header files */
#include <cadef.h>
#define MAX 30
/* Prototype for event handler */
void my_own_handler(struct event_handler_args args);
/* Flag used to communicate between main() and my_own_handler() */
int PEND_FLAG
int main()
{
int status;
char chName[MAX];
unsigned nelem;
chid chan;
chtype type;
/* Initialize Channel Access. */
status = ca_task_initialize();
SEVCHK(status, "Unable to initialize");
if (status != ECA_NORMAL)
exit(-1);
for (printf("Enter channel name or \"bye\" to quit.\n");
gets(chName) != NULL && strcmp(chName, "bye") != 0;
printf("Enter channel name or \"bye\" to quit.\n")){
status = ca_search(chName, &chan);
SEVCHK(status, "Bad channel name? Invalid chid.");
status = ca_pend_io(1.0);
if (status != ECA_NORMAL)
printf("%s: not found.\n", chName);
else {
/* Get PV's native data type. */
type = ca_field_type(chan);
/* Get number of elements of PV */
nelem = ca_element_count(chan);
/* Make callback request */
status = ca_array_get_callback(dbf_type_to_DBR_STS(type),
nelem, /* number of elements of database field */
chan, /* channel identifier or chid */
my_own_handler, /* name of callback event handler */
NULL);
SEVCHK(status, ca_message(status));
/* Flush request and block for 0.1 seconds waiting for event */
PEND_FLAG = 1;
while (PEND_FLAG)
status = ca_pend_event(0.1);
if (status != ECA_TIMEOUT && status != ECA_NORMAL)
printf("%s\n", ca_message(status));
}
SEVCHK(ca_clear_channel(chan), NULL);
}
return(0);
}
Basically, after including the appropriate header files and making the proper declarations, our program first initializes the Channel Access library (lines 13-16) and then enters a for loop (lines 17-39), which begins by prompting the user for the name of a PV and places it in chName. If the user enters "bye", the for loop exits and the program returns. Otherwise, the program tries to establish a connection to the PV specified by the user (line 20-39). If ca_pend_io() returns ECA_TIMEOUT or any other error code, the program assumes that a connection could not be established and skips the else statement in which the request to read the PV's value is made.
If a channel for the PV is established, the program then enters the else statement, in which it calls 1. Reading Values Using ca_array_get_callback()Next, the request is made to read the PV's value (line 28-32). Here it calls the callback functionca_array_get_callback() to do this. The ca_array_get_callback() function accepts five arguments:
ca_field_type(), on the other hand, returns the native type of a field: DBF_CHAR, DBF_ENUM, DBF_STRING, etc. The difference between these two sets is that the database request or DBR type indicates the data type requested by the Channel Access client while the native or DBF type indicates the data type for the PV used by the server.
For each native type there is a request type. For instance, DBF_CHAR corresponds to DBR_CHAR. In addition, the database request or DBR types include compound data types, which are structures which can hold information associated with a PV as well as the PV's value. For instance, the DBR_STS_FLOAT type is a structure which can hold the value of a requested PV in a floating point field along with the status and severity of that PV's record. A client could request a compound data type for the VAL field of a Calculation record, in which case the server would write the value of VAL and the current value of the STAT (status) and SEVR (severity) fields into the structure, which the program could then access. Any of the client calls that read or monitor PVs like the
For the first argument, our program uses the macro
The next argument is simply
After our program issues the get request, it checks the validity of the 2. Allowing an Event Handler to ExecuteIf the request is valid, the callback request is placed in the request buffer. Our program then callsca_pend_event() with an argument of 0.1 seconds (37-39).
While
For clients that are running in single-threaded environments like UNIX, when you use a callback request such as
Also because 3. Event HandlersAn event handlers refers to a function that is called when a certain event occurs or after an event completes. For instance, there are connection handlers, which are called when a channel's current connection state changes, i.e., when a previously connected channel is disconnected or when a disconnected channel is connected for the first time or reconnected. The connection handler is specified by a call toca_search_and_connect(). Basically, there are five types of events:
ca_array_get_callback() inside the for loop to read the PV's value. Since ca_array_get_callback() requires an event handler that the client program defines, our program supplies the handler my_own_handler(). It is called whenever the server has completed the request. Here, completed does not mean that the operation was performed successfully, i.e., that the server retrieved the value, but only that the server tried to complete the operation. Thus, a callback function should check to see if the operation successful before it performs any other task. How a function can do this is explained below.
An event handler is passed a structure specific to that type of event. For example, a connection handler is automatically passed a data structure called
Reviewing the call made to
status = ca_array_get_callback(dbf_type_to_DBR_STS(type), nelem, /* number of elements of database field */ chan, /* channel identifier or chid */ my_own_handler, /* name of callback event handler */ NULL);The event handler accepts one argument, a structure called event_handler_args, whose definition is
struct event_handler_args{
void *usr; /* User argument supplied when event added */
struct channel_in_use chid; /* Channel id */
long type; /* the type of the value returned */
long count; /* the element count of the item returned */
void *dbr; /* Pointer to the value returned */
int status; /* CA Status of the op from server - CA V4.1 */
};
A brief explanation of each member and how it is generally used follows.
3.1. my_own_handler()As an example, let's examinemy_own_handler(), the event handler specified in the call to ca_array_get_callback(). It's a little complicated because it must be able to print several data types. The last section of this chapter explains the different data types in more detail and how to deal with them.
Here is the prototype
6. void my_own_handler(struct event_handler_args args);And here is its definition. The important parts of code are numbered.
/* callback function: informs user of success or failure of */
/* get operation and prints the channel's value along with */
/* status and severity. */
void my_own_handler(struct event_handler_args args)
{
int i;
/* 1. union db_access_val defined in db_access.h */
union db_access_val *pBuf;
/* 2. If operation succeeded, print status,
severity, and channel value(s). */
if (args.status == ECA_NORMAL){
/* 3. Cast pointer to appropriate type
or member of db_access_val */
pBuf = (union db_access_val *)args.dbr;
printf("channel %s: ", ca_name(args.chid));
switch (args.type){
case DBR_STS_STRING:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->sstrval.status],
alarmSeverityString[pBuf->sstrval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%s\t", *(&(pBuf->sstrval.value) + i));
if ((i+1)%6 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_SHORT:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->sshrtval.status],
alarmSeverityString[pBuf->sshrtval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%-10d", *(&(pBuf->sshrtval.value) + i));
if ((i+1)%8 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_FLOAT:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->sfltval.status],
alarmStatusString[pBuf->sfltval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("-10.4f", *(&(pBuf->sfltval.value) + i));
if ((i+1)%8 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_ENUM:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->senmval.status],
alarmSeverityString[pBuf->senmval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%d ", *(&(pBuf->senmval.value) + i));
}
printf("\n");
break;
case DBR_STS_CHAR:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->schrval.status],
alarmSeverityString[pBuf->schrval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%-5", *(&(pBuf->schrval.value) + i));
if ((i+1)%15 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_LONG:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->slngval.status],
alarmSeverityString[pBuf->slngval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%-15d", *(&(pBuf->slngval.value) + i));
if((i+1)%5 == 0) printf("\n");
}
printf("\n");
break;
case DBR_STS_DOUBLE:
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->sdblval.status],
alarmSeverityString[pBuf->sdblval.severity]);
printf("current %s:\n", args.count > 1?"values":"value");
for (i = 0; i < args.count; i++){
printf("%-15.4f", *(&(pBuf->sdblval.value) + i));
}
printf("\n");
break;
default:
printf("data type was not dbr_sts_nnn.\n");
break;
}
}
/* if get operation failed, print channel name and message */
else
printf("channel %s: get operation failed\n", ca_name(args.chid));
return;
}
Our handler's purpose is simply to check if the get operation succeeded or failed, and if it succeeded, the handler prints the PV's name and value(s). To check the status of the operation, it simply accesses the status member of the event_handler_args structure which holds this information (2):
if(args.status == ECA_NORMAL) print values else print messageBy far, most of the code is for dealing with the different possible data types. It will print the status and severity and value of most PV types. Since this event handler provides for all the different basic data types (ENUM, STRING, etc.), it is long.
The
pBuf = (union db_access_val *)args.dbr;The event handler then uses a switch statement for each possible DBR_STS type, These types are compound types, each of which is a structure that contains the PV's value and the status and severity of the PV's record. There are nine DBR_STS types, eight correspond to the basic types DBR_ENUM, DBR_FLOAT, DBR_CHAR, etc., and one type which differs, having two values for the ACKT and ACKS fields in the PV's record. Our handler doesn't provide for the possibility of this type, only the basic types. Thus, the purpose of the switch statement is to print all the possible types that can be returned by the server.
Note that the status and severity members in each type of DBR_STS structure are printed by accessing
printf("alarm status = %s, alarm severity = %s\n",
alarmStatusString[pBuf->sdblval.status],
alarmSeverityString[pBuf->sdblval.severity]);
The status and severity members of the DBR_STS structure are two short integers which represent the alarm status and severity of the PV. Since they are just integers, they are not very informative. However, in the header file alarmString.h are two arrays of strings which hold the strings that describe the status and severity codes. By indexing alarmStatusString with the status member of the DBR_STS structure, we can print the string that corresponds to the PV's alarm condition: NO_ALARM, HIGH, HIHI, etc., and likewise by indexing the alarmSeverityString array with the severity member, we can print the severity of the alarm, NO_ALARM, MINOR, MAJOR, or INVALID.4. Data TypesThe Channel Access client software uses database request types which have theDBR prefix. The EPICS database software uses database field types, which have the prefix DBF. By using the request types, the client can exchange data with the server though the client is running on a different host.User programs can specify any DBR type and the server will convert from the native type to the DBR type. For instance, if the native type is DBF_FLOAT and the client program issues the following request,
ca_get_callback(DBR_INT, chid, my_handler, NULL);the server will return the data as an integer after converting it from a floating-point number. Of course, a floating-point to an integer is an easy conversion to predict; however, not all conversions are predictable.
Although the server can perform all conversions, it is recommended that large client programs that make a lot of requests perform some of the conversions themselves. For instance, although our example program was a small one, it performs most of the conversion work itself. For instance, in the original call to
type = ca_field_type(chan); /* Get number of elements of PV */ nelem = ca_element_count(chan); /* Make callback request */ status = ca_array_get_callback(dbf_type_to_DBR_STS(type), nelem, /* number of elements of database field */ chan, /* channel identifier or chid */ my_own_handler, /* name of callback event handler */ NULL);which obtains the native type of the PV using the ca_field_type() macro and then uses the dbf_type_to_DBR_STS() macro to request the DBR_STS type corresponding to the field's native type. So, for example, if the field's native type is DBF_FLOAT, the request passed to the call will be DBR_STS_FLOAT. We could have simply specified DBR_STS_STRING as the first argument. The server would then convert the floating-point field or whatever type of field to a string which could be easily printed out. As it is, our event handler must use a switch statement with seven cases to print the value because it doesn't ask the server to convert the value from the PV's native type.All variables in the client program that are going to hold variables returned by the Channel Access server, should be defined as the proper DBR type, though just using the corresponding C type will most often work, though not recommended. For instance, if a call specifies that the value be returned as a string, DBR_STRING, the pointer variable could be simply declared as,
char string[20];which will work, but the recommended declaration should be
dbr_string_t string;which makes matters simpler. Thus, in our first program the declaration which holds the state-string returned by ca_bget() is
dbr_string_t string;All the current database request types are listed below, along with the type to declare them, though a glance at db_access.h could also answer many questions:
Of course most of these are compound types like DBR_GR_STRING or DBR_CTRL_INT. The different compound types have different purposes and return different information, though all return the PV's value:
struct dbr_sts_enum{
dbr_short_t status;
dbr_short_t severity
dbr_enum_t value;};
As in this structure, the PV's value is in the value member. Note that this structure does not contain the enumerated strings for that PV. However, the DBR_GR_ENUM and DBR_CTRL_ENUM types do, so if you want to access all the possible state strings for a binary or multi-bit binary PV, you should use the DBR_GR or DBR_CTRL types.
There are many different macros defined both in cadef.h and db_access.h that should help a programmer to deal with data types. We have used two:
chtype type;Some of them can be used to check a PV's type, such as dbf_type_is_valid() which returns a non-zero value if the type passed to it is valid and a zero if it is not. Another example would be dbr_type_is_TIME() which returns non-zero if the type is a TIME compound type like DBR_TIME_FLOAT or zero if the type is not a DBR_TIME compound type. There are similar macros for all other simple and compound types, such as dbr_type_is_LONG().
There are macros to convert simple types to compound types. Our program used
Other macros in db_access.h can help with memory allocation of the different types. For instance, the macro Once again, a glance at db_access.h will tell the programmer of the available macros and how to use them. They greatly reduce the amount of work when dealing with types in a Channel Access client application.
|
|
L O S A L A M O S N A T I O N A L
L A B O R A T O R Y Copyright © 1996 UC Disclaimer
|