![]() |
![]() |
![]() |
![]() |
5 Integrating user code
This chapter describes how to integrate user code into AVS/Express using the Object Manager C, C++, and FORTRAN Application Programming Interfaces (APIs).
- Introduction, Section 5.1
- Adding new AVS/Express modules, Section 5.2
- Module creation overview, Section 5.3
- Code management properties for modules, Section 5.4
- Adding modules using the C API, Section 5.5
- Adding modules using the C++ API, Section 5.6
- Adding modules using the FORTRAN API, Section 5.7
- Manipulating AVS/Express objects using the C API, Section 5.8
- Manipulating AVS/Express objects using the C++ API, Section 5.9
- Manipulating AVS/Express objects using the FORTRAN API, Section 5.10
5.1 Introduction
There are two different ways that new or existing user code can be integrated with AVS/Express:
- New modules can be added to AVS/Express.
- Instances of existing AVS/Express modules and macros can be created, destroyed, and modified.
5.1.1 Adding new modules
A module is an AVS/Express object that has parameters and methods. Each method corresponds to a C, C++, or FORTRAN subroutine that is called when the object is created or destroyed or a parameter's value changes. When a method is called, it has the ability to access and modify its parameters values.
5.1.2 Manipulating instances of objects
Any piece of code running in the AVS/Express environment can create, destroy, and modify instances of AVS/Express objects. This includes code in
- a user-supplied "main"
- a user interface callback function of a widget managed by the user's code
- a callback handler responding to a system event such as a time-out, poll, or event on a file descriptor
- an AVS/Express module's method
All three programming interfaces allow this flexibility: the Object Manager C API (the OM library), the Object Manager C++ API (the OMX library), and the Object Manager FORTRAN API (the OMF library).
For more information on the OM library, see Section 5.8, Manipulating AVS/Express objects using the C API [page 5-30]
For more information on the OMX library, see Section 5.9, Manipulating AVS/Express objects using the C++ API [page 5-61]
For more information on the OMF library, see Section 5.10, Manipulating AVS/Express objects using the FORTRAN API [page 5-88]
5.2 Adding new AVS/Express modules
To import code into AVS/Express you add one or more methods to a module and specify that each method is executed by AVS/Express when some event occurs (usually object instanced, value changed, and/or object de-instanced). The same method can be executed for different events. Methods provide you with a flag indicating which event occurred to cause this method invocation.
When a method is executed by AVS/Express, you can check which parameter or parameters to this module have changed since the method was last executed.
There are three different techniques for adding modules supported by AVS/Express:
- through the Object Manager C API
- through the Object Manager C++ API
- through the Object Manager FORTRAN API
- Note: In previous releases of AVS/Express, another technique for adding modules, the User Code Interface (UCI), which allowed you to encapsulate a C or C++ structure or function was also available. The UCI has now been superceded by the C and C++ APIs. You should always use these techniques to encapsulate C and C++ structures and functions (although, for compatibility reasons, the UCI is still supported and documented in Appendix A, "User Code Interface (UCI) reference").
AVS/Express provides a C API for accessing the Object Manager. This API includes routines for getting and setting any AVS/Express object, navigating the object hierarchy, creating and deleting objects, controlling execution, and so forth. This API gives you the greatest flexibility but requires learning this API.
AVS/Express provides a C++ API for accessing the Object Manager that takes advantage of features in C++ for providing an interface that is easier to learn and produces code that is easier to read. This is the preferred API for users who know C++. It is appropriate for novice C++ programmers.
AVS/Express provides a FORTRAN API for accessing the Object Manager.
5.3 Module creation overview
Once you have chosen the code integration technique you are going to use, the three basic steps to adding AVS/Express modules are:
- Define the method or methods for the module and determine whether these methods will be executed when the module is instanced (that is, created) and/or de-instanced (that is, destroyed).
- Define the parameters for the module
- For each parameter, determine how this parameter relates to each method:
module AddImage {
Image+read+notify &Image1;
Image+read+notify &Image2;
Image+write outImage;
omethod+notify_inst update = "AddImage_update";
};
Image1, Image2, and outImage are parameters. update is a method.
A parameter can be a primitive data object (int, float, byte, and so on) or a group data object (any object whose base type is group). In some cases, you might event want to have a module itself be a parameter.
5.3.1 Methods
The method identifies its target function with its value. In the example below, update is a method whose target function is AddImage_update:
There are different types of methods. The one you choose depends largely on the integration technique:
5.3.2 Method naming restrictions
When you add your own AVS/Express objects that interface to C, C++, and FORTRAN code, AVS/Express generates code to bind the AVS/Express object to your code. This generated code uses symbols derived from the names of AVS/Express objects (for generating C++ classes) and the string values of AVS/Express method objects (omethod and cmethod).
To prevent your methods from clashing with symbols defined by other components that you are using, you should add a unique prefix to their names. As a general rule, you should avoid the prefixes used by AVS/Express components (OM, UI, FLD, DV, GD, GMOD, AG, PAL, IP, and AC) and simple names that might conflict with standard C functions (for example, read, write, and select).
If you do have any conflicts, you will see errors when AVS/Express generated code is being compiled.
5.3.3 Calling methods for instance/de-instance
A method will often want to be called when the object is instanced or de-instanced (a method is always de-instanced before it is destroyed). This is specified by merging the following base types with the method:
specifies a method "update" that will be executed when the module containing this method is instanced.
5.3.4 Defining parameter attributes for methods
The specification for a method must include the following information (specified by setting related attributes) for each parameter. For a complete table of attributes you can set on parameters to control the behavior of methods, see:
Section 9.1.3, Module control properties [page 9-19] in Appendix 9, "Properties, attributes, primitives, and functions"
Does the method read and/or write the parameter's value? The read and write attributes are used to determine the execution order of methods when more than one method has been triggered by the same operation. If one method reads a parameter and another method writes the same parameter, the method that writes the parameter will be executed first. The read attribute is turned off with the noread attribute. The write attribute is turned off with the nowrite attribute. If neither read or noread is set on a particular object, it inherits the value from its parent object. The same is true for write and nowrite.
Should a change in the parameter's value cause the method to be executed? If notify is set on a parameter for a particular method, when the parameter's value is changed an event is queued to execute that method. The event is delivered when the current operation is complete.
The notify attribute is turned off with the nonotify attribute. If neither attribute is set on a particular object, the parameter inherits the value of the notify flag from its parent.
Should the method only be executed when the parameter has a valid value? The req (short for required) attribute behaves differently than the read, write and notify attributes with respect to how it is propagated through the object hierarchy. It is turned off automatically when it is not specified. In other words, you must specify it for both the method and the parameter and at each level in the hierarchy for hierachical parameters. See below for an example.
The required attribute prevents the method from being called whenever required parameters do not have valid values. This applies to instance and deinstance events as well as value changed events. If you want your instance event to be called even if parameters are not valid, do not set the req attribute on the method.
By default, all of these attributes are off for a particular parameter but you can overwrite the default by setting one of these attributes on the method itself. Thus with the syntax:
the method update is notified whenever p1 and p2 change and also reads p1 and p2's values.
You can set attributes explicitly on parameters. The same result as the above can be obtained with
The attributes read, write and notify can be turned off with noread, nowrite and nonotify. A method that reads and is notified by p1 and p2 but writes and is not notified by p3 can be specified with
module test {
int p1;
int p2;
int+nonotify+noread+write p3;
omethod+notify+read update = "test_update";
};
5.3.4.1 Defining parameter attributes on a per-method basis
When dealing with multiple methods, it is sometimes convenient to be able to specify the attributes for parameters on a per-method basis. You can do this using a parameter syntax on the method:
When you specify parameters in this way, the method is only affected by the parameters listed. Parameters not included in the parentheses do not affect the method even if they have the notify, read or write attributes set.
Even though this specification is similar to the declaration of C function parameters, the way in which the C function is called (in this case, test_update) is not affected by using this syntax. The argument list of the C function is always the same, and is described in Section 5.5, Adding modules using the C API [page 5-17].
5.3.4.2 Setting parameter attributes on hierarchical parameters
If a parameter does not explicitly specify a value for read, noread, write, nowrite, notify, or nonotify, it will inherit the value from the parent object. If the parameter is an immediate child of a module, the value specified on the method is used. If the parameter is a hierarchical object, the attribute will be in effect until it is turned on or off explicitly as we move down the hierarchy. For example
module test {
group+read p1 {
int sub1_p1
int+noread+nonotify sub2_p1;
};
int+read p2;
omethod+notify update = "test_update";
};
The example has the update method reading and being notified by parameter p1.sub1_p1 but not reading and not being notified by parameter p1.sub2_p1.
5.3.4.3 Specifying the req attribute for a method
The req attribute behaves differently than read, write and notify for efficiency. It must be set at each level in the object hierarchy for it to be applied to a particular parameter starting with the method itself. To set the req attribute on a parameter p1 use the syntax
In this example, the p1 parameter has required set, the p2 parameter does not.
For hierarchical parameters, the req attribute again is not inherited:
In this example, the update method is not executed if p1.sub2_p1 does not have a valid value. It is executed even if p1.sub1_p1 does not have a valid value.
5.3.5 Defining the scope of an update method
An update method is influenced only by data objects that have the same parent as the method, in terms of where the method was originally defined.
For example, you create a template object called mod1. It includes data objects x and y, and update method upd:
You then create a module called mod2 that inherits from mod1 and defines an additional data object, z:
In mod2, upd is notified if x or y changes. It is not notified if z changes.
This allows a module writer to copy an existing module and add additional parameters and methods without interfering with the existing module's functionality. If you want to replace the functionality defined by an existing method, define a new method of the same name.
In some cases, this behavior may not be desired. For example, you might want the user of an object to be able to add additional parameters that cause the update method to run. To override this feature, set the property no_meth_ctx on the method to a value of 1. For example:
With this definition of mod1, if you it changes made in the copy do affect the way in which the upd method is called.
Here, the parameter x no longer notifies the upd and the z parameter does.
5.3.6 Controlling method execution
When an object is instanced or de-instanced, or when a parameter is changed, an event to execute one or more methods is put into a queue to be executed at the end of the current operation. By default, each method is executed inside of a single operation. In other words, no methods execute until your method returns. If multiple events are queued to execute the same method, they will be compressed into a single method invocation.
In AVS/Express, each operation is controlled using a context. A context is pushed at the start of the operation onto a stack with the push_ctx function.The context contains the following information:
At the end of the operation, the context is popped off of the stack with the pop_ctx function restoring the previous context (if any). Contexts can be nested inside of each other, but cannot overlap.
A method very rarely wants to queue events to itself. The event filter controls this functionality. A method can change a parameter that it would ordinarily receive notifications from without queueing a notification for itself because the event filter in the current context matches the event filter for the method.
The event filter for a method is the object that the method belongs to. Before a method is called, AVS/Express does a push_ctx using this object as its event filter.
The state mode indicates how values changed during this context are to be saved when this object is saved. The object is saved, for example, when the application containing this object is saved. When a value is modified, it is marked with the state mode of the current context.
There are three different states:
For example, when you select a filename from a file browser widget, the widget uses the push_ctx call to set the state mode to user state, then changes the value of its filename parameter. This value is then marked as user state.
Program state is always saved. This state marks the parameter values which are intrinsically part of the application itself. All changes made with the Network Editor and through the V description are considered program state.
User state is only conditionally saved. It marks changes that are made by a user of the application. This includes most parameter values made through user interface widgets.
Transient state marks parameter changes that can be reproduced by a module if its inputs are reproduced. For a "read image" module, the output image would likely be considered transient state since it can be reproduced as long as the filename parameter itself is restored.
The state mode on an object can be overridden using the val_state property. For example, if you want the filename parameter of the user interface widget to be marked as program state, you can set the val_state property on this object to be 1. In V, this is done with:
- Note: You must set the val_state property on the object that is actually storing the value of the object. In this case, if the filename parameter is connected to another value, you must set the val_state property on the object at the end of the connection.
The event mode indicates how events are to be treated in this context. The default event mode is for the events to be queued beginning with the push_ctx call and to be executed synchronously when the pop_ctx call is invoked. When the pop_ctx call returns, all events that were queued between the push_ctx and the pop_ctx call will have been delivered.
You can set the event mode to OM_CTX_PARENT_NOTIFIES. The push_ctx call can be nested one inside another (e.g. push_ctx(A) push_ctx(B) pop_ctx(B) pop_ctx(A)). When a push_ctx call is invoked, the most recent outstanding context is considered the parent context. OM_CTX_PARENT_NOTIFIES indicates that events generated during this context should be executed in the parent context. If there is no parent context, the events are executed in this context. When the system executes a method, it calls push_ctx before it calls the method with this argument.
You can set the event mode to OM_CTX_ALL_NOTIFIES. This mode causes the system to ignore the event filter and therefore queue events that might otherwise have been suppressed. This mode may cause a method to send an event to itself.
An event mode of OM_CTX_BEGIN_OP causes this push_ctx to have no relationship to the parent context. Use this option when the new context is not merely a piece of the existing operation but defines a new operation in its own right.
The push_ctx and pop_ctx operations are available in both the C and C++ APIs.
5.3.7 Controlling method execution order
Method dependencies determine the execution order of methods when a single operation triggers the execution of more than one method. Method A depends upon method B if it reads data that method B writes. The method that reads the shared parameter runs after the method that writes that parameter. This prevents the method that reads the parameter from running twice. Given the V code
int common;
module modB {
int+read+notify p1 => common;
int+write p2;
omethod update = "modB_update";
};
module modA {
int+read+notify p1 => common;
int+read+notify p2 => modB.p2;
omethod update = "modA_update";
};
the method modA.update will depend upon modB.update. When the integer common changes, it queues events for both modB and modA. Because modA depends upon the output of modB, it will run after modB.
To view dependencies, you can use the V command $deps on the particular method in question. With this example, the $deps command when executed on the object modA.update provides this information:
OM(SingleWindowApp) -> $deps modA.update
function: Root.Applications.SingleWindowApp.modA.update depends on:
--------------------------------------------
func: Root.Applications.SingleWindowApp.modB.update
read/write on: Root.Applications.SingleWindowApp.modB.p2
weight: 1, rc: 1, req=0
============================================
5.4 Code management properties for modules
As you define each module or each library of modules, you must provide AVS/Express with information about the code that each group of modules depends upon. With this information, AVS/Express provides the following functionality:
- Makefiles are automatically generated to compile and link each executable
- Modules can be moved from process to process by simply changing the process property on the module or library
You provide this information by adding special properties, called code management properties, on objects (usually modules or libraries) in the template hierarchy. You can either define a close relationship with this system by directly specifying source necessary to build your objects, or you can stand at arms length by pointing AVS/Express to already constructed libraries that you built with your own build system.
You can place code management properties on modules, libraries, or even directly on methods themselves. These properties are inherited by the subobjects. By placing a code interface property on a library, you indicate that all objects in the library have this code dependency. By placing it on a module, you indicate that only the module has the dependency.
For a complete table of all the code management properties, see Section 9.1.4, Code management properties [page 9-22] in Appendix 9, "Properties, attributes, primitives, and functions".
5.5 Adding modules using the C API
AVS/Express provides a C API for accessing the Object Manager. The API includes routines for getting and setting the value of any AVS/Express object, navigating the object hierarchy, creating and deleting objects, controlling execution, and so forth. The C API gives you the greatest flexibility of all module writing techniques, but requires the most lines of code.
Here is an object that averages two numbers:
module ave {
float+read+notify num1;
float+read+notify num2;
float+write res;
omethod+notify_inst upd = "ave_upd";
};
It performs its processing by calling the C function ave_upd.
Notice that upd's type is omethod. This tells AVS/Express that you intend to use the C API.
You need to specify some code management properties so that AVS/Express can tell what code is needed to define the methods that you write. You can specify these properties to point directly to the source or you can point the system at a set of object files or archived libraries.
You should also be aware of what process (i.e., executable) that your module is going to be compiled into. The process is specified with the process property which can either be set directly on your module or on the library that your module is placed in.
For example, you copy module ave into Templates.USER. USER already provides the process property for compiling the function into the user process. In this example, you need to add the src_file property and, since USER also sets build_dir to "user", you should set the build_dir property to your own build directory.
module ave<src_file="ave.c", build_dir="my_build_dir"> {
float+read+notify num1;
float+read+notify num2;
float+write res;
omethod+notify_inst upd ="ave_upd";
};
You can now compile the process containing this object by selecting the object in the Network Editor and selecting Project->Compile. If the source file does not exist, AVS/Express generates a template source file, which you can then edit. The source file will be placed in: <your_project_dir>/my_build_dir/ave.c.
When called at execution time, the target function receives three arguments: the object id of the group, the event_mask indicating the events that triggered this method and a sequence number.
Through calls to the C API, the function gets and sets the values of the module's subobjects.
If any of the inputs are not set, the function returns a 0 value. A 0 value indicates that the module has failed. If a module has failed, any modules that have required dependencies on the outputs of this module will not run until this module has succeeded.
If the module succeeds, it returns 1 for success.
#include <avs/om.h>
#include <avs/err.h>
/*
* ave_upd gets the module id, event_mask and.seq_num
*/
int ave_upd(OMobj_id obj_id,
OMevent_mask event_mask,/* triggering events */
int seq_num) /* sequence number of last exec */
{
double num1, num2, res;
if (OMget_name_real_val(obj_id, OMstr_to_name("num1"),
&num1) != 1)
return(0); /* module failed... input not valid */
if (OMget_name_real_val(obj_id, OMstr_to_name("num2"),
&num2) != 1)
return(0);
res = (num1 + num2) / 2;
OMset_name_real_val(obj_id,OMstr_to_name("res"),res);
For details on the C API routines, see Section 5.8, Manipulating AVS/Express objects using the C API [page 5-30]
5.6 Adding modules using the C++ API
All of the typical module writing rules apply to modules written with the C++ interface. See Section 5.3, Module creation overview [page 5-5] for details on how to specify parameters, how to determine when a method is executed, etc. The main difference between the V specification of a C++ module and other modules is that you use the method type: "cxxmethod" instead of "omethod" or "cmethod". (The "cxx" abbreviation is used by this interface to refer to C++.)
5.6.1 Adding C++ methods
The cxxmethod object accepts the notify_inst, notify, req, and notify_deinst properties in the same way as other methods. The cxxmethod object, however, does not require a string value as do the other methods. In fact, the string value of the cxxmethod is treated differently than other methods if one exists (this will be discussed later).
When a module has a subobject of type cxxmethod, it automatically has a C++ class generated for it. Each cxxmethod defines a C++ method that belongs to this class. The name of the C++ method is the same as the name of the cxxmethod object. Each C++ method takes two parameters:
- The event_mask parameter specifies which events caused this method to be invoked. This parameter allows you to use a single method to be executed in response to multiple events (for example instance and value changed) and to differentiate programmatically between the event cases. The event_mask object is a bitmask that specifies a combination of the flags: OM_EVENT_INST (instanced event), OM_EVENT_VAL (value changed), and OM_EVENT_DEINST (deinstanced event).
- The seq_num parameter specifies the sequence number from the last time that the method was executed. It is used for determining when a parameter is changed by passing it as the first argument to the "changed" method of a parameter. See Section 5.9.8, Determining when a parameter has changed [page 5-71] for more information on how to use this parameter.
For example, an AVS/Express object defined as
requires you to implement a C++ method that looks like
The return value for this routine should be either 1 or a 0. A value of 1 indicates that the method succeeded. A value of 0 means that the module failed. A failure status prohibits any modules that have a required dependency on the outputs of this method from running until this method has executed successfully.
5.6.2 Specifying the location of cxxmethod source code
When you define a cxxmethod object, there are several ways that you can specify where the source to this object is located. Each of these options is explained in detail in the following sections. The options are:
- Specify a specific source file to contain the method. AVS/Express generates a prototype of any methods that do not yet exist in the source file. You can edit the source using the "Edit Source" facility in the Network Editor.
- Directly specify the code for the method as the string value. This technique is useful for very small routines with limited external dependencies. The system maintains the entire definition of the object's V and C++ code all in one location.
- Specify the source through the cxx_src_files or the link_files property. This mechanism is useful for projects where you have your own "make" facilities and do not need the "prototype source" feature or the "Edit Source" facility.
5.6.2.1 Using the src_file property
By default, the system expects to find a src_file property set either on the cxxmethod, the module that contains the cxxmethod, or the library that contains the module. If no src_file is found, a warning message is printed. The first src_file property encountered while ascending the object hierarchy will be chosen as the holder of the source code for a particular cxxmethod.
During the Project->Compile operation, this source file will be scanned for a method named <module name>::<method name>. If such a method exists, no code generation is performed. If no method exists with this name, a prototype of this method is produced and placed at the end of the file named in the src_file property. This prototype method contains code specific to the parameters that the method reads and/or writes. If a method has not set either the read or the write flags on a parameter, no prototype source code is shown for manipulating that parameter.
The code generated varies based on the type of the parameter object. For scalar data objects, only a comment indicating the name of the parameter and the state of the read/write flags is provided. For array data objects, prototype code for getting a pointer to the array and for freeing the pointer to the array is generated. For group or group array parameters, either the name of the parameter is displayed with the state of the read/write flags or, for some data types, a more complete description of what subobjects of the group need to be set and in what order. For example, given the following V code:
module test<src_file="test.cxx"> {
Mesh_Unif+Scalar+notify+read &input;
int+write output;
cxxmethod update;
};
The system generates the following prototype code in test.cxx you compile the process containing this module:
int
test::update(OMevent_mask event_mask, int seq_num)
{
// input (Mesh_Unif+group read)
// input.ndim (int)
// input.dims (int [])
int *input_dims = (int *)input.dims.ret_array_ptr(OM_GET_ARRAY_RD);
// input.nspace (int)
// input.npoints (int)
// input.points (float [])
float *input_points = (float *)input.points.ret_array_ptr(OM_GET_ARRAY_RD);
// output (OMXint write)
/***********************/
/* Function's Body */
/***********************/
printf("I'm in method: test::update\n");
ARRfree((char *)input_dims);
ARRfree((char *)input_points);
// return 1 for success
return(1);
}
Once the prototype code is generated, the only way to have it regenerated is to either rename or remove the method <module name>::<method name>. No attempt is made to keep the prototype code up-to-date in place. This allows you complete freedom to modify the prototype source code.
5.6.2.2 Using the string value to specify method source
To specify a module entirely in V, you supply a string value to your cxxmethod object. This string value then becomes the body of the method. The code body must include a return statement (usually return(1); for success). When you provide a string value for a particular method, the code for this module is placed in the current output source file. The current output source file is set with the out_src_file property. By default the source is placed in <process>.cxx if no out_src_file property has been set.
Using this mechanism, you do not need to maintain any subsidiary files. One caveat is that any user who can edit this object has access to the source for the object as well.
Since the string value may need to be large, it is convenient to use the V syntax for specifying unescaped strings rather than using the normal double quote string syntax. This syntax begins a string with the two character sequence <" and ends a string with ">.
For example, the following specifies a complete definition for a module:
module by_itself {
int+read+notify a;
int+write b;
cxxmethod+notify_inst update = <"
if (a > b) b = a;
return(1);
">;
};
You can use this mechanism to define a module that calls a subroutine that you define elsewhere. The following example references a user-written function in the body of the source code for this module.
module subroutine_call {
int+read+notify ip1, ip2, ip3;
int+write ret_val;
cxxmethod+notify_inst update = <"
ret_val = user_subroutine(ip1, ip2, ip3);
if (ret_val > 0) return(1);
return(0);
">;
};
Since C++ requires a definition of every function before it can be used, you need to indicate to AVS/Express which header files include this definition. Do this with the cxx_hdr_files property. For more information on this capability, see Section 5.9.12, Managing dependencies on other code [page 5-74].
5.6.2.3 Detaching cxxmethod from source generation
If you do not want to use the src_file property mechanism to specify source for a particular method, you must set the use_src_file property to 0 on the method, the module, or the library that contains the module.
You must specify to AVS/Express some way that it can get a definition of the appropriate method or methods:
1. Through the link_files property. This allows you to specify a pre-built library or a pre-built .o file or a combination of both.
2. Through the cxx_src_files property. This allows you to specify one or more C++ source files, one of which contains the definition of the necessary method.
5.6.3 Example C++ module
Here is a simple example of an AVS/Express module written using the C++ API. To understand the details of how data is manipulated with this example, refer to Section 5.9, Manipulating AVS/Express objects using the C++ API [page 5-61]. This example implements a simple quadratic equation solver. It takes three parameters, a, b, and c, which are the coefficients to a simple quadratic polynomial. If there are any defined roots for the specified coefficients, the values are placed into the parameters r1 and r2 and the status message says: "Roots are real". If there are no defined roots, r1 and r2 are set to 0 and the status message says: "Roots are imaginary."
Each AVS/Express module consists of two parts, the AVS/Express object interface definition (usually either interactively edited in the Network Editor or written directly using V) and the source code for the module. The V definition for this module is:
module EXquad_solver<
src_file="quad_mod.cxx", // specify file containing module src
out_hdr_file="quad_gen.h" // specify file to place generated class
> {
float+read+notify+req a, b, c;
float+write r1, r2;
string+write status;
cxxmethod+notify_inst+req update_roots;
};
Important notes about this V code:
- The method update_roots runs whenever the object is instanced because notify_inst is set on the method. This allows the module to work properly if it is instanced with pre-defined values for a, b, and c.
- The update_roots method runs whenever the parameters a, b, or c are changed because the notify flag is set on them.
- The req (an abbreviation for required) flag is set both on the update_roots method and on the parameters a, b, and c. This combination prevents the method from running unless a, b, and c have defined values. This is true even for the instance event.The req attribute applies to instance events as well as value changed events.
The C++ code for "quad_mod.cxx" referenced above is
int
EXquad_solver::update_roots(OMevent_mask event_mask, int seq_num)
{
float op = b * b - 4 * a * c;
if (op < 0) {
status = "Roots are imaginary";
r1 = r2 = 0;
}
else {
op = sqrt(op);
float div = 2 * a;
if (div != 0.0) {
status = "Roots are real";
r1 = (-b + op) / div;
r2 = (-b - op) / div;
}
else {
status = "Roots are infinite";
r1 = 0;
r2 = 0;
}
}
return(1);
}
The variables a, b, c, r1, r2, and status are part of the C++ object that is an implicit argument to this method. These values could also have been referred to as
5.7 Adding modules using the FORTRAN API
AVS/Express provides a FORTRAN API for accessing the Object Manager. The API includes routines for getting and setting the value of any AVS/Express object, navigating the object hierarchy
Here is an object that averages two numbers:
module ave {
float+read+notify num1;
float+read+notify num2;
float+write res;
fmethod+notify_inst upd = "ave_upd";
};
It performs its processing by calling the FORTRAN function ave_upd.
Notice that upd's type is fmethod. This tells AVS/Express that you intend to use the FORTRAN API.
You need to specify some code management properties so that AVS/Express can tell what code is needed to define the methods that you write. You can specify these properties to point directly to the source or you can point the system at a set of object files or archived libraries.
You should also be aware of what process (i.e., executable) that your module is going to be compiled into. The process is specified with the process property which can either be set directly on your module or on the library that your module is placed in.
For example, you copy module ave into Templates.USER. USER already provides the process property for compiling the function into the user process. In this example, you need to add the src_file property and, since USER also sets build_dir to "user", you should set the build_dir property to your own build directory.
module ave<src_file="ave.f", build_dir="my_build_dir"> {
float+read+notify num1;
float+read+notify num2;
float+write res;
fmethod+notify_inst upd ="ave_upd";
};
You can now compile the process containing this object by selecting the object in the Network Editor and selecting Project->Compile. If the source file does not exist, AVS/Express generates a template source file, which you can then edit. The source file will be placed in: <your_project_dir>/my_build_dir/ave.f.
When called at execution time, the target function receives three arguments: the object id of the group, the event_mask indicating the events that triggered this method and a sequence number.
Through calls to the FORTRAN API, the function gets and sets the values of the module's subobjects.
If any of the inputs are not set, the function returns a 0 value. A 0 value indicates that the module has failed. If a module has failed, any modules that have required dependencies on the outputs of this module will not run until this module has succeeded.
If the module succeeds, it returns 1 for success.
C
C Source for fortran function ave_upd
C
integer function ave_upd(obj_id, mask, seq_num)
include 'avs/omf.inc'
integer obj_id(OIDSIZ)
integer mask
integer seq_num
integer name
integer num1_id(OIDSIZ), num2_id(OIDSIZ), res_id(OIDSIZ)
double precision num1, num2, res
ave_upd = 0
C
C Get the ids for each of our 3 arguments, num1, num2 and res
C Since we only access the first two values, we can use the flag
C OM_OBJ_RD. Since we modify the 3rd one we have to use OM_OBJ_RW.
C
name = OMFstr_to_name('num1')
if (OMFfind_subobj(obj_id,name,OM_OBJ_RD,num1_id) .NE. 1)
& return
name = OMFstr_to_name('num2')
if (OMFfind_subobj(obj_id,name,OM_OBJ_RD,num2_id) .NE. 1)
& return
name = OMFstr_to_name('res')
if (OMFfind_subobj(obj_id,name,OM_OBJ_RW,res_id) .NE. 1)
& return
C
C Get the double precision floating point values for num1 and num2
C Express always returns values in double precision format
C
if (OMFget_real8_val(num1_id,num1) .NE. 1) return
if (OMFget_real8_val(num2_id,num2) .NE. 1) return
res = (num1 + num2) / 2
if (OMFset_real8_val(res_id, res) .NE. 1) return
C
C Set return status to success
C
ave_upd = 1
return
end
5.8 Manipulating AVS/Express objects using the C API
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 is implemented with a library called the OM library. All of the routines in this library are prefixed with the letters OM. This section defines some basic concepts used by the OM library and provides a high-level description of basic operations.
To get detailed information for any particular OM routine see the appendix "Object Manager Application Programming Interface".
5.8.1 Understanding OM data structures
5.8.1.1 Object ids
Almost all of the calls in the OM library operate on the OMobj_id data structure. This is a handle to an AVS/Express object. The OMobj_id is a network-transparent data structure. In other words, it can be passed from process to process, machine to machine without change. It is implemented as a structure containing two 32-bit values. One value contains the process id that the object is located within. The other value is an identifier to the object within that process. This structure is generally passed around by value like an ordinary pointer.
A null object id exists called OMnull_obj. Some routines return this value when they encounter an error condition. You can use a macro to test for a null object:
Since an OMobj_id is a structure, you cannot compare two OMobj_ids using the `==' operator in C. To compare two ids for equality, you must use this macro:
5.8.1.2 Object name
Many routines use the OMobj_name data structure. This data structure has a 1-1 correspondence to a specific string. You can get an OMobj_name from any string using the routine:
and given an OMobj_name, you can get the string with the call
The string returned from OMname_to_str should be treated as read-only storage. You should not free this string or reuse the memory it points to. Two OMobj_names can be compared for equality using the `==' operator of C.
5.8.2 Understanding return values
Status returns from OM routines fall into two categories with only a few exceptions:
Routines that violate these rules explicitly state the nature of their return values in the documentation.
For routines returning an integer, a value of 1 or the constant OM_STAT_SUCCESS is returned when the routine succeeds. If the routine failed with a soft error such as "an unset value", the routine returns 0 or OM_STAT_UNDEF. If the routine returns a hard error because of an invalid object id or because the operation was invalid, a -1 or OM_STAT_ERROR is returned.
For routines returning an OMobj_id, a value of OMnull_obj is returned upon failure. You should use the OMis_null_obj macro to test for this case.
5.8.3 Using mode arguments
Many of the OM routines take a mode argument as their last argument. This mode argument alters the behavior of the routine. The mode argument specifies a list of flags that are combined with the logical OR operation. The default use of the routine is obtained with a mode value of 0 which indicates no optional modes. There may not be a mnemonic constant that corresponds to this value of mode. The values for the mode arguments for each routine are described in the documentation for each routine.
5.8.4 Accessing the object hierarchy
One of the main operations performed in AVS/Express is to traverse the AVS/Express object hierarchy. A single set of routines traverse the object hierarchy allowing you to find objects in a library, parameters in a module, modules in a macro, etc.
5.8.4.1 Accessing the root objects
There are three ids stored in global variables to access the base points of the object hierarchy:
5.8.4.2 Moving up the object hierarchy
The main way to go up the object hierarchy is using OMret_obj_parent:
This routine returns the id of the parent object of the obj_id argument. If this object has no defined parent, OMnull_obj is returned. This will be true for dangling objects that have been partially deleted, objects that or were improperly created without a valid parent object, or when the object is the Root object.
There are two main ways to go down the object hierarchy. You can either find subobjects by name, or you can go through the subobjects of a parent one at a time in an ordered list.
5.8.4.3 Finding subobjects by name
If you know the name of the subobject you want to access, you can use one of the two routines:
OMobj_id
OMfind_subobj(OMobj_id parent_id,
OMobj_name sub_obj_name,
int mode)
OMobj_id
OMfind_str_subobj(OMobj_id parent_id,
char *path_name, int mode)
The mode argument for both of these calls should be OM_OBJ_RW unless you are guaranteeing that no changes will be made to the object returned. In this case, you can use OM_OBJ_RD. A value of OM_OBJ_RD can prevent subobjects from being created that are inherited from a base template. In this case the object returned will have the base template as a parent, not the object specified with parent_id. When in doubt, use the flag OM_OBJ_RW.
If the specified subobject is not found or another error occurred, OMnull_obj is returned for both of these calls.
OMfind_subobj will return an immediate subobject of the object specified by parent_id with the given name specified by the OMobj_name 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);
OMfind_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. It can also go through array values if the pathname has [n] specified. These are valid string values for the pathname 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);
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 the parent_id is a library with the global attribute set. For example, with the V definition:
With the id of the root object in the variable OMroot_obj, the call:
will return the id of the UIwindow object because both the Templates library and the UI library are global. To override the searching of global libraries, you can OR in the flag OM_FIND_NO_LIBRARIES into the mode argument of the OMfind_subobj or OMfind_str_subjobj functions.
window_id = OMfind_str_subobj(OMroot_obj, "UIwindow" ,
OM_OBJ_RW | OM_FIND_NO_LIBRARIES);
// window_id will be OMnull_obj in this case!
5.8.4.4 Traversing subobjects as a list
You can also traverse subobjects as a list. To determine how many subobjects a particular parent has, use the call:
This routine takes the id of a hierarchical object like a library, macro, module, group, etc., and finds the number of immediate subobjects contained in the hierarchical object. The return value of this routine is 1 for success, 0 if the parent_id is a link object that does not have a current value, and -1 if the object specified does not define any subobjects.
To get the OMobj_id of an individual subobject, use the call:
The subobjects of an object are specified in an ordered list. This routine returns the n'th subobject in the list where n is specified by the which parameter. The id of the object is returned in the child_id field. The mode flag should be OM_OBJ_RW unless you know that you will not make modifications to the object. If you use the mode as OM_OBJ_RD, you may get the subobject from a derived superclass of the object. In this case, the parent object of the child returned will be the superclass, not the same as parent_id. Use OM_OBJ_RW if you are in doubt about which mode to use.
If you use this routine on an object that is a group, or module that has its reference mode set to `&', or define as an array of objects, these routines return the ids of the subobjects defined in the template object, not the ids in any of the values. For example, if you have a group object defined in V like:
When called with parent_id set to the ref object and an index of 0, the OMget_array_subobj routine returns the id of the object ref.a, not value.a. If you want to get the subobjects of the value, you must use the routine OMget_obj_val to get the value and then use the id obtained from this routine in the OMget_array_subobj routine.
5.8.5 Determining when a parameter has changed
A module written with an omethod gets a sequence number parameter as the third argument to the module's callback function. You can determine whether any parameter has been modified since the last time that this method ran by using this sequence number as the argument to the function:
This function returns 1 if this parameter has changed and 0 if it has not. A value of -1 indicates an invalid parameter_id argument.
Here is an explanation of how this process works. Whenever the OMpush_ctx call is used, the system increments a global sequence number. Since an OMpush_ctx call proceeds the execution of each method, each invocation of each method is guaranteed to have a unique sequence number. When the value of a parameter is changed, the parameter is marked with the current sequence number.
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 that has a sequence number that is greater than the sequence number passed in the third argument has changed. You can use the routine
to return the sequence number for a selected object. 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.
5.8.6 Setting and getting data values
The C 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 operate in general.
- A value of 0 or OM_STAT_UNDEF is returned when an attempt is made to get the value of an object that is not set.
- If you request a different data type than the type which is stored, C-style casting rules will be employed to convert the value. Floating-point numbers will be truncated to integers, shorts promoted to integers, etc.
- Connections are always followed by get operations and then usually by set operations. The only case where a set operation will break a connection is when the connection is to a read-only object such as an arithmetic expression.
The first argument to the basic routines that set and get data values is the OMobj_id of the primitive data object. For example, the routine to get an integer value from an object is specified like
If an error is returned, the returned_val memory is not changed.
When using this routine from a module, you first have to get the OMobj_id of the parameter from the OMobj_id of the parent. This is done with the OMfind_subobj routine. For example, given the module V code
the C source code for my_module_update routine would look like
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);
}
Many data set and get routines provide a convenience version of the routine that performs both the OMfind_subobj and OMget_int_val calls in one step. For OMget_int_val, this routine is specified as
This allows a more succinct version of the above module to be written:
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);
}
5.8.6.1 Scalar primitive values
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
This routine returns a pointer to the string value of the object specified. If the obj_id has an unset value or no defined string value, a NULL value is returned. All valid strings returned are terminated with a NULL character.
There are two ways that the string memory is managed:
- 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, the buf_size argument should specify the size of this buffer. 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
OMobj_id obj_id;
char buf[128], *buf_ptr;
obj_id = ...
buf_ptr = OMret_str_val(obj_id, buf, sizeof(buf));
The pointer data type is used to pass around 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, etc. but you sacrifice some flexibility. Pointer objects have the following restrictions:
- You are unable to access or set pointer 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. The pointer's value can change or not change. You are unable to send more detailed events that indicate only a particular structure member has changed.
5.8.6.2 array primitive values
There are three categories of primitive arrays in AVS/Express:
Each of these are accessed with different routines.
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.
Pointer arrays are not supported in AVS/Express.
You can either access raw data arrays in their entirety or you can deal with them a piece at a time. When operating on arrays a piece at a time, AVS/Express must copy the array information that you are getting or setting. When operating on the entire array, AVS/Express tries to avoid copying the array memory by giving you the AVS/Express array object's array pointer directly. There are some cases where this is not possible and a copy must be made even when the user code is operating on the entire array. For example, AVS/Express must make a copy when type conversion must be performed (e.g. an integer to floating-point conversion) or when the user code being executed is in a different process than the AVS/Express array object.
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 that is of the appropriate size for this operation. The data is either copied out of or into this memory by an AVS/Express sub_array call.
To perform a sub_array call (either set or get) you must provide three pieces of information:
- the dimensions of the array that you are operating on
- 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 this operation
There is a different sub_array routine for each data type supported by AVS/Express. The list of these routines is
The prototype for the OMset_sub_iarray routine is:
The prototype for each sub_array routine is the same except for the data type of the array pointer argument. The ndim argument specifies the number of dimensions in the array. The dims argument is an array of ndim integers and 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, this is 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 min_rng values should be greater than or equal to 0. The max_rng value should be less than or equal to the corresponding member of the dims array.
- Note: The max_rng value is not included in the values that are returned. This is different than the V syntax for referencing regions of arrays in which the maximum dimension is included.
The array_ptr should point to a piece of memory that contains enough space for
of the appropriate data type. For the set_sub_array calls, this memory will be copied into the appropriate locations of the AVS/Express array object. For the get_sub_array calls, AVS/Express returns the requested values in this memory.
There are two basic strategies for operating on the entire array. If 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 strategy works for all array operations: read-only, write-only, and read-write. The routines that operate in this mode are
These routines take an array mode which specifies how you plan on accessing the memory pointer returned to you. There are four choices for this mode:
- Use this mode if you plan on having only read-only access to the array. It is important that you do modify the returned array since AVS/Express may actually return you a pointer to the same memory that it is using to store the array.
- If you are only going to read the array but may need to modify the data in some situations, you can use this mode to force a private copy to be made.
- Use this mode if you plan on reading and writing the array. This call ensures that any existing values in the array are preserved and, when you free the array, triggers events to any methods that have requested to be notified when this array changes. If there is no existing definition of the array when you make this call, the array is allocated and initialized to 0.
- Use this mode if you plan on writing 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 initialized yet, AVS/Express does not have to initialize the data to 0.
If you use any one of these routines, you must free the array when you are finished with it using the ARRfree routine. This free call not only prevents memory leaks, but also signals that you are finished with the array. If you used OM_GET_ARRAY_WR or OM_GET_ARRAY_RW, any methods that requested notification on the array will have their events queued when the ARRfree call is made.
If you are modifying an array who's final dimensionality is not defined before you need to start modifying the array, you can use the OMset_array routine. This routine provides flexibility in how the array is allocated and freed by AVS/Express. To use this routine, you define the array using memory that you manage and use the OMset_array routine to tell the AVS/Express object about the new value for the array. At this time, any methods that have requested notification on the array have events queued.
There are three modes for how the memory passed in for this array is treated:
- A distinct copy is made by AVS/Express. When the routine is finished, you are free to do whatever you want with this pointer. AVS/Express does not free the pointer for you.
- This mode prevents unnecessary copying of the array. In order to use this mode, you must have allocated the array either using the ARRalloc routine or the C malloc facility. After the routine returns, you should not free the array yourself or modify the contents further.
- This mode will also prevent unnecessary copying of the array but places different restrictions on the way in which the memory is used. 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 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 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 may depend upon this array.
5.8.6.3 Setting values to the "unset" state
The basic primitive set commands do not provide a mechanism to delete the value of an object so that it returns to the unset state. You can do this with the call:
This call also breaks any existing connections that this object might have.
5.8.7 Creating and destroying objects
5.8.7.1 Creating objects
AVS/Express uses the prototype model for object creation. In this model, types of objects, called templates, and instances (active objects) are implemented with the copy operation. You copy an existing template object to create a new template object. You also copy an existing template object when you want to create an instance of any object. AVS/Express does not support copying of 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 only invokes the methods of instanced objects.
To create a new object from a template, you need to know the pathname of the template object. This name is usually of the form
or for objects that are stored in a global library, it would simply be
It is important not to use any global libraries in the pathname specification for an object since developers may change the names of these libraries. For example, the object clamp is stored with the pathname MODS.Filters.clamp. Since the Filters object is global, you should only specify the pathname of the object as: MODS.clamp. This gives you the flexibility to move the clamp object from the intermediate Filters library without your code having to change.
Templates in global libraries are defined for the common base types such as group and int so you can create these objects using a pathname equal to the base type name.
The next step is to determine the parent for the object you are going to create. 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, do you want this object to be deleted automatically when your module is deleted? If so, you may create a group within your module to serve as a destination for dynamically created objects.
Do you want the Network Editor to be able to access this object? This may affect your choice of a destination for the new object. You can set the NEvisible property to prevent this object from being edited in the Network Editor even if it is created as a subobject of an object that the Network Editor is displaying.
The easiest routine for creating new objects is OMcreate_obj_from_path. This routine is defined as:
The parameters for this routine are the pathname for the template object as discussed above, a character string that contains the name for the new object, and the OMobj_id of the parent object. This routine returns the id of the object that is created. If an error occurred, perhaps in finding the template object specified, OMnull_obj is returned.
If an object already exists with the name specified by the new_name parameter, this routine chooses a unique name of the form: <new_name>#1 etc.
If you created your object in a hierarchy that will be saved, you should determine whether or not you want this dynamically-created object to be saved. If not, you must set the nosave attribute on the object. See:
5.8.7.2 Creating objects through V
To create an object as an array with complex dimensions or properties, it is sometimes easier to generate a buffer that contains the V definition of the object and then to use the V parser to create the object. You can do this with the OMparse_buffer command. For example
char *buf = "int bar; int foo[bar*2];";
OMobj_id parent_id;
parent_id = ...
status = OMparse_buffer(parent_id, buf, 0);
This example creates two int objects bar and foo that will be placed into the parent object specified by parent_id.
5.8.7.3 Destroying objects
You can destroy objects using the routine OMuser_destroy_obj. This routine first breaks all connections to objects that are subobjects of the object being deleted, then destroys the object specified. The destroy callbacks for any objects that are deleted in this operation are called during this routine.
5.8.8 Manipulating connections
You can make and break connections using the C API. For objects that can only have a single connection (i.e., scalar primitive objects, scalar group references, and links), the routine OMset_obj_ref is the most useful. It replaces any existing connection with the specified new object connection. It can also be used with array objects when connecting one array object to another array object. The routine is declared as
Calling this routine is equivalent to the V statement
To disconnect an object, the to_id argument can be OMnull_obj.
The mode argument for this routine is typically 0. If you specify a mode value of OM_OBJ_REF_RDONLY, you make a connection that only allows get operations through the connection. Any set operations on the from_id object break the connection.
To manipulate objects that can have arrays of connections, you can use the routine
to add a new connection to the array from_id to the scalar to_id. While this routine can be used with a scalar object as the from_id argument, it returns an error if the object already has an existing connection. The mode argument to OMadd_obj_ref is usually 0. A value of OM_OBJ_REF_QUERY determines whether the connection can be made without actually making it. In this case a 1 is returned if the connection is valid and a 0 if the connection is not valid.
A single specific connection can be deleted with the routine:
This routine deletes the connection from from_id to to_id. The mode argument is always 0.
5.8.8.1 Following connections, pointers, and references
If your intention is to be able to traverse the connections as they would be displayed in the Network Editor, you should use the two routines OMget_num_refs and OMget_array_ref.
OMget_num_refs finds the number of connections from a specified object. For a scalar object, this is either 0 or 1. For an array object, the number of connections is 0 if there are no connections for the object, 1 if the object is connected to another array object, and a positive integer greater than 1 if the object is connected to a list of scalar objects.
The following table shows the correspondence between a V statement and the number of connections that the object would have as reported by OMget_num_refs on the from object:
The OMget_array_ref command finds the destination of a specified connection. It takes an integer index argument which should be less than the value supplied by the OMget_num_refs call.
When dealing with arrays of references to groups, if you use the OMget_array_ref call to access the values of each individual group, your code will not work properly for the case where the group is defined either as a single connection to an array of groups or is not defined as a local array. You should use the OMget_array_val routine in this situation.
Each AVS/Express object has an object value. Primitive objects define the object value differently than group objects. The object value for an object can be determined from V using the $obj_val command, or from the C API routine OMget_obj_val.
For primitive objects (like int, float, etc.), the object value is defined as the last object in a chain of simple connections. This table shows a few examples of how this rule is applied:
If a group object is not a reference or a pointer to a group (i.e., is not defined with the `&' or `*' reference modes), the object itself is the value. If the group object is a reference, its value is the first non-reference found in the chain of connections. The object value rules apply identically for `&' and `*' reference modes and are the same for both scalar groups and arrays of groups. This table shows how these rules are applied to different V statements:
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).
5.8.9 Managing arrays of groups, modules, macros
AVS/Express is limited to operating on one-dimensional arrays of groups, modules, and macros. The rules for how you manage arrays are identical for these three different object types. This section describes the rules using the group type as an example but in all cases, group can be replaced by module or macro.
5.8.9.1 Accessing arrays of groups
The routine OMget_array_size finds the number of groups defined in an array. This routine returns 0 if the array dimensions are not set or if the group's value is not set. The routine OMget_array_val takes as arguments the id of the array of groups and an index into the array and returns the id of the specified element.
These routines operate properly for the three different ways of specifying an array of groups:
In some cases, this routine returns the id of a name-link object, not the id of a group itself. You may want to use the OMget_obj_val routine on the id returned to skip through the name-links. This allows you to deal properly with the error case when the specified element of the array is not set which would occur with the following V definition:
In this case, the OMget_array_size function returns a size of 1 and the OMget_array_val routine returns the id of the v1 object, but since the value of v1 is not set, you should not process it as a valid object. The OMget_obj_val routine detects this case and return a 0 status.
5.8.9.2 Creating arrays of groups
You can create arrays of groups from the C API in two different ways:
With an explicit array of groups, the individual groups are created for you automatically as the size of the array is changed. With a reference to an array of groups, you must create and destroy each object in the array individually.
An explicit array of groups is defined in V like
When you set the value of the nfoos object, it automatically resizes the foo array. If you make nfoos smaller than it was before, the groups at the end of the foo array are destroyed. If you make nfoos larger than it was before, new groups are automatically created from foo's template and placed at the end of the array. In the above example, when new groups are created in this way, they 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 your explicit array of groups is using the V syntax
Using this syntax, you should set the size of the foo array with the OMset_array_size call.
Defining an explicit array of groups has two limitations:
- You can only create groups of a homogeneous type. Since new array values are only created from the template defined from the array, there is no mechanism for having two entries in the array defined from different template objects.
- New array objects are always added at the end and deleted from the end as the array changes. There is no mechanism to insert an array value or delete a specific array value.
To create an array of groups you have more control over, you can define your array of groups as a reference to an array of groups. With this technique, you must handle the creation and destruction of the individual array objects and you must manually insert them into the right location of the array of group references. Since the process of creating/destroying and inserting/removing are separately performed, you can also rearrange the objects in the array.
5.8.9.3 Creating new array elements
To create a new array object, follow the instructions in:
As indicated in this section, you must have a destination object that stores the newly created groups. To contain these objects, you may want to create an empty group object in your module or somewhere in your application to serve as a parent to manage these array objects. You may or may not want to set the nosave attribute on objects that you create to prevent them from being saved and restored with your application.
Make sure that the objects that you create match the declaration of the template for your array of groups. If not, when you attempt to add them to the array of groups, an error will occur.
There are two ways that you can define your array of groups. Which one you choose affects how objects are inserted and removed. It is easier if you define your array to have "unspecified" dimensions. Using this mechanism, 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. Your definition would then look like
With this syntax, you can perform the following operations on your array:
- To add a new entry onto the end of the array, use OMset_array_val with the index argument set to the flag OM_ARRAY_APPEND.
- To insert a new entry into your array, use OMset_array_val with the index argument set to the index you want your new entry to become after that operation.
- To remove a specific object, find the object with OMget_array_ref and remove it with OMdel_obj_ref.
While it is possible to add the same object to the array more than once, it is only possible to remove the first occurrence of that object in the array.
If you specify explicit dimensions for your array object, you can use the OMset_array_val routine to replace a specific entry in your array.
5.8.10 Manipulating properties and attributes
Properties and attributes attach additional information onto an object's structure.
For more information on properties and attributes, see Section , Attributes [page 2-12] and Section , Properties [page 2-12]
Properties are identified in these calls using the OMobj_name structure. Some of the standard system properties are predefined in the include file <avs/om_att.h>. You can use these global definitions or you can just call the OMstr_to_name function to convert a string to a property identifier.
Properties can 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.
Use this table to determine the routine to set and get properties for the various data types:
The Network Editor uses an array to define the NEportLevels property. This array has two integer values that specify the number of levels to export the port for both input and output. You can use the OMset_obj_iprop routine to set them both to a single value but if you want to set the number of input levels to a different value than the number of output levels, use the routine
To set the NEportLevels property, you would call this routine as
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 various attributes like read, write, notify, etc. You cannot define new attributes. Each attribute has a global variable that identifies that attribute in the include file <avs/om_att.h>. The variable name is of the form
Using the variable, you can query the state of the attribute with the routine OMget_obj_att. For example, to inquire whether the read attribute was set on an object, you use
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, use the routine OMset_obj_att.
5.8.11 Saving and restoring object descriptions
AVS/Express supports state save and restore in two different formats:
AVS/Express supports the save and restore of both of these formats using the C API.
5.8.11.1 Save using V language
To save an object description using the V language format, you specify the object id of the parent of the subtree of the AVS/Express object hierarchy that you want to save. AVS/Express saves text file that when loaded, recreates the object hierarchy. This operation is used by the "Save Objects" menu entry of the Network Editor.
This object file contains derived object definitions for each object in the file. For example, if you save an object "my_button" that was created from the template object UIbutton, the file is saved as:
In order to restore this file, it is required that any templates referenced by the file exist in the running version of AVS/Express. In the above example, the system must have a definition of the UIbutton object to load this file successfully.
The C API routine to save AVS/Express objects is the routine
If the file specified by filename already exists, it will be replaced unless the mode argument specifies the flag OM_WRITE_APPEND.
The mode argument also can specify bit flags to determine how to save the values of parameters in the object description. Parameter values are tagged with a state flag that indicates the three possible states that the value could be modified. These states are
Program state specifies values that are intrinsically part of the definition of the application. These include connections and values that are not modified during the course of execution of the application. The positions and sizes of widgets might fall into this category. These values are always saved by the OMwrite_obj routine.
User state flags values that are modified by a user of an application. These values are typically those values directly controlled by a user interface widget such as the filename parameter connected up to a file selection box.
Transient state values are modified by a module that can reproduce these values if the inputs to the module are saved. For example, the image data for a read image module are typically marked as transient state since it can be reproduced if the filename parameter is saved.
- If no flags are specified in the mode argument, program state, user state and transient state are saved.
- If the flag OM_STATE_USR is specified, only program state and user state values are saved.
- If the flag OM_STATE_PRG is specified, only program state values are saved.
For information about how to change the state mode when you are writing a module, see:
5.8.11.2 Reading V files
There are several different ways to restore files that contain the V language. The routine to read V statements from a file is
This routine reads the text V file specified so that objects defined in the file become subobjects of the object specified by the obj parameter. The mode argument should be specified as 0. This routine returns a 0 status if errors were encountered while reading the file. A -1 status indicates an invalid object id was specified. A 1 status indicates success.
To parse a buffer of V commands, use the routine:
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 object specified.
You can set an object's value from a V value string using the routine
The object specified gets its value set to the value string specified. The subroutine call
is equivalent to the V statement
Note that this mimics the = operator in V. If you want to mimic the => operator,use the routine OMparse_obj_ref. To get the V construct
To parse V commands from a stream, use the routine
This routine operates in the same way as the above two routines but treats the stream specified as an interactive stream of V commands specified by the fp parameter. This routine implements the V command processor (VCP).
5.8.11.3 Save using a binary file
An interface exists to save objects to a binary V file. There are two different types of binary V files. A simple binary V file contains the same information saved in a text V file. When saving an object that is derived from a template object, this file saves it as a named reference to the template object. To restore this file, the template object must exist with the same pathname. This type of binary V file is used by flibrary objects to create a binary description of the information stored in the V file specified by the libfile property.
A "complete" binary V file saves all of the templates needed to recreate the objects saved in the V file. This file is used by the "Save Compiled Project" feature of the Network Editor to create the binary V file for the compiled project.
Both of these binary V files are created by the same C API routine:
int OMbsave_objs(int nobjs, OMobj_id *obj_id_list,
int nex_objs, OMobj_id *ex_templ_list,
char *filename, int mode)
This routine specifies a list of objects to be saved into the binary V file using the nobjs and obj_id_list parameters. When the binary V file is stored, this list of objects are restored as peers in the AVS/Express object hierarchy so typically it makes sense for them to be peers in the AVS/Express object hierarchy when the save operation is performed. You should not specify an object that is the parent of another object in the list.
The filename parameter specifies the file to store the binary V objects in.
The mode argument specifies which mode to save these files in. A mode argument of 0 saves the objects in a binary V file that parallels a text V file. With this argument, the nex_objs argument should be 0 and ex_templ_list argument should be NULL.
A mode argument of OM_BSAVE_COMPLETE saves the objects and all required templates in a single binary V file. With this mode, the nex_objs and ex_templ_list can be used to specify additional templates that are added to the set of templates needed to define the objects being saved. When this mode is used, by default, the objects specified will be instanced when the file is loaded. If you want to override this option, add the flag OM_BSAVE_TEMPLS_ONLY to the mode argument.
5.8.11.4 Reading binary V files
to read in either a binary V file or a text V file. The objects in the V file are loaded as subobjects to the specified obj_id.
5.9 Manipulating AVS/Express objects using the C++ API
While C++ programmers are free to use the C API (the OM library) to manipulate AVS/Express objects, AVS/Express takes advantage of the features of C++ to provide a more intuitive interface. The library that implements this interface is called OMX.
Using the OMX interface, objects are accessed directly using C++ names to refer to AVS/Express objects of the same name. The C++ statement
assigns the filename parameter of the read_image module the string value "my_image.x". 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. The operator overloading feature of C++ is used to provide direct access to the data for AVS/Express objects that manage access to data (such as the "filename" parameter above).
C++ programmers can use OMX in two ways:
- You can use OMX to write new modules in AVS/Express. AVS/Express generates a C++ class that corresponds to the module that you create. All AVS/Express subobjects of your module are members of this C++ class.
- You can use OMX to generate C++ classes that represent AVS/Express objects. When you create an instance of the C++ class, the corresponding AVS/Express object is created. When you destroy the C++ instance, the AVS/Express object is destroyed. You can generate a C++ class for any group, module, or macro in AVS/Express.
When both importing and exporting code, the C++ programmer will have a C++ class that is associated with the AVS/Express object. In both cases, the member variables of the C++ object will correspond to the subobjects of the AVS/Express object. Once the class is created, the interface to using this class for both importing code and exporting code is the same.
5.9.1 Getting a pointer to the C++ object
When writing modules, you receive a pointer to your C++ object as the implied this argument to your method. For example, given the V code
the C++ code to set parameter_a to 1 is
In C++, you can omit the "this->" so the above could have been more succinctly written:
When you are exporting code from the AVS/Express environment, you need to get a pointer to the C++ object that corresponds to a particular AVS/Express object. There are two ways to do this:
1. If the object does not yet exist, create it using normal C++ constructs (usually using the "new" operator).
2. If the object already exists, get the OMobj_id (perhaps using the routine OMfind_str_subobj), then use the OMret_omx_ptr routine to get a pointer to an OMXgroup that corresponds to the object. The OMXgroup object has a method called ret_omx_ptr that returns a pointer to the desired object. This extra level of complexity is necessary as each object may be derived from several super classes each of which may have different pointers.
This 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 you use the C++ delete operator on that object, these actions destroy the AVS/Express object.
The constructor for the object takes an optional argument that determines what 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. But you may want to create a container object for the objects. A container object allows better debugging using the Network Editor. Do this by creating an application object, and then using this object as the first argument to the constructor.
If there is an existing instance of a "Field" object in the Network Editor with the pathname "SingleWindowApp.Field#1", get a pointer to a C++ object that controls this object with
// 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 the ret_omx_ptr method, 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. The C++ object 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. A 0 pointer is returned in case of an error.
5.9.2 Accessing values for AVS/Express subobjects
The values for AVS/Express subobjects can be accessed through member variables of the C++ class. The member variable hierarchy of the C++ class generally follows the AVS/Express object hierarchy one to one. There are several exceptions to this rule:
- Some AVS/Express subobjects are not represented in the C++ class because the "export=0" property is set on the subobject or due to the way in which the class is generated
- The member subobjects can be renamed using the cxx_name property. You can use this to remove name conflicts between AVS/Express objects and C++ names.
- The member objects may have an "_" appended if the AVS/Express name is a reserved keyword in C++.
Each AVS/Express object has a corresponding OMX class that implements the C++ programmer's interface to control that object. This list shows the mapping between the AVS/Express types and the OMX types:
- OMXint<-> int, byte, char, short
- OMXreal<->float, double
- OMXprim<->prim
- OMXprim_array<->prim[], char[], byte[], short[], int[], float[], double[]
- OMXgroup<-> group, module
- OMXappl<->application
- user defined group<-> generated C++ subclass of OMXgroup
- OMXgroup_array<-> group[]
- user defined group array<-> generated C++ subclass of OMXgroup_array
The following section describes how to use each of these different types of OMX object classes.
5.9.3 C++ interface to scalar data
Scalar data objects are implemented by a C++ class that is customized for their particular type. An integer is implemented with a C++ class called OMXint, for example. 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 attempt to assign a a value to an OMXint, the value is cast into an integer and calls a method to perform the assignment. You can use the C++ member objects in most cases where an ordinary integer would be used: in assignments, in expressions in comparisons, etc. For example, all of these operations are valid using the earlier example for foo.parameter_a:
One case where these objects cannot be used like ordinary values is when the program requires a pointer or a reference to the value. For example, you cannot do the following:
Another case where C++ may prevent this object from behaving identically to an int (compiler behavior varies from platform to platform) is when you must assign one OMX parameter to another. To ensure the portability of your code, you must always use an explicit cast in this case. For example, the following assignment statement would be appropriate for integer parameters:
For strings, use an assignment statement like this:
If a scalar byte, short, int, float or double AVS/Express object does not have a current value and an attempt is made to access the value, a 0 value is returned in its place. If you need to determine whether or not the object has a valid value, you can 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, etc. You can use a string object to produce a pointer to a null terminated character string. You should 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 assign a character string to an AVS/Express string object as well. 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.
5.9.4 C++ interface to array data
Arrays of ints and floats are not dealt with as transparently as scalar ints and floats. You cannot directly access them using the [] operator. There are two basic mechanisms to access arrays. One is most useful when you have defined the dimensionality of the array before the array operation begins, the other where the final array dimensionality may not be known until after the array is completely defined (you may be reallocating the array in a loop, for example).
If the array dimensions are defined before you need to access or modify any values in the array, use the ret_array_ptr method to get a pointer to the array. The ret_array_ptr method is defined as follows:
You must tell AVS/Express how you plan on using the array by specifying the mode argument to the ret_array_ptr method.
- If you are reading the array values only, use OM_GET_ARRAY_RD.
- If you are writing the array values only, use OM_GET_ARRAY_WR.
- If you are reading and writing the array values, use OM_GET_ARRAY_RW.
The size and type arguments are defaulted to NULL. Thus you can omit them for many uses of this routine (they are defined below).
If your V object is defined like
you are able to access this array like
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);
Whenever you are done with the pointer returned by the ret_array_ptr routine, free that pointer with the ARRfree routine. Failure to free this pointer not only results in a memory leak, but also prevents AVS/Express from propagating events that are registered on the array object.
The ret_array_ptr routine returns a NULL pointer if the array dimensions are not defined at the time the call is made. It is especially important to note that if your object is defined as
you must set nvalues before you try to get the pointer to the array. This is the correct order:
The following produces an error and a NULL pointer will be returned:
// WRONG... nvalues must be set before using array
int *my_array_ptr =
(int *)foo_ref->array.ret_array_ptr(OM_GET_ARRAY_WR);
nvalues = 3;
It is important that you match the type and size of the 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 as specified in the current definition of the AVS/Express object. If the AVS/Express definition leaves the type and/or size ambiguous as in this V definition
you can have the ret_array_ptr method return the type and size of the array with the defaulted second arguments. The size returned is the total size of the array (i.e., the product of all dimensions). The type returned is one of the constants: OM_TYPE_CHAR, OM_TYPE_BYTE, OM_TYPE_SHORT, OM_TYPE_INT, OM_TYPE_FLOAT, or OM_TYPE_DOUBLE. For example
//
// 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);
Using ret_array_ptr, the array is always returned with a data type that matches the way that the type is defined in AVS/Express. If you need to force the array to return a type that is different from the type specified in AVS/Express, you should use the method ret_typed_array_ptr. It is defined like
The mode argument specifies the same mode as ret_array_ptr. If the type argument is not OM_TYPE_UNSET, the array is forced to the type specified. If you provide a value OM_TYPE_FLOAT, a pointer to an array of floats is always returned. The conversion rules are the same as those used in 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 need to start operating on the array, you can use the set_array method. You build the array in memory that you have allocated and only when you are done do you give the array to AVS/Express. This method has several different modes for how it can manage memory with a goal of providing mechanisms that prevent unnecessary copies of the data.
The set array method takes several arguments. Its definition is:
The type argument should be one of OM_TYPE_CHAR, OM_TYPE_BYTE, etc. The array argument should contain a pointer to the array. The len argument should be 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 can be one of the following:
- Makes a copy of the values in the array. This is the default value if no mode argument is specified. You are free to reuse the storage pointed to by array immediately after this call. AVS/Express does not attempt to free the pointer from array.
- Indicates that the value specified in array was allocated with the "malloc" facility. With this value, the caller should NOT free the array. It is freed by AVS/Express when it is finished with the memory.
- Indicates that the memory pointed to by the array argument can be directly used by AVS/Express, if possible. AVS/Express makes no attempt 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.
5.9.5 C++ interface to group arrays
Group arrays in OMX are implemented by overriding the [] operator so that you can effectively treat the array of groups naturally in C++. For example, with a V specification of
you can reference the subobjects of the group array bar with
To reference the values of an array of groups, the dimensions of that array of groups must already be defined. If, for example, you have the following
you must set the ngroups value before trying to reference the array:
If you want to inquire the size of an array of groups (or any array), you can use the ret_array_size method. This method takes no arguments and can be used as follows:
5.9.6 Additional C++ methods
Many routines that are defined in the OM library, have OMX method equivalents. In general, these methods take the same argument list as the OM routines except they leave off the first OMobj_id argument. The object whose method is being invoked becomes the implicit first argument. In addition, some routines have default values specified for the arguments at the end of the parameter list. This makes these routines more convenient for you to use. For example, to call the OMpush_ctx routine using the OM library, you would use
To perform the same call with the OMX library, simply specify
The 0,0,0 values also happen to be the defaulted values for the push_ctx method so this same call can be more succinctly written
5.9.7 Using OM routines
All OMX object types implement the cast operator so that they can produce an OMobj_id. This makes it easy to use any OM routines using an OMX object as an argument directly. To call the OMret_obj_name function, for example, you can specify
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);
Include the explicit cast of OMX objects to OMobj_ids since some C++ compilers have difficulty determining how to do this automatically.
5.9.8 Determining when a parameter has changed
The changed method works in concert with the second argument to the user-written method associated with a module to indicate whether this object has changed since the last time that the method was called. It returns 1 if the method has changed, and 0 if it has not.
For example, with the V definition of a module
the C++ method you write could have this code to determine if 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);
}
5.9.9 Manipulating connections
The OMX operations 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 as the first argument. Since 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 which is defaulted to the value you want to use for normal connections. A C++ class generated from the V code
could connect object a to object b simply with
The set_obj_ref method replaces any existing connections with the new connection specified. It can be used to break a connection with
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. For the V code
you can add the object b to the list of connections in object a with
This creates a value expression for object a of
The del_obj_ref method deletes a connection from the source object to a specific destination object. You can delete the connection from object a to object b with
For more details on these routines, see the reference pages for the parallel OM routines OMadd_obj_ref, OMdel_obj_ref, and OMset_obj_ref.
5.9.10 Mapping AVS/Express object names to C++ class names
When AVS/Express generates a C++ class for an object, it must choose a name for that object that is unique across the system. AVS/Express object names are only guaranteed to be unique for the specific parent that they are contained within. To help avoid the problems that this may cause, AVS/Express prepends the library name onto the AVS/Express object name if the library is not a "global" library.
A global library allows its names to be seen from the peer level. The FLD library, for example, is global and thus to create a field from V you do not have to type
Since the FLD library is global, the C++ class name for the Field object is simply Field. If FLD were not global, the class name would be: "FLD_Field" (an '_' replaces the '.' in separating the library name from the object name).
The C++ name used for any AVS/Express object can be changed using the cxx_name property. This works for AVS/Express objects that become member variables, C++ class identifiers, or non-global libraries that are prepended to the class name for an object.
5.9.11 Managing C++ generated files
When AVS/Express generates a class description for an object, it places header code into the current value of the out_hdr_file property and it places source code into the current value of the out_src_file property.
If these properties are not set either directly on the AVS/Express object or on the library that contains the object, the default is to use the files <process>.cxx for source and <process>.h for header information. <process> is the process in which this object is defined (e.g., "express"). These files are placed in your project directory.
Except for very temporary projects, do not rely on the defaults since these definitions change from file to file as the process property on the object changes. This makes writing code that consistently includes these files problematic. You should therefore define values for these properties when defining a project of significant scope. If you specify the out_src_file property without a suffix, a ".c" file is generated if no objects contained have C++ references, otherwise a file with a ".cxx" file is produced.
All out_hdr_file's generated have ifdef __cplusplus constructs so that they can be included in C code as well as C++ code.
5.9.12 Managing dependencies on other code
There are a variety of situations in which a C++ class generated by AVS/Express can 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
- code specified as a string value to a cxxmethod
- code specified with the cxx_class, or the cxx_members properties
- references to other AVS/Express objects that have their definitions stored in a different 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 it on the module or on the library that contains the module and specify a string value that contains a list of file names:
These filenames are defined to be relative to the current value of the build_dir property (which again can either be set on the module or on the library that contains the module) or they can be defined to be relative to the current list of header directories. This list is by default set to include the top-level project directory and the include sub-directory 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, determining this dependency can be tricky. This dependency is generated by the definition of the AVS/Express objects, not by any user-written code. For example, two libraries are defined 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;
};
};
In this example, you have created a dependency between Bmod and the object Aobject. The library B needs to specify the cxx_hdr_files property so that B.h knows to include A.h before it can proceed.
This is particularly 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;
};
};
5.9.13 Importing existing C++ classes
You may have an existing C++ class that you wish to import into AVS/Express. You can do this by manually instancing this C++ class when your AVS/Express object is instanced, storing a pointer to the C++ object with the AVS/Express object, and deleting the C++ object when the AVS/Express object is deinstanced. In addition to requiring some significant programmer effort, this scheme breaks down when the AVS/Express object and the C++ class are sub-classed. AVS/Express provides a mechanism that automates this process.
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, the system automatically creates an instance of the C++ object and when the AVS/Express object is deinstanced, it automatically destroys it. When a cxxmethod is called, you can get a pointer to the C++ object.
So that AVS/Express can find the C++ class, the cxx_hdr_files and cxx_src_files properties should be set either on the module or the library containing the module. The properties should reference the file (or list of files) needed to define the C++ class.
Once in the update method, you get a pointer to the C++ object with the method ret_class_ptr. This method gets called with a single argument which is the string name of the class passed to cxx_class. It returns a "void *" which you can then cast into a pointer of the correct type.
For example, if you have a C++ class that implemented a quadratic equation solver, the header for this class might be quad.hxx:
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 object might be 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
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);
}
5.9.14 Constructing user classes
It is possible that the constructor to the C++ class you specified with the cxx_class property takes arguments. You can specify the parameter list to the constructor for your class with the cxx_class_args property. This property can reference variables defined as parameters to your AVS/Express object. The cxx_class_args property should take a string which is placed between the parentheses of the "new" operator in C++. It can take any valid C++ code that can be used in this context. If, for example, the class QuadraticSolver used in the example above had a constructor that took the 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);
};
where the constructor simply initialized the values of a, b, and c
QuadraticSolver::QuadraticSolver(float init_a, float init_b, float init_c)
{
a = init_a;
b = init_b;
c = init_c;
}
you specify the V for this object as
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:
5.9.15 Defining abstract user classes
When you associate a C++ class with an AVS/Express object using the cxx_class property, the system generates code to instance your class. If you are defining an abstract class (i.e., a class that cannot be instanced because its constructor is private), the generation of this code causes an error. To get around this error, set the cxx_abstract property to 1 so that AVS/Express does not generate code that attempts to construct your object.
5.9.16 Adding your own member variables
You can also add your own member variables to the generated C++ class with the cxx_members property. 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 can be referenced by the C++ code associated with the cxxmethod. For example, if you wanted to add a char * that only your code maintained, you write the V code
If you wanted to make sure that this variable was declared in a private section, you could add
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;
};
5.9.17 Exporting C++ classes
AVS/Express generates a C++ class definition automatically 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 programmatically create an instance of the object.
This is convenient particularly given the limitation in AVS/Express that each object can have only a single C++ class associated with it (not a severe limitation since a derived copy can easily be made to associate a different C++ class with an identical object).
You can force AVS/Express to generate a C++ class for a particular object using the export_cxx property. This property specifies one of four modes that control how the C++ class is generated for this object:
- 0 - Don't generate a C++ class for this object.
- 1 - Generate a C++ member for each AVS/Express subobject unless the property export is set to 0. This is the default for any module with a cxxmethod subobject.
- 2 - Generate a C++ member ONLY for AVS/Express subobjects that either have the NEportLevels property or the export property set to 2 or higher (i.e., so that they export themselves at least to their parent object).
- 3 - Generate a C++ member for each subobject that exports itself to this object's level either using the NEportLevels property or the export property.
Values 1 and 2 preserve the correspondence between the AVS/Express object hierarchy and the C++ members hierarchy. Value 3 promotes all exported descendants of the AVS/Express object to be 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, this is how the C++ class is generated for the following object for each of the modes:
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
};
};
class my_module ... {
OMXint my_exported_param;
OMXint my_param;
};
class my_macro ... {
my_module my_module;
};
A caveat to using mode 3 is that since the name of the leaf object is used at the top-level, V does not guarantee that these are 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.
Mode values of 1 and 2 preserve the AVS/Express class hierarchy in the generated C++ classes. If an AVS/Express object "B" is derived from an AVS/Express object "A", the C++ class corresponding to B will be derived from the C++ class corresponding to "A". For example, for the following V code
the corresponding C++ class hierarchy would look like
Thus in C++ an object of class B can be treated as an object of class A. This applies to export_cxx = 1 and 2. When using a value of export_cxx = 3, the C++ class hierarchy will not correspond to the AVS/Express object derivation hierarchy.
If a cxxmethod is associated with this object, a value for the export_cxx property of 1 is used to generate the C++ class unless overridden by this property.
You can set this 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).
Some important rules to follow when generating C++ classes for a library of objects:
- Make sure that you contain the code in the proper files using the out_hdr_file and out_src_file properties (see the section on "Managing generated files").
- Make sure that you have resolved dependencies on other C++ classes that the system may have defined
- Make sure that the name of the classes generated will not conflict with the names of any other software packages you are using.
5.9.18 Example of generated C++ classes
This example shows how you can use 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 feeds it some data, selects a contour level, and displays the output using a viewer object.
First you select the objects needed by the application. For this application, these objects are collected into a single library and given new unique names. This provides the ability to customize these objects without having to change the names referenced by the C++ code. It also will prevent conflicts from other applications that might create C++ classes for the standard AVS/Express objects.
The V code for this project is
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;
};
The properties set on the library are
- Since most of the objects this example wants to access from C++ code are already exported as ports, the export_cxx mode of 3 is the best choice. This creates C++ members at the top level that correspond to the AVS/Express objects that either have the NEportLevels property set or the export property set.
- These two properties specify where the system places generated C++ class definitions. The out_src_file 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.
- The process property is set to isolate these objects into a separate process. Usually the process property is used to define a process that contains objects that are instanced in the Network Editor. In this project the process is started by you, not by AVS/Express.
- This property indicates that AVS/Express should not include a default "main" for this process when it generates the makefile. Instead, the example supplies a main with the cxx_src_files property. It is important that you supply a C++ main if your process will be linked with C++ and a C main if your process is linked with C. AVS/Express automatically links with C++ if any C++ objects are referenced in the process.
- Because the objects referenced in this library depend upon the objects in the Field library, the cxx_hdr_files property must include the header file used by the Field library to contain its C++ class definitions.
- This property specifies the file that contains the definition of main and the code that creates objects using the C++ class definitions.
The objects defined in this library are
- This wrapper makes it easier to create a uniform scalar field. With this object, the C++ code using this object has to supply one array that defines the dimensions and another array that defines the data.
- This is a sub-library that contains two objects from the Data Visualization kit. These objects are defined in a sub-library 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.
- This object creates isolines for a 2D input field and an isosurface for a 3D input field.
- This object shows the boundaries of the cells for the input field.
- This object converts the field output from the EXiso and EXext_edge objects into an object that can be rendered by the EXviewer object.
- This object is derived from the standard AVS/Express object SimpleViewer3D. The only modification to this object is to export the back_col parameter from the View3D.View subobject. The value 4 indicates the number of levels of hierarchy between back_col and EXviewer. With this property exported, a C++ program can control the value of the back_col parameter.
The source code for this example is
// 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 our 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 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
}
5.10 Manipulating AVS/Express objects using the FORTRAN API
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. All of the routines in this library are prefixed with the letters OMF. This section defines some basic concepts used by the OMF library and provides a high-level description of basic operations.
To get detailed information for any particular OMF routine see Appendix D, Object Manager FORTRAN Application Programming Interface.
5.10.1 Understanding OM data structures
5.10.1.1 Object ids
Almost all of the calls in the OMF library operate on the object id data structure. This is a handle to an AVS/Express object. The object id is a network-transparent data structure. In other words, it can be passed from process to process, machine to machine without change. It is implemented as an array of 32-bit integers. Currently the size of this array is 2, but you should dimension this array with the constant OIDSIZ. To declare an object use the syntax:
5.10.1.2 Object name
Many routines identify objects by name. These object names are stored in an integer data structure. This data structure has a 1-1 correspondence to a specific string. You can get an OMobj_name from any string using the routine:
and given an integer with an object name, you can get the string with the call
integer name
character*20 name_str
C
name = OMFstr_to_name("string")
idum = OMFname_to_str(name, name_str)
The string returned from OMname_to_str should be treated as read-only storage. You should not reuse the memory it points to. Two integers returned by OMFstr_to_name can be compared for equality using the EQ operator of C.
5.10.2 Understanding return values
Almost all OMF routines return an integer status code. A code of OM_STAT_SUCCESS (or 1) means that the routine succeeded. A routine value of OM_STAT_UNDEF (or 0) means that an object has an undefined value. A routine value of OM_STAT_ERROR (or -1) means that the routine failed.
Routines that violate these rules explicitly state the nature of their return values in the documentation.
5.10.3 Using mode arguments
Many of the OM routines take a mode argument as their last argument. This mode argument alters the behavior of the routine. The mode argument specifies a list of flags that are combined with the logical OR operation. The default use of the routine is obtained with a mode value of 0 which indicates no optional modes. There may not be a mnemonic constant that corresponds to this value of mode. The values for the mode arguments for each routine are described in the documentation for each routine.
5.10.4 Accessing the object hierarchy
One of the main operations performed in AVS/Express is to traverse the AVS/Express object hierarchy. A single set of routines traverse the object hierarchy allowing you to find objects in a library, parameters in a module, modules in a macro, etc.
5.10.4.1 Finding subobjects by name
If you know the name of the subobject you want to access, you can use one of the two routines:
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)
Both of these routines return a subobject of the object par_id in the argument child_id.
The mode argument for both of these calls should be OM_OBJ_RW unless you are guaranteeing that no changes will be made to the object returned. In this case, you can use OM_OBJ_RD. A value of OM_OBJ_RD can prevent subobjects from being created that are inherited from a base template. In this case the object returned will have the base template as a parent, not the object specified with parent_id. When in doubt, use the flag OM_OBJ_RW.
If the specified subobject is not found or another error occurred as status of OM_STAT_UNDEF (or 0) is returned.
OMfind_subobj will return an immediate subobject of the object specified by par_id with the given name specified by the integer 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
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. It can also go through array values if the pathname has [n] specified. These are valid string values for the pathname argument:5.10.5 Determining when a parameter has changed
A module written with an fmethod gets a sequence number parameter as the third argument to the module's callback function. You can determine whether any parameter has been modified since the last time that this method ran by using this sequence number as the argument to the function:
This function returns 1 if this parameter has changed and 0 if it has not. A value of -1 indicates an invalid parameter_id argument.
5.10.6 Setting and getting data values
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 operate in general.
- A value of 0 or OM_STAT_UNDEF is returned when an attempt is made to get the value of an object that is not set.
- If you request a different data type than the type which is stored, C-style casting rules will be employed to convert the value. Floating-point numbers will be truncated to integers, shorts promoted to integers, etc.
- Connections are always followed by get operations and then usually by set operations. The only case where a set operation will break a connection is when the connection is to a read-only object such as an arithmetic expression.
The first argument to the basic routines that set and get data values is the OMobj_id of the primitive data object. For example, the routine to get an integer value from an object is specified like
If an error is returned, the ret_val integer is not changed.
When using this routine from a module, you first have to get the object id of the parameter from the object id of the parent. This is done with the OMFfind_subobj routine. For example, given the module V code
the C source code for my_module_update routine would look like:
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
Many data set and get routines provide a convenience version of the routine that performs both the OMFfind_subobj and OMFget_int_val calls in one step. For OMFget_int_val, this routine is specified as
This allows a more succinct version of the above module to be written:
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;
return5.10.6.1 Scalar primitive values
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. Thus the call to OMFget_real8_val should look like:
The routine OMFget_str_val is declared as
integer function OMFget_str_val(obj_id, retstr, maxlen)
integer obj_id(OIDSIZ)
character*?? retstr
integer maxlen
This routine returns the string value of the object specified by obj_id in the parameter retstr. The string passed to the routine should be of size maxlen. The standard error code is returned if the value is either unset or undefined. Here is an example of how to call this routine.
5.10.6.2 array primitive values
There are two categories of primitive arrays in AVS/Express that are accessible from FORTRAN:
Each of these are accessed with different 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 you can deal with them a piece at a time. When operating on arrays a piece at a time, AVS/Express must copy the array information that you are getting or setting. When operating on the entire array, AVS/Express tries to avoid copying the array memory by giving you the AVS/Express array object's array pointer directly. There are some cases where this is not possible and a copy must be made even when the user code is operating on the entire array. For example, AVS/Express must make a copy when type conversion must be performed (e.g. an integer to floating-point conversion) or when the user code being executed is in a different process than the AVS/Express array object.
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 that is of the appropriate size for this operation. The data is either copied out of or into this memory by an AVS/Express sub_array call.
To perform a sub_array call (either set or get) you must provide three pieces of information:
- the dimensions of the array that you are operating on
- 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 this operation
There is a different sub_array routine for each data type supported by AVS/Express. The list of these routines is
The declaration for the OMFset_sub_iarray routine is:
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 declaration for each sub_array routine is the same except for the data type of the array pointer argument. The ndim argument specifies the number of dimensions in the array. The dims argument is an array of ndim integers and 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, this is 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 min_rng values should be greater than or equal to 0. The max_rng value should be less than or equal to the corresponding member of the dims array.
- Note: The max_rng value is not included in the values that are returned. This is different than the V syntax for referencing regions of arrays in which the maximum dimension is included.
The array_ptr should point to a piece of memory that contains enough space for
of the appropriate data type. For the set_sub_array calls, this memory will be copied into the appropriate locations of the AVS/Express array object. For the get_sub_array calls, AVS/Express returns the requested values in this memory.
There are two basic strategies for operating on the entire array. If 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 strategy works for all array operations: read-only, write-only, and read-write.
The routines that operate in this mode are
These routines take an array mode which specifies how you plan on accessing the memory pointer returned to you. There are four choices for this mode:
- Use this mode if you plan on having only read-only access to the array. It is important that you do modify the returned array since AVS/Express may actually return you a pointer to the same memory that it is using to store the array.
- If you are only going to read the array but may need to modify the data in some situations, you can use this mode to force a private copy to be made.
- Use this mode if you plan on reading and writing the array. This call ensures that any existing values in the array are preserved and, when you free the array, triggers events to any methods that have requested to be notified when this array changes. If there is no existing definition of the array when you make this call, the array is allocated and initialized to 0.
- Use this mode if you plan on writing 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 initialized yet, AVS/Express does not have to initialize the data to 0.
Because these routines do not want to copy a potentially large array and because FORTRAN does not support pointers, these routines 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 onto the index of your array to dereference the array. This technique will not work if your compiler enforces array bounds checking.
If you use any one of these routines, you must free the array when you are finished with it using the ARRFfree routine. This free call not only prevents memory leaks, but also signals that you are finished with the array. If you used OM_GET_ARRAY_WR or OM_GET_ARRAY_RW, any methods that requested notification on the array will have their events queued when the ARRfree call is made.
Here is an example using 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)
If you are modifying an array who's final dimensionality is not defined before you need to start modifying the array, you can use the OMFset_array routine. This routine provides flexibility in how the array is allocated and freed by AVS/Express. To use this routine, you define the array using memory that you manage and use the OMFset_array routine to tell the AVS/Express object about the new value for the array. At this time, any methods that have requested notification on the array have events queued.
There are three modes for how the memory passed in for this array is treated:
- A distinct copy is made by AVS/Express. When the routine is finished, you are free to do whatever you want with this pointer. AVS/Express does not free the pointer for you.
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)
- This mode prevents unnecessary copying of the array. In order to use this mode, you must have allocated the array using the ARRFalloc routine. After the routine returns, you should not free the array yourself or modify the contents further.
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
- This mode will also prevent unnecessary copying of the array but places different restrictions on the way in which the memory is used. 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.
5.10.6.3 Setting values to the "unset" state
The basic primitive set commands do not provide a mechanism to delete the value of an object so that it returns to the unset state. You can do this with the call:
This call also breaks any existing connections that this object might have.
![]() |
![]() |
![]() |
![]() |