![]() |
![]() |
![]() |
![]() |
9 Object Manager APIs
This chapter discusses the Object Manager APIs, a set of language-specific APIs that allow C, C++, and FORTRAN code to communicate with the Object Manager. It includes the following sections:
- Introduction
- The C and C++ APIs: Overview
- C API Usage Notes
- C++ API Usage Notes
- The FORTRAN API: Overview
- FORTRAN API Usage Notes
When you include a method in a module, you must also provide code that implements the method. This code frequently must communicate with the Object Manager to obtain or supply information, hence AVS/Express provides a set of language-specific Object Manager APIs. Object Manager APIs are available in C, C++, and FORTRAN, to support method implementations as C functions, C++ classes, and FORTRAN subroutines. Each API contains a set of routines that accomplish various tasks required by your code.
The C API is a special API: it serves not only as the interface between your C functions and the the Object Manager, but as the primary interface between the most AVS/Express objects and the Object Manager. As a result, communications between C functions and the Object Manager are handled differently than communications between C++ classes or FORTRAN subroutines and the Object Manager.
- When you implement a method using a C function, you include calls to C API routines, which communicates directly with the Object Manager.
- When you implement a method using a C++ class, you include calls to C++ API routines. These routines, which are wrappers, map to the appropriate C API routines, which then communicate with the Object Manager.
- Similarly, when you implement a method using a FORTRAN subroutine, you include calls to FORTRAN API routines. Most routines are also wrappers that map to the appropriate C API routines, which then communicate with the Object Manager. (Note that the FORTRAN API does not include wrappers for all C API routines).
The following figure illustrates this structure.
The following sections describe the Object Manager APIs. Because the C and C++ APIs have much in common, they are discussed together, with separate subsections discussing specific topics as appropriate. The FORTRAN API is discussed separately.
The C and C++ APIs include five types of routines, identifiable by their prefixes as noted in the following table:
The AVS/Express online help system provides reference descriptions of the C and C++ API routines. Use these descriptions as your primary source of information (see the next section for notes on how to use the descriptions). To supplement these descriptions, this section provides a summary of the available routines and information on header files, and subsequent sections provide usage notes for the C and C++ API routines.
For conciseness, the online help system does not provide separate descriptions of C and C++ API routines; instead, it provides a single description for both routines, listed under the name of the C routine. For example, the description of OMfind_obj applies both to OMfind_obj (a C API routine) and find_obj (a C++ API routine).
Also for conciseness, the AVS/Express online help system provides a single description for related routines rather than a separate description for each routine. For example, the description of OMget_data_type also applies to OMset_data_type. Specifically:
- The get, set, ret, and del routines for the same data type are generally grouped together; for example, OMget_int_val and OMset_int_val.
- Routines that take a parent object and a subobject's name are grouped with the routine that operates directly on the object itself; for example, OMget_name_int_val with OMget_int_val.
- C++ API routines that operate on an object are grouped with the C API routines of the same name; for example, set_int_val and OMset_int_val.
The organization of the summary of routines, later in this section, reflects these groupings.
With one exception, the parameters, return code, and functionality for a C++ API routine are the same as those for the C API routine with which it is documented. The exception is that the first argument to the C API routine is replaced by the object on which the C++ API routine is invoked. For example, consider the following C API call:
This is the C++ API equivalent:
Some C++ methods have defaulted arguments. If you do not supply a value for these arguments, the default value is supplied. In the online help, defaulted arguments are indicated in the declaration of the routine. For example:
In this definition, you can call this routine as follows:
In this case, AVS/Express supplies NULL values for the size and type arguments.
This section summarizes the C and C++ API routines. As in the online help system, it lists only the C API routines; to obtain the name of the C++ routine that corresponds to a C API routine, simply remove the OM prefix from the C API routine name.
The tables in this section group the routines by functional category, to help you find the routine you need to accomplish a particular task. Within functional groupings, the routines are grouped to reflect the organization of the C/C++ descriptions in the online help system. (For details, see Using the Online Help System Descriptions on page 9-4.)
- Note: When two or more API routines appear together, see the description of the first routine listed for details on all routines in the group.
When you implement a method with code that calls C API routines to communicate with the Object Manager, you must include various header files that define items that the Object Manager uses. You may need to include some or all of the following header files:
- Object Manager header files. If your code accesses the Object Manager via the C API, you typically include the basic C Object Manager header file, as follows:
- However, if it accesses the Object Manager via the C++ API, you include the basic C++ Object Manager header file instead:
- If your code uses the definition of any attributes or properties, you also include the attributes header file:
- If it manipulates any base types, you also include the type header file:
- Event-handling header files. If you use any C or C++ API event-handling routines (identified by a prefix of EV), include the event header file, as follows:
- Other header files. Depending on the AVS/Express kits that your code accesses, you may need to include additional header files specific to those kits. For details, see the relevant kit documentation.
The C API is the most flexible and most comprehensive interface for manipulating AVS/Express objects. The Network Editor and the V language interface are implemented using the C API to access Object Manager functionality. If you can perform an operation with the Network Editor or with V, you can perform it using the C API.
The C API contains C-specific routines implemented in the Object Manager (OM) library, as well as an assortment of other routines (for example, event-handling routines). This section defines some basic concepts used by the OM library and provides a high-level description of basic operations using the routines in this library.
For detailed descriptions of the C API routines, both OM and other, see the AVS/Express online help system.
The Object Manager provides special data structures for three special types of data that it references:
AVS/Express assigns a unique ID to each object. In a C function, the type of an object ID is OMobj_id, which is defined in the header file <avs/om.h> as the following structure:
typedef struct _OMobj_id {
OMobj_id_type obj_id; /* Pointer within a process */
OMproc_id proc_id; /* Process ID */
} OMobj_id;
Each value in this structure is a 32-bit value.
- The first is points to the object within its process.
- The second contains the ID of the process in which the object is located.
Most OM library routines operate on the OMobj_id data structure. This structure is network transparent; that is, it can be passed from process to process, and machine to machine without change. It is generally passed around by value like an ordinary pointer.
When you initialize OMobj_id, you generally can ignore the fact that it is a structure. Note, however, that on some compilers, you cannot initialize a OMobj_id in its declaration. For example, the following initialization returns an error on some compilers:
To be safe, initialize OMobj_id as follows:
Using the appropriate C API routines, you can obtain the object IDs of AVS/Express objects. For example, the following V code defines the group object grp1 and two subobjects named a and b:
A C function can obtain the object IDs of these objects as follows (assume that grp1's parent ID is parent_id):
OMobj_id parent_id, grp1_id, a_id, b_id;
...
grp1_id = OMfind_subobj(parent_id, OMstr_to_name("grp1"),OM_OBJ_RW);
a_id = OMfind_subobj(grp1_id, OMstr_to_name("a"), OM_OBJ_RW);
b_id = OMfind_subobj(grp1_id, OMstr_to_name("b"), OM_OBJ_RW);
Because an OMobj_id is a structure, you cannot compare two OMobj_ids using the `==' operator in C. To compare two object IDs for equality, you must use the C API routine OMequal_objs, as follows:
AVS/Express provides the following set of predefined variables of type OMobj_id, for use in your application:
The Object Manager defines these variables in the file <avs/om.h>; they are initialized at system start-up.
Some routines return the OMnull_obj value when they encounter an error condition. For information on how to test for this condition, see Object ID Return Values on page 9-20.
Each object in an AVS/Express application has a name. One representation of this name is a name string: a character string that is unique among its sibling objects in the object hierarchy. The Object Manager does not store and manipulate this name string directly; instead, it allocates a unique integer value, stored in a C function as an OMobj_name type, that maps directly to the name string. This approach has two primary benefits:
- It prevents the duplication of name strings in memory.
- It allows you to compare two name strings using a single integer comparison.
Note that if your code calls a C API routine for which the object-name argument is optional, you can specify the constant OM_NULL_NAME to indicate no object name.
Using the OMget_object_name routine, you can obtain the integer-value name of an AVS/Express object. For example:
/* Get the name of the object whose ID is func_id. */
OMobj_id func_id;
OMobj_name func_name;
int status;
...
status = OMget_obj_name (func_id, &func_name);
The C API also includes routines that you can use to convert between an integer-value object name and the corresponding name string.
- The routine OMstr_to_name converts a name string to an integer-value object name.
- The routine OMname_to_str converts an integer-value object name to a name string.
OMobj_name object_name; // An integer.
char object_string[30];
...
object_name = OMstr_to_name("My string");
strcpy(object_string, OMname_to_str(object_name));
You should treat the name string returned from OMname_to_str as read-only storage. Do not free this string or reuse the memory to which it points.
To compare two OMobj_name objects for equality, use the C equality operator (==).
Each object in an AVS/Express application has a base type. AVS/Express assigns a unique ID to each base type. In a C function, the type of each base type ID is OMtype_id. You need to use the OMtype_id type only if you are creating objects explicitly in a C function, which not a typical use of the Object Manager.
With few exceptions, OM routines return a status whose value is either an integer or an object ID (an OMobj_id value). If a routine does not return one of these values, the description of the routine in the online help system explicitly states the nature of its return value.
Most API routines return an integer status code. It can have the following values:
Some API routines generate error messages that contain diagnostic information when they fail with a status of OM_STAT_ERROR. These messages are printed to stderr. You can suppress them by "squashing" them for the duration of your code. (Typically, you squash errors only for one API routine at a time.)
To squash the error messages generated by an API routine, call the macro ERRsquash_start before the routine and ERRsquash_end after it. For example:
ERRsquash_start(); // begin suppressing errors
stat = OMget_int_val(obj_id, &val);
ERRsquash_end(); // end suppressing errors
Several API routines return an object ID; for example, OMfind_subobj. If a routine of this type fails, it returns the value OMnull_obj for the object ID. You can test for this value with the routine OMis_null_obj. For example, the following code attempts to get the object ID of subobject x but prints a message if the attempt fails:
OMobj_id parent_id, x_id;
...
x_id = OMfind_subobj(parent_id, OMstr_to_name("x"), OM_OBJ_RW);
if (OMis_null_obj(x_id))
printf("Error searching for x\n");
Many OM routines take a mode argument as their last argument. The mode argument alters the behavior of the routine: it specifies a list of flags that are combined using the logical OR operation.
For a list of mode argument values for a specific OM routine, see its description in the online help system.
To use a routine's default behavior (that is, no optional modes), specify a mode value of 0. Note that a mnemonic constant that corresponds to this mode value may not exist.
One of the main operations performed in AVS/Express is traversing the AVS/Express object hierarchy to look for a specific object. Using a single set of traversal routines, you can find objects in a library, parameters in a module, modules in a macro, and so forth.
AVS/Express provides three IDs that allow you to access the base points of the object hierarchy. These IDs are stored in the following global variables:
To move up the object hierarchy , use the OM routine OMret_obj_parent, as follows:
This routine returns the ID of the parent object of the obj_id argument. If this object has no defined parent, it returns OMnull_obj. An object may have no parent for the following reasons:
- It is a dangling object that has been partially deleted.
- It was improperly created without a valid parent object.
- It is the Root object.
There are two main ways to go down the object hierarchy:
If you know the name of the subobject that you want to access, you can use either OMfind_subobj or OMfind_str_subobj to find it. You use these routines as follows:
OMobj_id
OMfind_subobj(OMobj_id parent_id,
OMobj_name sub_obj_name,
int mode)
OMfind_str_subobj(OMobj_id parent_id,
char *path_name,
int mode)
The mode argument. If you cannot guarantee that changes will not be made to the object whose ID is returned, specify OM_OBJ_RW as the mode argument for both of these calls. If you can make this guarantee, specify OM_OBJ_RD instead. The value OM_OBJ_RD can prevent the creation of subobjects that are inherited from a base template. In this case, the returned object has the base template as a parent, not the object specified with parent_id. When in doubt, use the flag OM_OBJ_RW.
The returned subobject. OMfind_subobj returns the ID of an immediate subobject of the object specified by parent_id with the name specified by sub_obj_name; for example:
OMobj_id obj_id, parent_id;
parent_id = ...
obj_id = OMfind_subobj(parent_id,
OMstr_to_name("sub_name"), OM_OBJ_RW);
By comparison, OMfind_str_subobj can take a pathname to the subobject. The pathname can have multiple levels of path entries and can go up levels in the hierarchy using the <- notation. If the path_name argument has [n] specified, it can also go through array values. The following are examples of valid string values for the path_name argument:
obj_id = OMfind_str_subobj(parent_id, "A.B.C", OM_OBJ_RW);
obj_id = OMfind_str_subobj(parent_id,"B[3].C", OM_OBJ_RW);
obj_id = OMfind_str_subobj(parent_id,"<-.<-.C", OM_OBJ_RW);
If these OM routines do not find the specified subobject or another error occurs, they return OMnull_obj.
Finding through Libraries. The OMfind_subobj and OMfind_str_subobj calls can skip through more than one level in the AVS/Express object hierarchy if one of the immediate subobjects of parent_id is a library with the global attribute set. For example, consider the following V definition:
Now consider the following call to OMfind_str_subobj (recall that the variable OMroot_obj contains the ID of the root object):
This call returns the ID of the UIwindow object. This is because both the Templates library and the UI library are global. To override the searching of global libraries, use the OR operator (|) to incorporate the OM_FIND_NO_LIBRARIES flag into the mode argument of OMfind_subobj or OMfind_str_subjobj. For example:
The Omfind _str_subobj routine now returns OMnull_obj instead of the ID of the UIwindow object.
You can also traverse subobjects as a list.
Determining the number of subobjects. To determine how many subobjects a particular parent has, call OMget_num_subobjs, as follows:
This routine takes the ID of a hierarchical object such as a library, macro, module, or group, and finds the number of immediate subobjects contained in the hierarchical object. It returns the following integer status values: 1 for success, 0 to indicate that the parent_id is a link object that does not have a current value, and -1 to indicate that the specified object does not define any subobjects.
Getting the subobject's ID. To get the ID of an individual subobject, call OMget_array_subobj, as follows:
The subobjects of an object are specified in an ordered list. This routine returns in the child_id field the ID of the nth subobject in the list where n is specified by the which parameter. Set the mode flag just as you would if you were searching for a subobject by name (see Using a Mode Argument on page 9-80).
If you use Omget_array_subobj on an object that is a group, or on a module that has its reference mode set to `&' or is defined as an array of objects, it returns the IDs of the subobjects defined in the template object, not the IDs in any of the values. For example, suppose that you the following V definition of a group object:
If you call OMget_array_subobj with parent_id set to the ref object and an index of 0, it returns the ID of the object ref.a, not value.a. To get the subobjects of the value, you must call OMget_obj_val to get the value and then use the ID it returns in the OMget_array_subobj routine.
A module with an omethod (that is, a module whose method is implemented by a C function) has a sequence number parameter as the third argument of its callback function. To determine whether any parameter has been modified since the last time this method ran, specify this sequence number as the argument to the function; for example:
OMchanged returns the following integer status values: 1 to indicate that the parameter has changed, 0 to indicate that it has not, and -1 to indicate an invalid parameter_id argument.
This is how the sequence-number process works.
1. Whenever OMpush_ctx called, the AVS/Express increments a global sequence number. An OMpush_ctx call proceeds the execution of each method, therefore each invocation of each method is guaranteed to have a unique sequence number.
3. When the C function associated with an omethod is called, it is passed the sequence number corresponding to the last execution of the method as its third argument.
Therefore, any parameter with a sequence number greater than the sequence number passed in the third argument has changed. To return the sequence number for a selected object, you can use the OMget_obj_seq routine, as follows:
The OMchanged routine simply calls this routine and compares the sequence number argument passed to it with the sequence number of the parameter. Using this lower level facility, you can keep track of the ordering of various events in the system.
The C API provides routines to set and get all data values that can be maintained using AVS/Express objects. There are several general rules for how these routines generally operate.
- If you try to get the value of an object that is not set, the routine returns value of OM_STAT_UNDEF (0).
- If you request a different data type than the type that is stored, C-style casting rules are employed to convert the value. For example, floating-point numbers are truncated to integers and shorts are promoted to integers.
- Connections are always followed by get operations and then usually by set operations. The only case in which a set operation breaks a connection is when the connection is to a read-only object such as an arithmetic expression.
The first argument to the basic OM routines that set and get data values is the object ID (OMobj_id) of the primitive data object. For example, you specify the OM routine that gets an integer value from an object as follows:
If OMget_int_val returns an error, returned_val memory is not changed.
Before you can use this routine from a module, you must get the object ID of the parameter from the object ID of the parent, using the OMfind_subobj routine. For example, suppose that the V code for your module is as follows:
The C source code for the my_module_update function is as follows:
int
my_module_update(OMobj_id obj_id,
OMevent_mask event_mask,
int seq_num)
{
OMobj_id p1_id;
int p1_val;
p1_id = OMfind_subobj(obj_id,
OMstr_to_name("p1"),
OM_OBJ_RD);
if (OMget_int_val(p1_id, &p1_val) != 1)
return(0);
/* operate on p1_val */
return(1);
}
For many data set and get routines, the OM library provides a convenience version that performs both the OMfind_subobj and OMget_int_val calls in one step. For OMget_int_val, the routine is called OMget_name_int_val. It is specified as follows:
Using this convenience routine, you can write the following more succinct C code for my_module_update:
int
my_module_update(OMobj_id obj_id,
OMevent_mask event_mask,
int seq_num)
{
int p1_val;
if (OMget_name_int_val(obj_id,
OMstr_to_name("p1"),
&p1_val) != 1)
return(0);
/* operate on p1_val */
return(1);
}
AVS/Express supports getting and setting the following types of scalar primitive values:
Getting a string value is different from getting an integer or a real value and thus deserves special mention. The routine OMret_str_val is declared as follows:
If OMret_str_val finds the specified object, it returns a pointer to its string value. If obj_id is unset or has no defined string value, the routine returns NULL value. All valid strings returned are terminated with a NULL character.
String memory is managed in two ways:
- If the buf argument is NULL or buf_size argument is 0, enough memory to contain the entire string is allocated and a pointer to this string is returned. In this case, the caller must free the string memory returned using the C routine free.
- If the buf argument is given a valid argument buffer, be sure to specify the size of this buffer in the buf_size argument. In this case, the first buf_size-1 characters of the string are copied to buf and buf is returned.
This latter usage is typically implemented using code similar to the following:
OMobj_id obj_id;
char buf[128], *buf_ptr;
obj_id = ...
buf_ptr = OMret_str_val(obj_id,
buf,
sizeof(buf));
You use the pointer data type to pass information to user-defined data structures. It is more efficient and easier to use this data type than to describe the details of the structure as ints, floats, and so forth, but you sacrifice some flexibility. Pointer objects have the following restrictions:
- You cannot access or set pointers to the values of objects across process boundaries.
- You cannot change the values of pointer objects, or the data contained in a pointer object from the Network Editor.
- AVS/Express cannot save and restore the values of pointer objects.
- Pointer objects appear to AVS/Express as monolithic structures. A pointer's value can only change or not change; you cannot send more detailed events to indicate that only a particular structure member has changed.
The C API supports getting and setting the following types of primitive arrays:
These array types are accessed with different OM routines.
- Note: AVS/Express does not support pointer arrays.
String arrays have two limitations:
To determine the size of a string array, use the routine OMget_array_size. To get a specific string value, use the routine OMret_str_array_val. To set a particular string value, use the routine OMset_str_array_val.
You can either access raw data arrays in their entirety or process them as subarrays; that is, a piece at a time.
- When you operate on subarrays, AVS/Express copies the array information that you are getting or setting.
- When you operate on an entire array, AVS/Express typically supplies the array object's array pointer directly rather than copying the array memory. However, in some cases, this is not possible, and AVS/Express must copy the entire array. This occurs, for example, when type conversion must be performed (for example, an integer to floating-point conversion) or when the user code being executed and the array object are in different processes.
See Accessing Entire Arrays on page 9-30 for details.
When operating on arrays a piece at a time, you specify the range of each dimension that you want to set or get, then specify a piece of memory of the appropriate size for the operation. You then copy the data either into or out of this memory using the appropriate OMget_sub_xarray or OMget_sub_xarray routine. AVS/Express provides a different set of these routines for each data type it supports, as noted in the following table:
To use these OM routines, you must provide three pieces of information:
- The dimensions of the array on which you are operating
- The minimum and maximum index into the array for each dimension
- A piece of memory of the appropriate size to contain the values specified in the operation
For example, here is the prototype for the OMset_sub_iarray routine:
The prototypes for the other OMget_sub_xarray and OMset_sub_xarray routines are the same except for the data type of the array pointer argument. In the these prototypes:
- The ndim argument specifies the number of dimensions in the array.
- The dims argument is an array of ndim integers that specifies the size of the array in each dimension. These values should be greater than 0 for each dimension or the call is invalid. The first value in the array is the fastest varying dimension in the array (in C, the right-most dimension in the [a][b] specification).
- The min_rng and max_rng arguments specify the range for each dimension that this operation affects. These arrays should each have ndim values.
- The array_ptr argument points to a piece of memory that contains enough space for the following for the appropriate data type:
- For OMset_sub_xarray routines, this memory is copied into the appropriate locations of the AVS/Express array object. For OMget_sub_xarray calls, AVS/Express returns the requested values in this memory.
How you access an entire array depends on whether you know the dimensions of the array before you begin to operate on it.
Array dimensions are known. When the dimensions of an array are defined in the AVS/Express object before you begin to operate on it (this is always true for for read-only array operations), start by having AVS/Express allocate and return a pointer to an array, then operate on that array. This technique works for all modes of array operations: read-only, write-only, and read-write. You use the following OM routines:
Each of these routines takes an array mode that specifies how you will access the memory pointer returned to you. Four modes are available:
- OM_GET_ARRAY_RD. Use this mode if you need only read-only access to the array. Note that you must modify the returned array, otherwise AVS/Express may return a pointer to the same memory in which it stores the array.
- OM_GET_ARRAY_RD_COPY. Use this mode if you will only read the array but may need to modify the data in some situations. It forces AVS/Express to make a private copy.
- OM_GET_ARRAY_RW. Use this mode if you will read and write the array. This mode ensures that any existing values in the array are preserved and, when you free the array, it triggers events to any methods that have requested to be notified when this array changes. If no definition of if the array exists when you make this call, the array is allocated and initialized to 0.
- OM_GET_ARRAY_WR. Use this mode if you will write the array without reading the existing contents. If the array is located in a remote process, AVS/Express does not have to retrieve the old data. If the array is not yet initialized, AVS/Express does not have to initialize the data to 0.
After using these routines, be sure to free the array by calling ARRfree. Calling this routine serves two purposes: it prevents memory leaks and it signals that you are finished with the array. If you specified the mode as OM_GET_ARRAY_WR or OM_GET_ARRAY_RW, a call to ARRfree also queues the events of any methods that requested notification on the array.
Array dimensions are not known. When the dimensions of an array are not defined in the AVS/Express object before you begin to operate on it, use the OMset_array routine. It is specified as follows:
This routine provides flexibility in how AVS/Express allocates and frees the array. You first define the array using memory that you manage and then call OMset_array to tell the AVS/Express object about the new value for the array. At this time, events are queued for any methods that have requested notification on the array.
OMset_array takes an array mode that specifies how the memory passed in for this array is treated. Three modes are available:
- OM_SET_ARRAY_COPY. Use this mode to have AVS/Express make a distinct copy. When the routine completes, you can do whatever you want with the pointer. Note that AVS/Express does not free the pointer for you.
- OM_SET_ARRAY_FREE. Use this mode to prevent unnecessary copying of the array. In order to use it, you must have allocated the array either using the ARRalloc routine or the C malloc facility. After the routine returns, do not free the array yourself or modify the contents further.
- OM_SET_ARRAY_STATIC. Also use this mode to prevent unnecessary copying of the array; however, it places different restrictions on the way in which the memory is used than does OM_SET_ARRAY_FREE. AVS/Express requires that the memory that the array occupies remains valid until a subsequent call to OMset_array is made on the same object. AVS/Express does not free the array when it is finished with it.
- One way to use this routine is to have AVS/Express point to an array in a FORTRAN common block or global definition in a C program. You can subsequently modify the array but you must then issue another call to OMset_array with the same pointer so that AVS/Express can notify methods that depend upon this array.
The basic primitive set commands do not provide a mechanism for deleting the value of an object and returning it to the unset state. To do this, you can call OMset_obj_val as follows:
This call also breaks any existing connections that the specified object may have.
This section describes how to create objects, either by copying them from templates or by parsing an object definition in a V buffer, and how to delete objects.
AVS/Express uses the prototype model for object creation; that is, you use the copy operation to implement both templates (types of objects) and instances (active objects). Specifically, you copy an existing template object to create a new template objector an instance of any object.
- Note: AVS/Express does not support copying instances.
The destination of the copy operation determines whether the object is instanced or not:
- The Applications object and all of its subobjects are instanced.
- The Templates object and all of its subobjects are not instanced.
AVS/Express invokes only the methods of instanced objects.
There are four steps in the object-creation process:
Tthe pathname of a template object is usually of the following form:
For objects stored in a global library, you can omit the library-name component:
Do not use any global libraries in the pathname specification for an object: developers may change the names of these libraries. For example, the object named clamp is stored with the pathname MODS.Filters.clamp. Because the Filters object is global, you should specify the pathname of the clamp object only as MODS.clamp. This gives you the flexibility of moving the clamp object from the intermediate Filters library without changing your code.
Templates in global libraries are defined for the common base types such as group and int so that you can create these objects using a pathname equal to the base type name.
When determining the parent for the object you are going to create, note that if you are creating an instanced object, the parent should be somewhere in the Applications hierarchy (perhaps even the Applications object itself).
If you are creating your object from a module's update method, you must determine whether you want this object to be deleted automatically when your module is deleted. If so, you might create a group within your module to serve as a destination for dynamically created objects.
You must also determine whether you want the Network Editor to be able to access the object. This may affect your choice of a destination for the new object. Note that you can set the NEvisible property to prevent your object from being edited in the Network Editor even if you create it as a subobject of an object that the Network Editor displays.
The simplest way to create a new object is to call the OMcreate_obj_from_path routine, specified as follows:
The parameters for this routine are the pathname of the template object (see Determining the Template Pathname on page 9-33), a character string that contains the name for the new object, and the object ID of the parent object. If an object already exists with the specified new name, OMcreate_obj_from_path chooses a unique name of the form new_name#1 (or new_name#2 and so forth, as appropriate). When it finishes, OMcreate_obj_from_path returns the ID of the object that it creates. If an error occurs, for example in finding the specified template object, it returns OMnull_obj.
If you created your object in a hierarchy that will be saved, you must determine whether or not you want this dynamically created object to be saved with the rest of the hierarchy. If not, set the nosave attribute on the object (see General Properties and Attributes on page 14-3).
If you are creating an object such as an array that has complex dimensions or properties, a simpler approach may be to generate a buffer that contains a V definition of the object and then use the V parser to create the object. To do this, you use the OMparse_buffer command.
The following example creates two int objects named bar and foo that are placed into the parent object specified by parent_id:
char *buf = "int bar; int foo[bar*2];";
OMobj_id parent_id;
parent_id = ...
status = OMparse_buffer(parent_id, buf, 0);
To delete objects, use the OMuser_destroy_obj routine. This routine first breaks all connections to objects that are subobjects of the object being deleted, then destroys the specified object. The destroy callbacks for any objects that are deleted in this operation are called during this routine.
This section describes making and breaking connections, following connections, and following pointers, and references to determine object values.
The C API contains a number of OM routines that you can use to make and break connections.
For objects that can have only a single connection (scalar primitive objects, scalar group references, and links), OMset_obj_ref is the most useful choice. This routine replaces any existing connection with the specified new object connection. You can also use it with array objects when connecting one array object to another array object. It is declared as follows:
Calling OMset_obj_ref is equivalent to the following V statement:
The mode argument for this routine is typically 0. However, you can set mode to OM_OBJ_REF_RDONLY to make connection that allows only get operations through the connection. In this mode, set operations on the from_id object break the connection.
To break a connection with OMset_obj_ref, specify the to_id argument as OMnull_obj.
For objects that can have arrays of connections, an appropriate choice is OMadd_obj_ref, declared as follows:
This routine adds a new connection from the array from_id to the scalar to_id. While you can use OMadd_obj_ref with a scalar object as the from_id argument, it returns an error if the object has an existing connection.
The mode argument for OMadd_obj_ref is typically 0. However, you can set mode to OM_OBJ_REF_QUERY to determine whether a connection can be made without actually making the connection. In this case, the routine returns 1 if the connection is can be made and 0 if it cannot.
To break a single, specific connection in an array of connections, call OMdel_obj_ref as follows:
This routine deletes the connection from from_id to to_id. The mode argument for this routine is always 0.
If you want to traverse connections as they are displayed in the Network Editor, you use two routines: OMget_num_refs and OMget_array_ref.
OMget_num_refs determines the number of connections from a specified object.
- For a scalar object, the number of connections is either 0 or 1.
- For an array object, the number of connections is 0 if the object has no connections, 1 if the object is connected to another array object, or an integer greater than 1 if the object is connected to a list of scalar objects.
The following table shows, for various V statements, how many connections OMget_num_refs would report for the "from" object:
OMget_array_ref finds the destination of a specified connection. You provide an integer index argument that is less than the value supplied by the OMget_num_refs call.
Note that if you are dealing with arrays of references to groups and you use the OMget_array_ref call to access the values of each individual group, your code will not work properly if the group either is defined as a single connection to an array of groups or is not defined as a local array. In this case, use the OMget_array_val routine.
Each AVS/Express object has an object value. Object value is defined differently for primitive objects (int, float, and so forth) than for group objects. You can determine an object value either in V, using the $obj_val command, or from the C API routine OMget_obj_val.
- For primitive objects, the object value is defined as the last object in a chain of simple connections. This table provides some examples of how the definition is applied:
- For a group object that is not a reference or a pointer to a group (that is, it is not defined with the & or * reference modes), the object value is defined as the object itself. If the group object is a reference, however, its object value is defined as the first nonreference in the chain of connections. Note that the object value rules apply identically for & and * reference modes and are the same for both scalar groups and arrays of groups. This table provides some examples of how the definitions are applied:
- The C API routine OMget_obj_val returns a 0 status if the value is not set (as is the case with a group reference that is not connected to another object).
AVS/Express is limited to operating on one-dimensional arrays of groups, modules, and macros. Because you manage arrays of these three object types identically, this section describes management techniques only for arrays of groups. To manage arrays of modules or macros, simply follow the same instructions but substitute module or macro for group.
There are three ways to specify an array of groups:
You use the same commands to access an array of groups regardless of how it was specified.
To determine the number of groups defined in an array, use the OMget_array_size routine. This routine returns 0 if the array dimensions are not set or the group's value is not set.
To get information about a specific array element, use OMget_array_val. This routine takes as arguments the ID of the array of groups and an index into the array, and typically returns the ID of the specified element. In some cases, however, it returns the ID of a name-link object rather than the ID of a group itself. When this happens, you can use the OMget_obj_val routine on the returned ID to skip through the name-links. Calling OMget_obj_val allows you to handle properly the error case that occurs when the specified element of an array is not set. For example, this error occurs with the following V definition:
In this case, the OMget_array_size function returns a size of 1 and OMget_array_val returns the ID of the v1 object. However, because the value of v1 is not set, you should not process it as a valid object. OMget_obj_val detects this case and returns a 0 status.
You can create arrays of groups from the C API in two different ways:
- As an explicit array of groups. AVS/Express creates the individual groups for you automatically as the size of the array changes.
- As a reference to an array of groups. You must create and destroy each object in the array individually
The following example V code defines an explicit array of groups:
Resetting the value of nfoos automatically resizes the foo array.
- If you decrease nfoos, the groups at the end of the foo array are destroyed.
- If you increase nfoos, AVS/Express automatically creates new groups from foo's template and places them at the end of the array. In the preceding example, new groups created in this way have a sub1 subobject with the value 10 and a sub2 subobject whose value is unset as defined in foo's template.
An alternate way to specify the dimensions for an explicit array of groups is to use the following V syntax:
When you use this syntax, you should set the size of the foo array with the OMset_array_size routine.
Defining an explicit array of groups has two limitations:
- You can create only groups of a homogeneous type. Because new array values are created only from the template defined from the array, there is no way to define two entries in the array from different template objects.
- New array objects are always added at the end and deleted from the end as the array changes. Therefore, you cannot insert an array value in a specific position or delete a specific array value.
By defining your array of groups as a reference to an array of groups, you can create an array of groups over which you have more control. However, this technique is more complicated, because you must handle the creation and destruction of the individual array objects and manually insert them into the right location of the array of group references. However, because you perform the create/destroy and insert/remove processes separately, you can arrange the objects in the array as you choose.
For general instructions on creating array objects, see Creating and Deleting Objects on page 9-32. This section contains some notes specific to array objects (groups, in this case).
As noted previously, you must have a destination object that stores your newly created groups. You may also want to create an empty group object in your module or elsewhere in your application to contain these objects and serve as a parent for managing them. Finally, you must decide whether to set the nosave attribute on objects that you create, to prevent them from being saved and restored with your application.
When creating an array object, you must ensure that it matches the template declaration for your array of groups. If not, an error will occur when you try to add the object to the array of groups. For more infomatiom see Viewing and Setting an Object's Reference Mode on page 5-51
You can define an array of groups to have either explicit or unspecified dimensions. Note that if the dimensions of your group are unspecified, inserting and removing objects is much easier. When an array's dimensions are unspecified, the array size is determined automatically as you add or delete entries. If you need the size of the array as a separate integer object, you can use the array_size V function, as follows:
If your groups are defined using this syntax, you can perform the following operations on your array of groups:
- Add a new entry onto the end of the array, using OMset_array_val with the index argument set to the flag OM_ARRAY_APPEND.
- Insert a new entry into your array, using OMset_array_val. Set its index argument to the index that you want your new entry to have after the operation.
- Remove a specific object, by finding the object with OMget_array_ref and removing that object with OMdel_obj_ref.
Note that while you can add the same object to an array more than once, you can remove only its first occurrence in the array.
If you specify explicit, rather than unspecified, dimensions for your array, you can use the OMset_array_val routine to replace a specific entry in it.
Properties and attributes attach additional information to an object's structure.
For descriptions of specific properties and attributes, see Properties and Attributes on page 14-3.
Properties have simple primitive values such as integers, strings, floating-point numbers and arrays of these values. They cannot be connected to other objects and cannot have hierarchical object values. The following table lists the C API routines with which you set and get properties for various data types:
In these routines, properties are identified using the OMobj_name structure (see Object Names on page 9-18). Some standard system properties are predefined in the include file <avs/om_att.h>. You can either use these global definitions or call the OMstr_to_name routine to convert a string to a property identifier.
The Network Editor define the NEportLevels property as an array. This array has two integer values that specify the number of levels to export the input and export ports, respectively. To set these values to the same value, you can use the OMset_obj_iprop routine. However, if you want to set the input and output levels to different values, you must use the OMset_iarray_prop routine, defined as follows:
To set the NEportLevels property, you call this routine as follows:
int vals[2];
vals[0] = 0;
vals[1] = 2;
OMset_iarray_prop(obj_id,
OMstr_to_name("NEportLevels"),
2,
vals);
Attributes are special Boolean values associated with an object. AVS/Express defines a specific set of attributes, for example read, write, and notify; you cannot define new attributes. Each attribute is identified in the include file <avs/om_att.h> by a global variable having the following form:
You can query the state of an attribute by calling the routine OMget_obj_att and specifying the associated variable. For example, to inquire whether the read attribute is set on an object, call OMget_obj_att as follows:
OMobj_id obj_id;
int state;
obj_id = ...
if (OMget_obj_att(obj_id,
OM_att_read,
&state) == 1 && state == 1) {
// read attribute is set
}
To set or clear a particular attribute on an object, call the OMset_obj_att routine, referencing the relevant variable.
AVS/Express supports state save and restore operations in two different formats:
You can use C API routines to perform save and restore operations in either format.
When you save object descriptions in a text V file, you specify the objects to be saved by supplying the object ID of the parent of the subtree of the relevant AVS/Express object hierarchy. AVS/Express then saves a V file that contains derived object definitions for the saved objects. (The Object->Save Objects command in the Network Editor uses this operation.) When you restore (load) the file, AVS/Express simply recreates the object hierarchy. Note that you can restore a V file only if any templates referenced by it exist in the running version of AVS/Express.
Suppose, for example, that you create an object named my_button from the template object UIbutton, then save it to a V file. The V file contains this definition for my_button:
To restore this definition successfully, the system must have a definition of the UIbutton object.
You use the C API routine OMwrite_obj, defined as follows, to save objects to a V file:
In this definition, obj_id is the object ID of the parent of the subtree of the object hierarchy to be saved, and filename is the name under which to save it. If a file having the specified filename already exists, AVS/Express replaces it unless you set the mode argument to OM_WRITE_APPEND.
You can also use the mode argument to specify bit flags that determine what parameter values in the object descriptions are saved. Parameter values are tagged with a state flag that indicates one of three possible states for the value:
Table I-13
Set the mode argument as follows to specify particular combinations of parameter types to save:
- Set no flags to save program state, user state and transient state values.
- Set it to OM_STATE_USR to save only program state and user state values.
- Set it to OM_STATE_PRG to save only program state values.
For information about changing the state mode when writing a module, see Event Notification of Methods on page 8-11.
There are several ways to restore text V files.
To read V statements from a files, call the OMread_desc routine, defined as follows:
OMread_desc reads the specified V file so that objects defined in the file become subobjects of the object specified by the obj parameter. Specify the mode argument as 0. This routine returns 1 to indicate succes, 0 to indicate that errors were encountered while reading the file, or -1 to indicate that an invalid object ID was specified.
To parse a buffer of V commands, call the OMparse_buffer routine, defined as follows:
The buffer argument specifies a null terminated buffer of V statements. These statements are parsed in the same way that the file in the OMread_desc routine is parsed. Any objects defined in the buffer becomes subobjects of the specified object.
To set an object's value from a V value string call the OMparse_obj_val routine, defined as follows:
The value of the specified object is set to the specified value string. For example, consider the following call to this routine:
This call is equivalent to the following V statement:
Note that this mimics the = operator in V. To mimic the => operator instead, call the routine OMparse_obj_ref. For example, consider the following call to this routine:
This call is equivalent to the following V statement:
To parse V commands from a stream, call the OMparse_stream routine, defined as follows:
This routine operates in the same way as the previous two routines, except that it treats the specified stream as an interactive stream of V commands specified by the fp parameter. This routine implements the V command processor (VCP).
AVS/Express supports save operations to two types of binary V files.
- A simple binary V file contains the same type of information saved in a text V file, but in binary form; that is, it saves descriptions of objects derived from template objects as named references to those template objects. You can restore this file only if the template objects exist and have the same path name as at the time of the save.
- AVS/Express flibrary objects use this type of binary V file to create a binary description of the information stored in the V file specified by the libfile property.
- A complete binary V file saves not just objects created from templates, but also all of the templates needed to recreate the objects.
- The Project->Save Compiled Project command in the Network Editor uses this operation.
You use the OMbsave_objs routine, defined as follows, to create both types of binary V files:
int OMbsave_objs(int nobjs,
OMobj_id *obj_id_list,
int nex_objs,
OMobj_id *ex_templ_list,
char *filename, int mode)
The nobjs and obj_id_list parameters specify a list of objects to be saved in the binary V file. When you restore the binary V file, these objects are restored as peers in the AVS/Express object hierarchy; therefore, they typically should be peers in the AVS/Express object hierarchy at the time of the save.
- Note: Do not specify an object that is the parent of another object in the list.
The filename parameter specifies the name under which to save the binary V file.
The mode argument specifies which type of binary V file to create.
- Set the mode argument to 0 to save objects in a binary V file that parallels a text V file. In this mode, you should also set the nex_objs argument to 0 and ex_templ_list argument to NULL.
- Set the mode argument to OM_BSAVE_COMPLETE to save objects and all required templates in a complete binary V file. In this mode, you can use the nex_objs and ex_templ_list arguments o specify additional templates that are added to the set of templates needed to define the objects being saved. For this mode, the default is that the objects specified are instanced when the file is loaded. To override this option, add the flag OM_BSAVE_TEMPLS_ONLY to the mode argument.
Use the OMopen_file routine, defined as follows, to restore (read in) either a binary V file or a text V file:
The objects in the V file are loaded as subobjects to the object specified by obj_id.
C++ programmers are free to use the routines in the C API (the OM library) to manipulate AVS/Express objects, but they can also use the routines in the C++ API. This API, implemented in the OMX library, takes advantage of C++ features to provide a more intuitive interface. This section defines some basic concepts used by this library and provides a high-level description of basic operations using the routines in this library.
For detailed descriptions of the C++ API routines, see the AVS/Express online help system.
Using the OMX interface, you access objects directly, using C++ names to refer to AVS/Express objects of the same name. For example, consider the following C++ statement:
This statement assigns the string value "my_image.x" to the read_image module's filename parameter. The C++ structure for the read_image object parallels the object hierarchy in AVS/Express; member variables in C++ correspond to subobjects in AVS/Express. For AVS/Express objects that manage access to dat (for example, the filename parameter in this exampl), the operator-overloading feature of C++ provides direct access to data.
C++ programmers can use OMX in two ways:
- To import code into AVS/Express. When you use OMX routines to write a new AVS/Express module, AVS/Express generates a C++ class that corresponds to your module. All AVS/Express subobjects of your module are members of this C++ class.
- To export code from AVS/Express. You can use OMX to a generate C++ class that represents an AVS/Express object. (You can do this for any AVS/Express group, module, or macro.) Creating an instance of this class creates the corresponding AVS/Express object, and destroying the C++ instance, destroys the AVS/Express object.
During both imports and exports of code, the C++ programmer has a C++ class that is associated with the AVS/Express object. In both cases, the member variables of the C++ object correspond to the subobjects of the AVS/Express object. Once you have created the class, the interface is the same whether you are using this class to import or export code.
When writing modules, you receive a pointer to your C++ object as the implied this argument to your method. For example, consider the following V code:
The C++ code to set parameter_a to 1 is as follows:
In C++, you can omit the this-> string to write this code more more succinctly, as follows:
In order to export code from the AVS/Express environment, you must get a pointer to the C++ object that corresponds to a particular AVS/Express object. To do this:
- If the object does not yet exist, create it using normal C++ constructs (typically the new operator).
- If the object already exists:
- This extra level of complexity is necessary because each object may be derived from several superclasses, each of which may have different pointers.
The following code fragment creates a new AVS/Express object of the Field class:
When you use a C++ object to create an AVS/Express object and then use the C++ delete operator on that object, you destroy the AVS/Express object.
The constructor for the object takes an optional argument that determines which AVS/Express object is used as its parent. The default parent is the Root.Applications object, which is an acceptable place to place new objects; however, you may want to create a container object for the objects. A container object allows better debugging using the Network Editor. To do this, create an application object, then use this object as the first argument to the constructor:
If the Network Editor contains an existing instance of a Field object with the pathname SingleWindowApp.Field#1, get a pointer to a C++ object that controls this object, as follows:
// Get the object id
OMobj_id field_id = OMfind_str_subobj(OMinst_obj,
"SingleWindowApp.Field#1",OM_OBJ_RW);
if (!OMis_null_obj(field_id)) { // If we can find the object...
Field *my_field =
((OMXgroup *)OMret_omx_ptr(field_id,0))->ret_omx_ptr("Field");
my_field->nnodes = 3;
}
When using OMret_omx_ptr, do not use the C++ delete operator on this object. There is a single C++ object that is shared by all users of the corresponding AVS/Express object, and it is deleted automatically when the object itself is destroyed. To destroy the object, use the routine OMuser_destroy_obj. To access the pointer to the object in this manner, you should be in the same process as the object being manipulated. In case of error, a 0 pointer is returned.
You can access the values for AVS/Express subobjects through member variables of the C++ class. Although there is generally a one-to-one correspondence between the member variable hierarchy of the C++ class and the AVS/Express object hierarchy, there are several exceptions:
- An AVS/Express subobject may not be represented in the C++ class because the export=0 property is set on the subobject or because of the way in which the class is generated (see Exporting C++ Classes on page 9-66).
- Member subobjects may have been renamed using the cxx_name property. You typically do this to remove name conflicts between AVS/Express objects and C++ names.
- If the AVS/Express name is a reserved keyword in C++, an underline character (_) may have been appended to the names of the member objects.
Each AVS/Express object has a corresponding OMX class that implements the C++ programmer's interface to control that object. The following table shows the mapping between the AVS/Express types and the OMX types:
The following sections describe how to use each of these different types of OMX object classes.
Scalar data objects are implemented by a C++ class that is customized for their particular types. For example, an integer is implemented with a C++ class called OMXint. Each of these C++ classes overloads the C++ operators so that these objects can be treated as though they are stored as the particular type. If you try to assign a a value to an OMXint object, the value is cast into an integer and a method is called to perform the assignment. You can use the C++ member objects in most operations where ordinary integers are used: assignments, expressions, comparisons, and so forth. For example, all of these operations are valid using the previous foo.parameter_a example:
There are a few cases in which you cannot use these objects like ordinary values:
- When the program requires a pointer or a reference to the value. For example, the following statement returns a compilation error:
- When you assign one OMX parameter to another. C++ may prevent the object from behaving identically to an int depending on the specifics of your compiler (compiler behavior varies from platform to platform). To ensure the portability of your code, always use an explicit cast when assigning one OMX parameter to another. For example, the following assignment statement is appropriate for integer parameters:
- For strings, you can use an assignment statement like this:
- When the object does not have a current value. If a scalar byte, short, int, float or double AVS/Express object does not have a current value and you try to access the value, a 0 value is returned instead.To determine whether the object has a valid value, use the valid method:
if (foo.parameter_a.valid_obj())
cout << "object parameter a is valid" << endl;
else
cout << "object parameter a is not valid" << endl;
- String values operate much like ints, floats, and so forth; you can use a string object to produce a pointer to a null-erminated character string. Do not free the character string; the space is managed by AVS/Express. If an AVS/Express string object has no defined value, a NULL character string is returned.
You can also assign a character string to an AVS/Express string object. AVS/Express makes a copy of the string passed during the assignment. Assigning a NULL pointer to a string object is equivalent to setting the string to an unset state.
The C++ interface does not handle arrays of ints and floats as transparently as it handles scalar ints and floats: you cannot directly access them using the [] operator. There are two basic ways to access arrays:
- Use the ret_array_ptr method when you define the array's dimensionality explicitly before the array operation begins.
- Use the set_array method when the final array dimensionality is not known until after the array is completely defined (for example, when reallocating an array in a loop).
If the dimensions of your array are defined before you need to access or modify any of its values, use the ret_array_ptr method to get a pointer to the array. For example:
You use the mode argument to specify how you will use the array.
- Set the mode argument to OM_GET_ARRAY_RD if you will only read the array values.
- Set the mode argument to OM_GET_ARRAY_WR if you will only write the array values.
- Set the mode argument to OM_GET_ARRAY_RW if you will both read and write the array values.
The size and type arguments (described later in this section) default to NULL, thus you can omit them for many uses of this routine.
Suppose that your object is defined as follows in V code:
You can to access it as follows:
foo *foo_ref = new foo;
int *my_array_ptr =
(int *)foo_ref->bar.ret_array_ptr(OM_GET_ARRAY_WR);
my_array_ptr[0] = 0;
my_array_ptr[1] = 1;
my_array_ptr[2] = 2;
ARRfree((char *) my_array_ptr);
When you are done with the pointer returned by the ret_array_ptr routine, free it with the ARRfree routine. Failing to free this pointer results in a memory leak and prevents AVS/Express from propagating events that are registered on the array object.
If the array dimensions are not defined at the time that the call is made, the ret_array_ptr routine returns a NULL pointer . It is especially important to note that if your object is defined as follows, you must set nvalues before you try to get the pointer to the array:
By comparison, the following produces an error and returns a NULL pointer:
It is important to match the type and size of an array properly. The ret_array_ptr method optionally returns the array in the most efficient data type that it can. It also returns an array that contains the number of values specified in the current definition of the AVS/Express object. If the AVS/Express definition leaves the type and/or size ambiguous, as in the following V definition, you can have the ret_array_ptr method return the type and size of the array with the defaulted second arguments:
The ret_array_ptr method returns a size that is the total size of the array (that is, the product of all dimensions). It returns a type that is one of the following constants: OM_TYPE_CHAR, OM_TYPE_BYTE, OM_TYPE_SHORT, OM_TYPE_INT, OM_TYPE_FLOAT, or OM_TYPE_DOUBLE.
//
// foo points to the group containing array...
//
int size, type;
void *my_ptr =
foo->array.ret_array_ptr(OM_GET_ARRAY_WR,&size,&type);
switch (type) {
case OM_TYPE_FLOAT:
float *my_float_ptr = (float *)my_ptr;
for (int i = 0; i < size; i++) my_float_ptr[i] = 10.0;
break;
case OM_TYPE_DOUBLE:
double *my_double_ptr = (double *)my_ptr;
for (int i = 0; i < size; i++) my_double_ptr[i] = 10.0;
break;
}
ARRfree(my_ptr);
The ret_array_ptr method always returns an array with a data type that matches the definition of the type in AVS/Express. If you need to force the array to return a type that is different from the type specified in AVS/Express, use the method ret_typed_array_ptr; for example:
The mode argument specifies the same mode as ret_array_ptr. Unless the type argument is set to OM_TYPE_UNSET, the array is forced to the specified type; for example, setting it to OM_TYPE_FLOAT always returns a pointer to an array of floats. The conversion rules are the same as those for a C cast operation. The size argument returns the number of values in the array if NULL is not specified as this argument.
If the dimensions of the AVS/Express array object are not defined until after you start operating on the array, use the set_array method to get a pointer to the array. With set_array, you build the array in memory that you allocate and give the array to AVS/Express only when you are done. For example:
The type argument must be OM_TYPE_CHAR, OM_TYPE_BYTE, or similar. The array argument contains a pointer to the array. The len argument is the number of values in the array. (For an array of ints, this is the number of ints, not the size in bytes of the array.) The mode argument provides several ways to manage memory, in order to prevent making unnecessary copies of data. It can be one of the following:
- OM_SET_ARRAY_COPY. Use this mode (the default) to have AVS/Express make a copy of the values in the array. You can reuse this storage immediately after this call. AVS/Express does not try to free the pointer from the array.
- OM_SET_ARRAY_FREE. Use this mode ti indicate s that the value specified in the array was allocated with the malloc facility. In this mode, the caller should not free the array. AVS/Express frees it when it is finished with the memory.
- OM_SET_ARRAY_STATIC. Use this mode to indicate that, if possible, AVS/Express can directly use the memory pointed to by the array argument. AVS/Express does not try to free the array, but the caller must guarantee that array is valid as long as the AVS/Express object used with this call has not been deleted and no subsequent call to the set_array method has been made.
In OMX, group arrays are implemented by overriding the [] operator so that you can effectively treat the array of groups naturally in C++. For example, consider this V definition:
You can reference the subobjects of the group array bar as follows:
Note that you can reference the values of an array of groups only if its dimensions are already defined. If, for example, consider this V definition:
You must set the ngroups value before you can reference this array:
To inquire the size of an array of groups (or any array), use the ret_array_size method. For example:
Note that this method takes no arguments.
Many C API routines defined in the OM library have OMX method equivalents. Typically, an OMX method takes the same argument list as the corresponding OM routines, with one exception: it omits the first OMobj_id argument, and the object whose method is being invoked becomes the implicit first argument. In addition, some routines also have default values specified for the arguments at the end of the parameter list, making these routines more convenient to use.
For example, you call the OMpush_ctx routine using the OM library as follows:
You can perform the same call using the OMX library as follows:
The 0,0,0 values also happen to be the defaulted values for the push_ctx method, so you can also call this routine more succinctly as follows:
All OMX object types implement the cast operator to produce an OMobj_id. This makes it easy to use any OM routines using an OMX object as an argument directly. For example, you can call the OMret_obj_name routine as follows:
foo *my_foo_ptr = ... // get a pointer to a foo object
// casts my_foo_ptr to an OMobj_id
char *my_name = OMret_obj_name((OMobj_id)*foo_my_ptr);
- Note: Some C++ compilers cannot easily determine how to cast an OMX object to an OMobj_ID value automatically, so you must be sure to include the explicit cast in the statement.
The changed method and the second argument to a user-written method associated with a module work in concert to indicate whether an object has changed since the last time the method was called. The changed method returns 1 to indicate that the parameter has changed or 0 to indicate that it has not. For example, consider the following V definition of a module:
In the C++ method you write to implement the module's update method, you can use the following code to determine whether the bar parameter has changed:
foo::update(OMevent_mask event_mask, int seq_num)
{
if (bar.changed(seq_num)) {
cout << "bar has changed" << endl;
}
return(1);
}
The OMX routines that deal with making and breaking connections parallel the OM routines with the same names:
int add_obj_ref(OMobj_id conn_id,
int mode = 0);
int del_obj_ref(OMobj_id conn_id,
int mode = 0);
int set_obj_ref(OMobj_id conn_id,
int mode = 0);
Each of these routines takes an OMobj_id value as the first argument. Because the OMX objects implement the cast operator to produce an OMobj_id, you can directly specify the name of an OMX object as the first argument. The second argument is a mode that defaults to the value you want to use for normal connections. Suppose that you have a C++ class generated from the following V code:
You use set_obj_ref to replace any existing connections from a source object with either a new connection to a target object or a null connection. (You create a null connection to break the existing connections.) You use add_obj_ref to add a new connection. To delete a specific connection, you use del_obj_ref.
You can simply connect object a to object b as follows:
The set_obj_ref method replaces any existing connections with the specified new connection. You can use this same method to break a connection, as follows:
The add_obj_ref method adds a new connection to the source object. You can use it to connect a source array object to a destination scalar object (or, through multiple calls, a list of destination scalar objects). If the source object is a scalar, this routine returns an error if the source object is already connected to another object.
Suppose that you have an object whose V definition is as follows:
You can use add_obj_ref as follows to connect object a to object b.
This new connection is added to the list of object a's connections (it does not replace those connections). The new connection creates the following value expression for object a:
The del_obj_ref method deletes a connection from the source object to a specific destination object. For example, you can delete a connection from object a to object b as follows:
When AVS/Express generates a C++ class for an object, it must assign it a name that is unique across the system. However, AVS/Express object names are guaranteed to be unique only for the specific parent that contains them. To work around this potential problem, AVS/Express generally creates a name by prepending the name of the containing library onto the name of the AVS/Express object. The exception is that it does not prepend the library name if the library is a global library.
For example, the FLD library is global and its names can be seen at peer level, so you do not have to type the following to create a field in V:
Because the FLD library is global, the C++ class name for the Field object is simply Field. If the FLD library was not global, the C++ class name would be FLD_Field (note that an underline character (_) replaces the period as the separator between the library name and the object name).
You can change the C++ name for any AVS/Express object using the cxx_name property. This works for AVS/Express objects that become member variables, C++ class identifiers, and nonglobal library names that are prepended to the class name for an object.
When AVS/Express generates a C++ class description for an object, it places header code into the file specified by the current value of the out_hdr_file property and source code into the file specified by the current value of the out_src_file property. You can set these properties either directly on the AVS/Express object or on the library that contains the object. If you do not set these properties, AVS/Express uses the defaults of process_name.cxx for source and process_name.h for header information, where process_name indicates the process in which the object is defined ( for example, express). These files are placed in your project directory.
- Note: Do not rely on these defaults except for very temporary projects: the definitions change from file to file as the process property on the object changes. This makes writing code that consistently includes these files problematic. Therefore, when defining a project of significant scope, you should define values for these properties.
If you specify the out_src_file property without a suffix, AVS/Express generates a file whose name has a the suffix .c if none of the file's objects have C++ references, otherwise it generates a file whose name has the suffix .cxx.
When AVS/Express generates a file specified by out_hdr_file, it always includes ifdef__cplusplus constructs in the file so that they can be included in C code as well as in C++ code.
In a number of cases, a C++ class generated by AVS/Express may depend upon other code in the system, either user-written code or other C++ class definitions generated by AVS/Express. These dependencies can be generated from the following:
- Code specified as a string value to a cxxmethod
- Code specified with the cxx_class, or the cxx_members properties
- References to AVS/Express objects whose definitions are stored in different output header files (specified by out_hdr_file)
Unless you provide additional header information, the generated C++ code will not compile properly. To provide this additional header information, you can indicate the dependencies using the cxx_hdr_files property. (You can set this property either on the module or on the library that contains the module.) To set it, specify a string value that contains a list of file names; for example:
These filenames are defined relative to either of the following
- The current value of the build_dir property (also set either on the module or on the library that contains the module)
- The current list of header directories
By default, this list includes the top-level project directory and the include subdirectory of each project in the XP_PATH variable. You can extend this list with the hdr_dirs property.
When a dependency is generated because an AVS/Express object references objects in a library with a different setting for out_hdr_file, it can be difficult to determine the particular dependency. This type of dependency is generated by the definition of the AVS/Express objects rather than by user-written code.
The following example defines two libraries, each with a single object:
library A<out_hdr_file="A.h"> {
group Aobject {
};
};
library B<out_hdr_file="B.h",cxx_hdr_files="A.h"> {
module Bmod {
Aobject &my_input;
};
};
This example creates a dependency between Bmod and Aobject. The library B must specify the cxx_hdr_files property so that B.h knows to include A.h before it proceeds.
Determining such a dependency is especially tricky if you have specified the build_dir property on the object that has the out_hdr_file property set. In this case, the file specified by out_hdr_file is placed into the build_dir directory. In order to reference the header file, you must prepend the value of build_dir onto the value of out_hdr_file in the cxx_hdr_files definition. For example:
library A<build_dir="ADIR",out_hdr_file="A.h"> {
group Aobject {
};
};
library B<out_hdr_file="B.h",cxx_hdr_files="ADIR/A.h"> {
module Bmod {
Aobject &my_input;
};
};
You can import an existing C++ class into AVS/Express. One way to do this is as follows:
However, this process requires significant programming effort and breaks down when the AVS/Express object and the C++ class are subclassed. Therefore, AVS/Express provides a mechanism to automate the import process.
1. Associate the class name of a C++ object with the AVS/Express module by setting the cxx_class property to contain the name of the C++ class.
- When the AVS/Express C++ object is instanced or deinstanced, AVS/Express automatically creates an instance of the C++ object or destroys the instance. When a cxxmethod is called, you can get a pointer to the C++ object.
2. Ensure that AVS/Express can find the C++ class by setting the cxx_hdr_files and cxx_src_files properties either on the module or the library containing the module. These properties should reference the file (or list of files) needed to define the C++ class.
3. Once in the update method, get a pointer to the C++ object with the ret_class_ptr method. This method is called with a single argumentthat is the string name of the class passed to cxx_class. It returns the following value, which you can cast into a pointer of the correct type:
Suppose, for example, that you have a C++ class that implements a quadratic equation solver, and that the header for this class, quad.hxx, reads as follows:
class QuadraticSolver {
protected:
float a, b, c;
public:
void set_params(float new_a,float new_b,float new_c);
virtual int get_roots(float &r1,float &r2);
};
The source for this class might be the following, in quad.cxx:
#include <math.h>
#include "quad.hxx"
void
QuadraticSolver::set_params(float new_a, float new_b, float new_c)
{
a = new_a;
b = new_b;
c = new_c;
}
int
QuadraticSolver::get_roots(float &r1, float &r2)
{
float op = b * b - 4 * a * c;
if (op < 0) return(0); // roots are imaginary
op = sqrt(op);
r1 = (-b + op) / (2 * a);
r2 = (-b - op) / (2 * a);
return(1);
}
You define the interface to this object using the following V code:
module EXquad_solver<cxx_class="QuadraticSolver",
cxx_hdr_files="quad.hxx",
cxx_src_files="quad.cxx",
src_file="quad_mod.cxx"> {
float+read+notify+req a, b, c;
float+write r1, r2;
string+write status;
cxxmethod+req update_roots;
};
The source for this method then looks like the following:
int EXquad_solver::update_roots(OMevent_mask event_mask,
int seq_num)
{
// Get the pointer to the user defined C++ class
QuadraticSolver *my_class_ptr =
(QuadraticSolver *)ret_class_ptr("QuadraticSolver");
my_class_ptr->set_params(a, b, c);
//
// must use temps since r1 and r2 are OMXfloats, not floats
// and the get_roots method takes references to floats.
//
float r1_tmp, r2_tmp;
if (my_class_ptr->get_roots(r1_tmp, r2_tmp) == 0) {
status = "roots are imaginary";
r1 = r2 = 0;
}
else {
status = "roots are real";
r1 = r1_tmp;
r2 = r2_tmp;
}
return(1);
}
It is possible for the constructor to a C++ class specified with the cxx_class property to take arguments. In this case, you use the cxx_class_args property to specify the parameter list to the constructor. The cxx_class_args property can reference variables defined as parameters to your AVS/Express object.
The cxx_class_args property takes a string that is placed between the parentheses of the new operator in C++. You can supply any valid C++ code that can be used in this context. Suppose, for example, that the constructor for the QuadraticSolver class in the previous example takes default values for the parameters a, b, c:
class QuadraticSolver {
protected:
float a, b, c;
public:
QuadraticSolver(float a, float b, float c);
void set_params(float a,float b,float c);
virtual int get_roots(float &r1,float &r2);
};
Suppose also that the constructor simply initialized the values of a, b, and c, as follows:
QuadraticSolver::QuadraticSolver(float init_a,
float init_b,
float init_c)
{
a = init_a;
b = init_b;
c = init_c;
}
You specify the V code for this object as follows:
module EXquad_solver<cxx_class="QuadraticSolver",
cxx_class_args="a,b,c",
cxx_hdr_files="quad.hxx",
cxx_src_files="quad.cxx",
src_file="quad_mod.cxx"> {
float+read+notify+req a, b, c;
float+write r1, r2;
string+write status;
cxxmethod+notify_inst+req update_roots;
};
The generated code that uses the cxx_class_args property then looks like the following:
When you associate a C++ class with an AVS/Express object using the cxx_class property, AVS/Express generates code to instance your class. If you define an abstract class (that is, a class that cannot be instanced because its constructor is private), the generation of this code causes an error. To work around this problem, set the cxx_abstract property to 1, which instructs AVS/Express not to generate code that tries to construct your object.
You can use the cxx_members property to add your own member variables to a generated C++ class. This property takes a string value that is placed directly into a public section of the generated C++ class. This code can then declare new member variables that the C++ code associated with the cxxmethod can reference. For example, suppose that you write the following V code to add a char * value that only your code maintains:
You can ensure that this variable is declared in a private section by adding the following:
In addition, you can add code to construct these members with the property cxx_members_constr. The string for this property is inserted in the code that defines the constructor for the generated C++ class. To initialize the pointer to 0, use the following V code:
module my_module<cxx_members="private: char *my_ptr;",
cxx_members_constr="my_ptr(0)"> {
...
cxxmethod+notify update;
};
AVS/Express automatically generates a C++ class definition for each object that contains one or more cxxmethod subobjects. In addition to using this generated class to implement the cxxmethod, you can use it to create an instance of the object programmatically. This is convenient particularly given the limitation in AVS/Express that each object can have only a single C++ class associated with it. (This is not a severe limitation since you can easily make a derived copy associate a different C++ class with an identical object.)
To force AVS/Express to generate a C++ class for a particular object, use the export_cxx property. This property specifies one of four modes that control whether and how the C++ class is generated for this object:
- 0 : Do not generate a C++ class.
- 1: Generate a C++ member for each AVS/Express subobject unless its export property is set to 0. This is the default for any module with a cxxmethod subobject. Setting the mode to 1 preserves the correspondence between the AVS/Express object hierarchy and the C++ members hierarchy.
- 2: Generate a C++ member only for AVS/Express subobjects that have either the NEportLevels property or the export property set to 2 or higher (that is, they export themselves at least to their parent object). Setting the mode to 2 also preserves the correspondence between the AVS/Express object hierarchy and the C++ members hierarchy.
- 3: Generate a C++ member for each subobject that exports itself to the current object's level using either the NEportLevels property or the export property. Setting the mode to 3 promotes all exported descendants of the AVS/Express object to direct members of the C++ class for this object. This mode is designed to allow you to create a simple high-level programming interface for macro objects that may export ports from several levels down in the hierarchy.
For example,suppose that you have the following V code object definition:
macro my_macro {
group my_module {
int my_exported_param<export=3>; // exported to my_macro
int my_hidden_param<export=0>; // hidden to everyone..
int my_param; // gets the default
};
};
The following examples illustrate how AVS/Express generates a C++ class in the modes 1, 2, and 3.
class my_module ... {
OMXint my_exported_param;
OMXint my_param;
};
class my_macro ... {
my_module my_module;
};
A caveat when using mode 3: because an exported subobject is promoted to the top level, V does not guarantee that its name is unique. If a conflict occurs, the C++ compiler gives a syntax error. You can use the cxx_name property on one or more subobjects to specify an alternate name to use when generating either C++ class name or the C++ member name for the object.
Recall that modes 1 and 2 preserve the AVS/Express class hierarchy in the generated C++ classes. This means, for example, that if AVS/Express object B is derived from AVS/Express object A, the C++ class that corresponds to object B is derived from the C++ class that corresponds to object A. Suppose that you have the following V code definition:
For this definition, AVS/Express generates the following C++ class hierarchy:
If you set export_cxx to 1 or 2, you can treat an object of class B as an object of class A. However, if the value of export_cxx is 3, the C++ class hierarchy does not correspond to the AVS/Express object derivation hierarchy.
- Note: If a cxxmethod is associated with the AVS/Express object, AVS/Express uses a value of 1 for export_cxx when generating the C++ class unless you explicitly specify a value for this property.
You can set the export_cxx property only on hierarchical objects: libraries, macros, modules, groups, or applications. If you set this property on a library, it does not generate a C++ class for the library but generates C++ classes for all subobjects of the library (until the export_cxx property is set again to override the parent's value).
Here are some important rules to follow when generating C++ classes for a library of objects:
- Contain the code in the proper files using the out_hdr_file and out_src_file properties (see Managing C++ Generated Files on page 9-60).
- Completely resolve any dependencies on other C++ classes that the system may have defined (see Managing Dependencies on Other Code on page 9-60).
- Ensure that the names of generated classes will not conflict with the names of any other software packages that you are using.
This example illustrates using the C++ class export mechanism to build a C++ application that uses AVS/Express objects. The application is a simple isosurface/isoline viewer. The C++ program inputs data, selects a contour level, and displays the output using a viewer object.
You start by selecting the objects that the application needs. For this particular application, you collect them into a single library called EXiso_objects and give them new unique names. This makes it possible to customize them without changing the names referenced by the C++ code. It also prevents conflicts with other applications that might create C++ classes for standard AVS/Express objects.
Here is the V code for this project:
library EXiso_objects<
export_cxx=3,
out_hdr_file="ex_out.h",
out_src_file="ex_out.cxx",
process="ex_iso",
no_main=1, // ex_iso.cxx has its own main...
cxx_hdr_files="fld/Xfld.h",
cxx_src_files="ex_main.cxx"> {
SimpleViewer3D EXviewer {
View3D.View.back_col<export=4>;
};
library EXdv_objects<export_cxx=1> {
DV.DViso EXiso;
DV.DVext_edge EXext_edge;
};
VIEW.DataObject EXdata_object;
FLD_MAP.uniform_scalar_field EXuniform_scalar_field;
};
This code sets the following values on the EXiso_objects library:
- export_cxx=3. Since most of the objects that this application will access from C++ code are already exported as ports, setting export_cxx mode to 3 is the best choice. This creates C++ members at the top level that correspond to AVS/Express objects that have either the NEportLevels property set or the export property set.
- out_hdr_file="ex_out.h". This property specifies that AVS/Express places generated header code into the file ex_out.h. The file ex_out.h is automatically placed into the generated makefile for this project. You include the value of out_hdr_file to use the definitions of the generated classes.
- out_src_file="ex_out.cxx". This property specifies that AVS/Express places generated source code into the file ex_out.cxx.
- process="ex_iso". This property isolate these objects into a separate process, called ex_iso. Usually you set the process property to define a process that contains objects that are instanced in the Network Editor. In this project, you start the process, not by AVS/Express.
- no_main=1. This property indicates that AVS/Express should not include a default main for this process when it generates the makefile. Instead, the application supplies a main with the cxx_src_files property.
- cxx_hdr_files="fld/Xfld.h". This property includes the header file used by the Field library to contain its C++ class definitions. This is necessary because the objects referenced in the EXiso_objects library depend upon the objects in the Field library.
- cxx_src_files="ex_main.cxx". This property specifies that the file ex_main.cxx contains the definition of main and the code that creates objects using the C++ class definitions.
The following objects are defined in the EXiso_objects library:
- EXviewer. This object, derived from the standard AVS/Express object SimpleViewer3D, is modified only to export the back_col parameter, whose value is 4, from the View3D.View subobject. The value 4 indicates the number of levels of hierarchy between back_col and EXviewer. Exporting this property enables a C++ program to control the value of the back_col parameter.
- EXdv_objects. This object is a sublibrary that contains two objects from the Data Visualization kit. These objects are defined in a sublibrary both because they are similar in function and because it allows the export_cxx property for both objects to be redefined with a single property setting.
- The two objects are as follows:
- EXdata_object. This object converts the field output from the EXiso and EXext_edge objects into an object that the EXviewer object can render.
- EXuniform_scalar_field. This object is a wrapper that makes it easier to create a uniform scalar field. With this object, C++ code that uses this object must supply one array that defines the dimensions and another array that defines the data.
Here is the source code for this application:
// Include the header file set with out_hdr_file on the library.
#include "ex_out.h"
void
main (int argc, char **argv)
{
// Causes the system to not try and load "inst.v" by default.
OMset_init_state(OM_INIT_LOAD_TEMPL);
// Initialize the system (load the templates).
(void) OMmain_init(argc, argv);
// Create a new application object to house the objects.
// This makes it more convenient to debug in the Network Editor.
OMXappl *appl = new OMXappl;
// Begin a single operation... no results will be displayed until
// the correspond pop_ctx method is invoked.
appl->push_ctx();
// Create the objects, placing them in the application.
EXuniform_scalar_field *field =
new EXuniform_scalar_field(*appl);
EXiso *iso = new EXiso(*appl);
EXext_edge *ext_edge = new EXext_edge(*appl);
EXdata_object *obj1 = new EXdata_object(*appl);
EXdata_object *obj2 = new EXdata_object(*appl);
EXviewer *viewer = new EXviewer(*appl);
// Connect them: explicit casts to OMobj_id are necessary.
iso->in.set_obj_ref((OMobj_id) field->out);
ext_edge->in.set_obj_ref((OMobj_id)field->out);
obj1->in.set_obj_ref((OMobj_id)iso->out);
obj2->in.set_obj_ref((OMobj_id)ext_edge->out);
viewer->objs_in.add_obj_ref((OMobj_id)obj1->obj);
viewer->objs_in.add_obj_ref((OMobj_id)obj2->obj);
// Specify the dimensions of the field that we are creating.
int dims[2];
dims[0] = dims[1] = 4;
field->in_dims.set_array(OM_TYPE_INT,(char *)dims,2,
OM_SET_ARRAY_COPY);
// Specify data for the field that we are creating.
static int data[16] = {0,1,3,4, 6,3,2,1, 5,2,1,4, 3,1,5,4};
field->in_data.set_array(OM_TYPE_INT,(char *)data,16,
OM_SET_ARRAY_COPY);
// Set the isosurface/line threshold value and the edge angle.
iso->level = 2.5;
ext_edge->angle = 0;
// Modify the background color of the view window.
float *back_col =
viewer->back_col.ret_array_ptr(OM_GET_ARRAY_WR);
back_col[0] = 0.3;
back_col[1] = 0.3;
back_col[2] = 0.3;
ARRfree((char *)back_col);
appl->pop_ctx(); // Causes refresh.
OMmain_loop(OM_VCP_DEFAULT); //OM_VCP_SUPPRESS to get rid of vcp.
}
The FORTRAN API includes two types of routines, identifiable by their prefixes as noted in the following table:
The AVS/Express online help system provides reference descriptions of the FORTRAN routines. Use these descriptions as your primary source of information (see the next section for notes on how to use the descriptions). To supplement these descriptions, this section provides a summary of the available routines and information on header files, and a subsequent section provides usage notes for the FORTRAN API routines.
The AVS/Express online help system provides a separate description for each FORTRAN routine (related routines are not consolidated under the same description, as for C and C++). For conciseness, the descriptions of most FORTRAN routines consist only of the following:
- A specification of the routine and its arguments
- A reference to the description of the corresponding C routine
- A brief description of the arguments and the return value
- Details of differences in functionality or arguments, as well as specific FORTRAN behavior
A few descriptions describe functionality specific to FORTRAN and do not, therefore, reference C API descriptions.
This section summarizes the FORTRAN Object Manager API routines. The tables in this section group the routines by functional category, to help you find the routine you need to accomplish a particular task. Within functional groupings, the routines are grouped as follows:
- The get, set, ret, and del routines for the same data type are generally grouped together; for example, OMFget_int_val and OMFset_int_val.
- Routines that take a parent object and a subobject's name are grouped with the routine that operates directly on the object itself; for example, OMFget_name_int_val with OMFget_int_val.
Note that unlike for the C and C++ routines, these groupings do not reflect the organization of the FORTRAN descriptions in the online help system; they are used simply to make locating related routines easier. Always look for the online help system description of a FORTRAN API routine under its own name.
The FORTRAN header file omf.inc contains predefined values specified via PARAMETER statements and function type declarations. Most functions are of type INTEGER but do not follow implicit type declaration conventions. It is important, therefore, to include the header file omf.inc whenever your code calls FORTRAN API routines.
A typical way to include this file is to use the following preprocessor statement:
An SGI f77 machine calls the preprocessor automatically; on Sun and HP machines, the filename extension ".F" is required to resolve the statement correctly.
The FORTRAN API provides a subset of the calls in the C API for the operations most commonly used to write modules. These routines are implemented by wrappers that call the equivalent C API routine. The FORTRAN API is implemented with a library called the OMF library, and all of its routines are prefixed with the letters OMF. This section defines some basic concepts used by this library and provides a high-level description of basic operations using the routines in this library.
For detailed descriptions of the FORTRAN API routines, see the AVS/Express online help system.
The Object Manager provides special data structures for two special types of data that it references:
AVS/Express assigns a unique ID to each object. In a C function, the type of an object ID is OMobj_id (see Object IDs on page 9-16); the FORTRAN API represents this type as the obj_id type, an array of 32-bit integers, by default of size 2. The omf.inc header file defines the size of this type via the parameters OM_OBJ_ID_SIZE or OIDSIZ, which you should use; for example:
Almost all routines in the OMF library operate on the object ID data structure. This type is network transparent; that is, it can be passed from process to process, and machine to machine without change.
The omf.inc header file also provides the following set of predefined object ID variables, for use in your application:
These objects are defined in the file as follows:
INTEGER OMnull_obj(OIDSIZ),
INTEGER OMroot_obj(OIDSIZ),
INTEGER OMinst_obj(OIDSIZ),
INTEGER OMtempl_obj(OIDSIZ)
COMMON /OMF/ OMnull_obj, OMroot_obj, OMinst_obj, OMtempl_obj
By default, their values are not initialized. You can set them as follows:
Each object in an AVS/Express application has a name. One representation of this name is a name string: a character string that is unique among its sibling objects in the object hierarchy. The Object Manager does not store and manipulate this name string directly; instead, it allocates a unique integer value that maps directly to the name string. In a C function, the type of an object name is OMobj_name (see Object Names on page 9-18); the FORTRAN API represents this type as an INTEGER type. This approach has two primary benefits:
- It prevents the duplication of name strings in memory.
- It allows you to compare two name strings using a single integer comparison.
The FORTRAN API contains routines that you can use to
The C API also includes routines that you can use to convert between an integer-valueobject name and the corresponding name string.
- The routine OMFstr_to_name converts a name string to an integer-value object name.
- The routine OMFname_to_str converts an integer-value object name to a name string.
For example, you can get an integer-value object name from a name string as follows:
To start with the integer-value object name and get the name string:
integer name
character*20 name_str
C
name = OMFstr_to_name("string")
idum = OMFname_to_str(name, name_str)
You should treat the name string returned from OMFname_to_str as read-only storage. Do not reuse the memory to which it points.
To compare two integer-value object names for equality, use the FORTRAN equality operator (EQ=).
AVS/Express supports the full array access mechanism available in the C API for FORTRAN arrays of type INTEGER, REAL, and DOUBLE PRECISION, thereby conserving the memory organization of an array. For multi-dimensional arrays specified in AVS/Express as the following:
The corresponding FORTRAN representation is the following:
Some C API routines expect or return pointers to memory locations. For efficiency reasons, the FORTRAN API requires a corresponding mechanism. Since FORTRAN77 does not have a standard (and therefore portable) pointer type, the address of a memory location is represented in terms of an INTEGER type. In cases where the address of an allocated memory area is returned, a function for the translation into an offset relative to a specified memory location is provided. Another utility function returns the address of a storage area for the purpose of passing a FORTRAN array location to the Object Manager.
- Note: Array indexing in AVS/Express is zero-based, that is, the first element of an array object has the index 0. This is important when addressing particular array elements or ranges via an Object Manager API function.
With few exceptions, OMF routines return an integer status code. It can have the following values, which are defined in the omf.inc header file:
If a routine does not return one of these values, the description of the routine in the online help system explicitly states the nature of the its return value.
Many OMF routines take a mode argument as their last argument. This mode argument alters the behavior of the routine: it specifies a list of flags that are combined using the logical OR operation.
For a list of mode argument values for a specific OMF routine, see its description in the online help system.
To use a routine's default behavior (that is, no optional modes), specify a mode value of 0. Note that a mnemonic constant that corresponds to this mode value may not exist.
One of the main operations performed in AVS/Express is traversing the AVS/Express object hierarchy to look for a specific object. Using a single set of traversal routines, you can find objects in a library, parameters in a module, modules in a macro, and so forth.
If you know the name of the subobject that you want to access, you can use either OMFfind_subobj or OMFfind_str_subobj to find it. You use these routines as follows:
integer
OMFfind_subobj(par_id, name, mode, child_id)
integer par_id(OIDSIZ), name, mode, child_id(OIDSIZ)
integer
OMFfind_str_subobj(par_id, name_str, mode, child_id)
integer par_id(OIDSIZ)
character*256 name
integer mode, child_id(OIDSIZ)
Each of these routines returns a subobject of the object par_id in the argument child_id.
The mode argument. If you cannot guarantee that changes will not be made to the object whose ID is returned, specify OM_OBJ_RW as the mode argument for both of these calls. If you can make this guarantee, specify OM_OBJ_RD instead. The value OM_OBJ_RD can prevent the creation of subobjects that are inherited from a base template. In this case the returned object has the base template as a parent, not the object specified with parent_id. When in doubt, use the flag OM_OBJ_RW.
The returned subobject. OMfind_subobj returns the ID of an immediate subobject of the object specified by par_id with the name specified by the name parameter. For example, if you are implementing an update method for a module, you can use the OMFfind_subobj routine to get the object ID of one of your parameters:
integer function update(obj_id, mask, seq_num)
include 'avs/omf.inc'
integer obj_id(OIDSIZ), mask, seq_num, name
integer param_id(OIDSIZ)(
C
update = 0
name = OMFstr_to_name('num1')
if (OMFfind_subobj(obj_id,name,OM_OBJ_RW,param_id) .NE. 1)
& return
By comparison, OMFfind_str_subobj can take a pathname to the subobject. This pathname can have multiple levels of path entries and can go up levels in the hierarchy with the <- notation. If the pathname argument, name, has [n] specified, it can also go through array values. The following are examples of valid string values for the pathname argument:
If these OMF routines do not fine the specified subobject or another error occurs, they return a status of OM_STAT_UNDEF (0).
A module written with an fmethod (that is, a module whose method is implemented by a FORTRAN subroutine) has a sequence number parameter as the third argument of its callback function. To determine whether any parameter has been modified since the last time this method ran, specify this sequence number as the argument to the function; for example:
OMFchanged returns the following integer status values: 1 to indicate that the parameter has changed, 0 to indicate that it has not, and -1 to indicate an invalid parameter_id argument.
The FORTRAN API provides routines to set and get all data values that can be maintained using AVS/Express objects. There are several rules for how these routines generally operate.
- If you try to get the value of an object that is not set, the routine returns a value of OM_STAT_UNDEF (0).
- If you request a different data type than the type that is stored, C-style casting rules are employed to convert the value. For example, floating-point numbers are truncated to integers and shorts are promoted to integers.
- Connections are always followed by get operations and then usually by set operations. The only case in which a set operation breaks a connection is when the connection is to a read-only object such as an arithmetic expression.
The first argument to the basic OMF routines that set and get data values is the object ID (obj_id) of the primitive data object. For example, you specify the OMF routine that gets an integer value from an object as follows:
If OMFget_int_val returns an error, the ret_val integer is not changed.
Before you can use this routine from a module, you must get the object ID of the parameter from the object ID of the parent, using the OMFfind_subobj routine. For example, suppose that the V code for your module is as follows:
The FORTRAN source code for the my_module_update subroutine is as follows:
integer function my_module_update(obj_id, mask, seq_num)
include 'avs/omf.inc'
integer obj_id(OIDSIZ), mask, seq_num, name
integer param_id(OIDSIZ), p1_val
C
my_module_update = 0
name = OMFstr_to_name('p1')
if (OMFfind_subobj(obj_id,name,OM_OBJ_RW,param_id) .NE. 1)
& return
if (OMFget_int_val(param_id, p1_val) .NE. 1)
& return
C
C operate on p1_val here
C
my_module_update = 1;
return
For many data set and get routines, the OFM library provides a convenience version that performs both the OMFfind_subobj and OMFget_int_val calls in one step. For OMFget_int_val, the routine is called OMFget_name_int_val. It is specified as follows:
Using this convenience routine, you can write the following more succinct FORTRAN code for my_module_update:
integer function update(obj_id, mask, seq_num)
include 'avs/omf.inc'
integer obj_id(OIDSIZ), mask, seq_num, name
integer param_id(OIDSIZ), p1_val
C
update = 0
name = OMFstr_to_name('p1')
if (OMFget_name_int_val(param_id, name, p1_val) .NE. 1)
& return
C
C operate on p1_val here
C
update = 1;
return
AVS/Express supports getting and setting the following types of scalar primitive values:
AVS/Express always returns real numbers in real*8 format, even if they are stored as float objects. Therefore, a call to the OMFget_real8_val routine looks like the following:
The routine OMFget_str_val is declared as follows:
integer function OMFget_str_val(obj_id, retstr, maxlen)
integer obj_id(OIDSIZ)
character*?? retstr
integer maxlen
If OMFget_str_val finds the object specified by obj_id, it returns its string value in the parameter retstr. (The string that you pass to the routine should be of size maxlen.) If obj_id is either unset or undefined, the routine returns the standard error code.
Here is an example of how to call this routine:
The FORTRAN API supports getting and setting the following types of primitive arrays:
These are accessed with different OMF routines.
String arrays have two limitations:
To determine the size of a string array, use the routine OMFget_array_size. To get a specific string value, use the routine OMFget_str_array_val. To set a particular string value, use the routine OMFset_str_array_val.
You can either access raw data arrays in their entirety or process them as subarrays; that is, a piece at a time.
- When you operate on subarrays, AVS/Express copies the array information that you are getting or setting.
- When you operate on an entire array, AVS/Express typically supplies the array object's array pointer directly rather than copying the array memory. However, in some cases, this is not possible, and AVS/Express must copy the entire array. This occurs, for example, when type conversion must be performed (for example, an integer to floating-point conversion) or when the user code being executed and the array object are in a different processes.
- When you operate on an entire array, AVS/Express typically supplies the array object's array pointer directly rather than copying the array memory. However, in some cases, this is not possible, and AVS/Express must copy the entire array. This occurs, for example, when type conversion must be performed (for example, an integer to floating-point conversion) or when the user code being executed and the array object are in different processes.
See Accessing Entire Arrays on page 9-87 for details.
When operating on arrays a piece at a time, you specify the range of each dimension that you want to set or get, then specify a piece of memory of the appropriate size for the operation. You then copy the data either into or out of this memory using the appropriate OMFget_sub_xarray or OMFset_sub_xarray routine. AVS/Express provides a different set of these routines for each data type it supports, as follows:
To perform a sub_array call (either set or get) you must provide three pieces of information:
- The dimensions of the array on which you are operating
- The minimum and maximum index into the array for each dimension
- A piece of memory that is of the appropriate size to contain the values specified in the operation
For example, here is the declaration for the OMFset_sub_iarray routine:
integer function OMFset_sub_iarray(obj_id, ndim, dims, & min_rng, max_rng, array)
integer obj_id(OIDSIZ), ndim, dims[OM_ARRAY_MAXDIM]
integer min_rng[OM_ARRAY_MAXDIM], max_rng[OM_ARRAY_MAXDIM]
integer array[*]
The declarations for the other OMFget_sub_xarray or OMFset_sub_xarray routines are the same except for the data type of the array pointer argument. In these declarations:
- The ndim argument specifies the number of dimensions in the array.
- The dims argument is an array of ndim integers that specifies the size of the array in each dimension. These values should be greater than 0 for each dimension or the call is invalid. The first value in the array is the fastest varying dimension in the array (in FORTRAN, the right-most dimension in the [a][b] specification).
- The min_rng and max_rng specify the range for each dimension that this operation affects. These arrays should each have ndim values.
- The array_ptr points to a piece of memory that contains enough space for the following for the appropriate data type:
- For OMFset_sub_xarray routines, this memory is copied into the appropriate locations of the AVS/Express array object. For OMFget_sub_xarray calls, AVS/Express returns the requested values in this memory.
How you access an entire array depends on whether you know the dimensions of the array before you begin to operate on it.
Array dimensions are known. When the dimensions of the array are defined in the AVS/Express object before you begin the operation (which must always be true for read-only array operations), you can have AVS/Express return a pointer to an array that it has allocated that you can then operate on. This technique works for all modes of array operations: read-only, write-only, and read-write. You use the following OMF routines:
Each of these routines takes an array mode that specifies how you will access the memory pointer returned to you. Four modes are available:
- OM_GET_ARRAY_RD. Use this mode if you need only read-only access to the array. Note that you must modify the returned array, otherwise AVS/Express may return a pointer to the same memory in which it stores the array.
- OM_GET_ARRAY_RD_COPY. Use this mode ify ou will only read the array but may need to modify the data in some situations. If forces AVS/Express to make a private copy.
- OM_GET_ARRAY_RW. Use this mode if you will read and write the array. This mode ensures that any existing values in the array are preserved and, when you free the array, it triggers events to any methods that have requested to be notified when this array changes. If no definition of the array exists when you make this call, the array is allocated and initialized to 0.
- OM_GET_ARRAY_WR. Use this mode if you will write the array without reading the existing contents. If the array is located in a remote process, AVS/Express does not have to retrieve the old data. If the array is not yet initialized, AVS/Express does not have to initialize the data to 0.
Because these routines are not intended to copy potentially large arrays and because FORTRAN does not support pointers, they use an offset mapping scheme to allow you to index the returned array. The routine returns an integer offset that refers to the array. You call the routine ARRFoffset with this offset and a dummy array of your own. The ARRFoffset routine returns an offset that you add to the index of your array to dereference the array.
- Note: This technique will not work if your compiler enforces array bounds checking.
After using these routines, be sure to free the array by calling ARRFfree. Calling this routine serves two purposes: it prevents memory leaks and it signals that you are finished with the array. If you specified the mode as OM_GET_ARRAY_WR or OM_GET_ARRAY_RW,a call to ARRFfree also queues the events of any methods that requested notification on the array.
The following example illustrates use of the OMFret_array_ptr routine:
integer ndims, dims(OM_ARRAY_MAXDIM), iaddr1, offset1
real base(1)
C
C Get the "integer address" of the array
C
iaddr1 = OMFret_array_ptr(obj_id,OM_GET_ARRAY_WR,isz,itype)
C
C Get the offset from this address to our `base' array
C
offset1 = ARRFoffset(iaddr1, base, OM_TYPE_FLOAT)
C
C Now add this offset onto the normal loop index for our array
C
do 101 i=1,isz
base(offset1 + i) = 333.333
101 continue
C
C Free the array when we are done with it
C
ARRFfree(iaddr1)
Array dimensions are not known. When the dimensions of an array are not defined in the AVS/Express object before you begin to operate on it, use the OMset_array routine. This routine provides flexibility in how AVS/Express allocates and frees the array. You first define the array using memory that you manage and then call OMFset_array to tell the AVS/Express object about the new value for the array. At this time, events are queued for any methods that have requested notification on the array.
OMset_array takes an array mode that specifies how the memory passed in for this array is treated. Three modes are available:
- OM_SET_ARRAY_COPY. Use this mode to have AVS/Express make a distinct copy. When the routine completes, you are can do whatever you want with the pointer. AVS/Express does not free the pointer for you.
- For example:
real values(3)
values(0) = 0
values(1) = 1
values(2) = 2
OMFset_array(obj_id, OM_TYPE_FLOAT, values, 3, & OM_SET_ARRAY_COPY)
- OM_SET_ARRAY_FREE. Use this mode to prevent unnecessary copying of the array. In order to use it, you must have allocated the array using the ARRFalloc routine. After the routine returns, do not free the array yourself or modify the contents further.
- For example:
integer iaddr, offset, i
real base(1)
iaddr = ARRFalloc(0, OM_TYPE_FLOAT, 10, 0)
offset = ARRFoffset(iaddr, base, OM_TYPE_FLOAT)
do 101 i = 1, 10
base(i+offset) = 2 * i
101 continue
- OM_SET_ARRAY_STATIC. Also use this mode to prevent unnecessary copying of the array; however, it places different restrictions on the way in which the memory is used than does OM_SET_ARRAY_FREE. AVS/Express requires that the memory that the array occupies remain valid until a subsequent call to OMFset_array is made on the same object. AVS/Express will not free the array when it is finished with it.
- One way that this routine can be used is to have AVS/Express point to an array in a FORTRAN common block. You can subsequently modify the array but you must then issue another call to OMFset_array with the same pointer so that AVS/Express can notify methods that may depend upon this array.
The basic primitive set commands do not provide a mechanism for deleting the value of an object and returning it to the unset state. To do this, you can call OMFset_obj_value as follows:
This call also breaks any existing connections that this object may have.
![]() |
![]() |
![]() |
![]() |