Developer's Reference |
|
This chapter describes how to integrate user code into AVS/Express using the Object Manager C, C++, and FORTRAN Application Programming Interfaces (APIs).
There are two different ways that new or existing user code can be integrated with AVS/Express:
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.
Any piece of code running in the AVS/Express environment can create, destroy, and modify instances of AVS/Express objects. This includes code in
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).
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:
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.
Once you have chosen the code integration technique you are going to use, the three basic steps to adding AVS/Express modules are:
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.
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:
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.
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.
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:
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
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].
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
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.
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.
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.
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:
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.
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
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:
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:
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".
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:
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.
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.
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++.)
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:
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.
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:
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:
The system generates the following prototype code in test.cxx you compile the process containing this module:
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.
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:
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.
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].
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:
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:
Important notes about this V code:
The C++ code for "quad_mod.cxx" referenced above is
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
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:
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.
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.
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.
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:
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.
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.
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.
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.
There are three ids stored in global variables to access the base points of 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.
If you know the name of the subobject you want to access, you can use one of the two routines:
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:
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:
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.
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.
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.
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.
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
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:
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:
This latter usage is typically implemented using code similar to
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:
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:
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.
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:
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:
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.
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:
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
This example creates two int objects bar and foo that will be placed into the parent object specified by parent_id.
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.
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.
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).
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.
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.
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:
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.
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:
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.
Properties and attributes attach additional information onto an object's structure.
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
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
To set or clear a particular attribute on an object, use the routine OMset_obj_att.
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.
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.
For information about how to change the state mode when you are writing a module, see:
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).
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:
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.
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.
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:
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.
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:
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
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.
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:
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:
The following section describes how to use each of these different types of OMX object classes.
Scalar data objects are implemented by a C++ class that is customized for their particular 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:
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.
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.
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
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:
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
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:
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:
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
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
Include the explicit cast of OMX objects to OMobj_ids since some C++ compilers have difficulty determining how to do this automatically.
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:
The OMX operations that deal with making and breaking connections parallel the OM routines with the same names:
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.
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.
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.
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
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:
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:
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:
The source for this object might be quad.cxx:
You define the interface to this object using the following V code:
The source for this method then looks like
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
where the constructor simply initialized the values of a, b, and c
you specify the V for this object as
The generated code that uses the cxx_class_args property then looks like:
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.
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:
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:
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:
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:
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
The properties set on the library are
The objects defined in this library are
The source code for this example is
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.
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:
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
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.
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.
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.
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.
If you know the name of the subobject you want to access, you can use one of the two routines:
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:
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.
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.
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:
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;
return
AVS/Express supports getting and setting the following types of scalar primitive values:
AVS/Express always returns real numbers in real*8 format, even if they are stored as float objects. Thus the call to OMFget_real8_val should look like:
The routine OMFget_str_val is declared as
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.
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:
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:
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.
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:
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:
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:
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.
![]() |
![]() |
![]() |
![]() |