![]() |
![]() |
![]() |
![]() |
8 Developing Modules
This chapter describes how to create and manage modules. It includes the following sections:
A module is an AVS/Express object that contains parameters and methods.
- Parameters define various aspects of a module's operation.
- Methods access and can modify parameter values.
You can develop user modules for use in AVS/Express applications either by adding new, custom modules or by modifying existing AVS/Express modules into custom modules.
- Note: Many examples in this chapter are written using the V command language. For a description, see Chapter 7, V and the V Command Processor.
AVS/Express provides two tools for adding new modules:
- The Add Module Tool in the Network Editor
- The V command language (see Chapter 7, V and the V Command Processor)
Modules run by executing functions written in C, C++, or FORTRAN. The execution of these functions is supported by a third tool: the Object Manager APIs, which are available in C, C++ , or FORTRAN versions (see Chapter 9, Object Manager APIs for details).
The Add Module Tool is a series of dialog boxes that guide you though the steps you need to complete in order to add your own module to AVS/Express. The dialog boxes provide a structured interface that make it easy to set the necessary properties for the module and the methods and parameters it contains. You can use the Add Module Tool in two ways:
1. You can create a module that encapsulates code you have already written and integrate it into AVS/Express. You can then connect the module into an AVS/Express network and use it with other objects.
2. You can build a module using the Add Module Tool and then ask AVS/Express to generate code for it. AVS/Express uses the structure you have created in the Network Editor and generates skeleton code describing it. You can then edit the code to function correctly.
- Note: To integrate your own code into AVS/Express, you must create your own project and work in it. For introductory information about projects, see Saving Your Work In Projects on page 3-36.
The Add Module Tool leads you through the following three steps to create a module:
1. Name your module and define one or more methods for it. You must create at least one method before adding parameters.
3. Specify the code management properties that control how your code is integrated into AVS/Express.
For more information about the Add Module Tool see the Getting Started manual for usage details, and the on-line help for panel details.
You can modify existing modules using the Object Editor or the V command language. Just as for new modules, support is also provided by the Object Manager APIs.
For more information, see Chapter 6, Managing and Editing Objects, Chapter 7, V and the V Command Processor, or Chapter 9, Object Manager APIs, respectively.
Any code running in the AVS/Express environment can create, modify, or destroy instances of AVS/Express objects, including code in the following:
- A user-supplied main process
- The user-interface callback function of a widget managed by the user's code
- A callback handler responding to a system event such as a time-out, poll, or event on a file descriptor
- An AVS/Express module's method
All three Object Manager APIs allow this flexibility.
As noted previously, a module contains parameters and methods. For example:
module AddImage {
Image+read+notify &Image1;
Image+read+notify &Image2;
Image+write outImage;
omethod+notify_inst update = "AddImage_update";
};
In this module, Image1, Image2, and outImage are parameters and update is a method.
Creating a custom module-whether as a new module or a modification of an existing module-involves setting up its methods and parameters and defining how they interact. Specifically, you must:
- Define the method(s) for the module and determine whether they will be executed when the module is instanced (created) and/or deinstanced (destroyed).
- Define the parameters for the module.
- Determine how each method relates to each parameter:
This section provides an overview of parameters and methods and how to relate them. Subsequent section provides detailed information on setting up target functions for your methods, and information on some special module-level topics.
Parameters define various aspects of a module's operation. 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, a parameter may even be another module.
Methods access and can modify parameter values. Each method in a module is associated with a specific target function-written in C, C++, or FORTRAN-that is executed when the method is triggered. Typical trigger events include instancing (creating) or deinstancing (destroying) the module or changing a parameter value. You can have different events trigger the same method: methods provide flags that indicate which event triggered the method. Also, when AVS/Express executes a method, you can check which parameters of the module have changed since the method was last executed.
There are three types of methods, corresponding to the three languages supported for target functions. The following table notes the method types, their associated languages, and their values (the value of a method is the name of its target function).
Table H-1
An optional string value that is interpreted as source code for the method (see Specifying the cxxmethod Type on page 8-18 for details)
For detailed information on setting up a module to use a particular type of target function, see Target Functions on page 8-15.
When you add a custom module that interfaces to C, C++, and/or FORTRAN code, AVS/Express generates additional code that binds the module 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 cxxmethod).
You must exercise caution when naming the target functions for your methods, in order that they do not clash with symbols defined by other components that you are using. To do this, simply add a unique prefix to the name of target function. As a general rule, avoid the following:
- Prefixes used for AVS/Express components; that is, OM, UI, FLD, DV, GD, GMOD, AG, PAL, IP, and AC
- Simple names that may conflict with standard C functions; for example, read, write, and select
If your names do cause conflicts, AVS/Express returns errors when it compiles the generated code.
When you define a method, you must set various attributes that control what events trigger the method and how the method interacts with the associated parameters.
- Method attributes operate on a per-module basis and are set only on methods.
- Parameter attributes control method behavior relative to specific parameters, and can be set on individual parameters, on the associated method, or on both.
As noted previously, the instancing and/or deinstancing of a module is a common trigger event for the module's method. To specify that this occurs, you merge notify_inst or notify_deinst attributes with the relevant method:
- The notify_inst attribute specifies that the method is the module's instance method; that is, AVS/Express executes the method's function whenever the module is instanced.
- The notify_deinst attribute specifies that the method is the module's deinstance method; that is, AVS/Express executes the method's function whenever the module is deinstanced. (Note that a module is always deinstanced before it is destroyed.)
In this example, instancing the containing module always triggers the update method:
Another common trigger event is a change in a parameter value. This is handled with the notify/nonotify parameter attributes rather than with method attributes (see the next section for details).
For a discussion of how event notification works, see Event Notification of Methods on page 8-11.
Whenever you define a method, you must set some basic parameter attributes that specify how the method interacts with the associated parameters. At minimum, you must specify settings for the read/noread, write/nowrite, notify/nonotify, and req attributes.
For descriptions of all of the module-control attributes you can set, see Module-Control Properties and Attributes on page 14-12.
The following list explains how to use these attributes.
- The read/noread and write/nowrite attributes determine whether the method reads and/or writes the parameter's value. The read and write attributes turn on the associated behaviors; noread and nowrite turn them off.
- How you set these attributes determines the execution order of methods when the same operation triggers more than one method. If Method A reads a parameter and Method B writes the same parameter, Method B is executed first. (For an example, see Controlling Method Execution Order on page 8-13.)
- If neither read nor noread is set on a particular object, it inherits the read/noread setting of its parent object. Similarly, if neither write nor nowrite is set on a particular object, it inherits the write/nowrite setting of its parent object.
- The notify/nonotify attributes determine whether a change in the parameter's value triggers the method. The notify attribute turns on the behavior; nonotify turns it off.
- If you set the notify attribute on a parameter for a particular method, changing the parameter's value queues an event to trigger the method. The event is delivered when the current operation is complete.
- If neither notify nor nonotify is set on a particular object, it inherits the notify/nonotify setting of its parent object.
- Another common trigger event is the instancing/deinstancing of the containing module. This is handled with the notify_inst/notify_deinst method attributes rather than with parameters attributes (see Setting Method Attributes on page 8-6 for details).
- For a discussion of how event notification works, see Event Notification of Methods on page 8-11.
- The req attribute specifies that the associated method is triggered only when the parameter has a valid value (req is an abbreviation of required). Unlike the read, write, and notify attributes, req is automatically off when unspecified and must be turned on explicitly: you must specify it for both the parameter and the method and, for hierarchical parameters, at each level in the hierarchy. (For an example, see Setting Parameter Attributes on Hierarchical Parameters on page 8-9.)
- Setting the req attribute on a parameter prevents the triggering of the associated method if that parameter does not have a valid value, regardless of whether the trigger event is an instancing or deinstancing or a change of parameter value. Leave this attribute unset only if you want your instance event to be called even if parameters are not valid.
By default, all of these parameter attributes are off. You can turn them on either on a parameter, on the method associated with the parameter, or on both parameters and methods.
The following example defines a module named test, with parameters named p1 and p2 and a method named update:
Observe that in this example, the notify and read attributes are set on the p1 and p2 parameters. The update method reads p1 and p2 and is notified whenever the value of p1 or p2 changes.
When you set a parameter attribute on a method, you overwrite the default behavior for the associated parameters; that is, each parameter takes the setting specified at the method level. This is a convenient way to deal with multiple methods.
Like the first example in the previous section, the following example V code defines a module named test, with parameters named p1 and p2 and a method named update:
In this case, the notify and read attributes are set on the update method. Again, the method reads p1 and p2 and is notified whenever the value of p1 or p2 changes. The result is the same, although the technique is different.
Another way to set parameter attributes on a per-method basis is using the parameter/attribute syntax illustrated in the following example:
When you specify attributes in this way, the method is only affected only by the parameters included in the parentheses. Parameters not included do not affect the method even if they have the notify, read or write attributes set. Although this parameter syntax is similar to C function parameter declarations, using it does not affect the way in which the target function is called (test_update in this example). The C function's argument list is always the same (see C Target Functions on page 8-16 for details).
You are not restricted to specifying parameter attributes either on a parameter or on the associated method. You can combine attribute settings on methods and parameters to achieve specific results. In the following example, the update method reads and is notified by p1 and p2, but writes and is not notified by p3:
module test {
int p1;
int p2;
int+nonotify+noread+write p3;
omethod+notify+read update = "test_update";
};
A parameter for which read/noread, write/nowrite, or notify/nonotify is not set inherits the setting for its parent object. (If the parameter is an immediate child of a module, it uses the setting for the associated method, as described in Setting Parameter Attributes on Methods on page 8-8.) If the parameter is a hierarchical object, the attribute is in effect downstream from the parameter until it is explicitly turned on or off. The following example defines a module named test, with a number of subobjects:
module test {
group+read p1 {
int sub1_p1
int+noread+nonotify sub2_p1;
};
int+read p2;
omethod+notify update = "test_update";
};
In this example, the update method reads and is notified by the parameter p1.sub1_p1, but it does not read and is not notified by the parameter p1.sub2_p1.
For efficiency, the req attribute behaves differently than read/noread, write/nowrite,and notify/nonotify. Its setting is not propagated: if it is to be applied to a particular parameter, you must set it at each level in the object hierarchy starting with the method itself.
The following example sets the req attribute on the parameter p1 but not the parameter p2:
The setting of the req attribute is also not inherited for hierarchical parameters:
In this example, the update method is not executed if p1.sub2_p1 does not have a valid value, but is executed even if p1.sub1_p1 does not have a valid value.
A module's method is influenced only by parameters that have the same parent as the method, in the scope where the method was originally defined. The following example defines a module named mod1, with parameters named x and y and a method named upd:
In this example, the upd method, which has the same parent as the x and y parameters, reads and is notified by both. You now create a module called mod2 that inherits from mod1 and defines an additional parameter named z:
In mod2, the inherited upd method is notified if x or y changes but, because it does not have the same parent as z, it is not notified if z changes.
This scope feature allows you 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, simply define a new method with the same name.
In some cases, however, the behavior resulting from the scope feature may not be desirable. 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 the scope feature, set the property no_meth_ctx on the method to a value of 1.
Suppose, for example, that you redefine mod1 by adding the no_meth_ctx attribute to the upd method as follows:
You now create the module mod2 exactly as you did before:
Because of the no_meth_ctx attribute, the upd method is no longer notified by x but is notified by z.
Previous sections described how to enable notifying a method when a desired trigger event occurs, typically the instancing or deinstancing of a module or a change to a parameter value. This section discusses how AVS/Express event notification occurs.
When a trigger event occurs, AVS/Express queues an event to execute one or more methods at the end of the current operation. By default, each method executes inside of a single operation; that is, no other methods execute until your current method returns. If multiple events are queued to execute the same method, they are compressed into a single method invocation.
Each operation is controlled using a context. At the start of the operation, a context is pushed onto a stack with the push_ctx function. At the end of the operation, the context is popped off the stack with the pop_ctx function restoring the previous context (if any). Contexts can be nested inside of each other but cannot overlap.
- The event filter ensures that a method does not queue events to itself. A method can change parameters from which it would ordinarily receive notifications 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 to which the method belongs.) Before a method is called, AVS/Express performs a push_ctx using the method's event filter.
- The state mode indicates how values changed during the current context are saved when the object itself is saved. (An object is saved, for example, when the application containing the object is saved.) When a value is modified, it is marked with the state mode of the current context:
- 1 indicates program state. Program state values are always saved. This state marks parameter values that are an intrinsic part of an application. All changes made with the Network Editor and through the V description are marked program state.
- 2 indicates user state. User state is only saved conditionally.This state marks changes that are made by an application user. This includes most parameter values made through user interface widgets.
- 4 indicates transient state. Transient state values are not saved. This state marks parameter changes that can be reproduced by a module if its inputs are reproduced. For a read image module, the output image can be reproduced as long as the filename parameter is restored, and would likely be considered transient state.
- For example, when you select a filename from a file browser widget, the widget calls push_ctx to set the state mode to user state, then changes the value of its filename parameter. This value is then marked as user state.
- You can override the state mode on an object 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:
- Note: You must set the val_state property on the object that actually stores the value of the object. In this case, if the filename parameter is connected to another value, you must set the val_state property on the object at the end of the connection.
- The event mode indicates how events are handled relative to the current context. By default, events are queued beginning with the push_ctx call and are executed synchronously when the pop_ctx call is invoked. When the pop_ctx call returns, AVS/Express will have delivered all events queued between the push_ctx and pop_ctx calls. You can change the event mode by setting it to one of three values:
- OM_CTX_PARENT_NOTIFIES. In this mode, AVS/Express executes events generated during the current context in the parent context. The parent context is the most recent outstanding context when a push_ctx call is invoked). This typically occurs when multiple push_ctx calls that are nested inside of each other; for example:
- If you specify this event mode and there is no parent context, AVS/Express executes the events in the current context. When the system executes a method, it calls push_ctx before it calls the method with this argument.
- OM_CTX_ALL_NOTIFIES. In this mode, AVS/Express ignores the event filter and therefore queue events that might otherwise have been suppressed. This mode can cause a method to send an event to itself.
- OM_CTX_BEGIN_OP. In this mode, the current push_ctx call has 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 routines are available to you in the C API and the C++ API, but not the FORTRAN API. For their descriptions, see the AVS/Express online help.
When a single operation triggers the execution of multiple methods, method dependencies determine the execution order: a method that depends upon another is executed after it. For example, if Method A reads data that method B writes, Method A depends upon method B and executes after it (which prevents Method A from executing twice).
Here is a more detailed example. Suppose that you have defined two modules, modB and modA, and the int common as follows:
int common;
module modB {
int+read+notify p1 => common;
int+write p2;
omethod update = "modB_update";
};
module modA {
int+read+notify p1 => common;
int+read+notify p2 => modB.p2;
omethod update = "modA_update";
};
The p2 parameter of modA, which modA reads and is notified by, reads the p2 parameter of modB. As a result, the method modA.update depends upon modB.update. If the integer common changes, it queues events for both modB and modA. However, because modA depends upon the output of modB, it runs after modB.
If you are unsure of a method's dependencies, call the V command $deps to determine them; for example:
OM(SingleWindowApp)-> $deps modA.update
function: Root.Applications.SingleWindowApp.modA.update depends on:
--------------------------------------------
func: Root.Applications.SingleWindowApp.modB.update
read/write on: Root.Applications.SingleWindowApp.modB.p2
weight: 1, rc: 1, req=0
===============================================
For a reference description of the V command $deps, see the AVS/Express on-line help.
When you include a method in a module, you must provide a C, C++, or FORTRAN target function that the method executes when it is triggered. A target function contains a mix of native code and calls to Object Manager API routines. These API routines, which enable communications with the Object Manager, perform tasks such as getting and setting the values of AVS/Express objects, navigating the object hierarchy, creating and deleting objects, and controlling execution.
Consider the following when you select a language for your target function:
- The C API provides the greatest flexibility of all module-writing techniques, but necessitates the most lines of code.
- By taking advantage of C++ language features, the C++ API provides an interface that is easier to learn and produces code that is easier to read. This is the preferred API for C++ users, including novice C++ programmers.
- The FORTRAN API provides FORTRAN versions of a subset of the C API routines.
You can write a target function for your method from scratch, but it is far simpler to let AVS/Express do most of the work for you. In summary, all you must do is:
2. Set it up for the type of target function that you want to use (C, C++, or FORTRAN), as described in subsequent sections.
- AVS/Express generates a template source fileand places it in the following file:
Some details of this process are slightly different for C++ target functions (see Supplying C++ Source Code on page 8-19 for details).
The following sections provide specifics for setting up modules so that their methods can execute C, C++, and FORTRAN target functions. It does not provide discussions of how to use Object Manager API routines within a target function; for details, see Chapter 9, Object Manager APIs.
AVS/Express provides a special command for editing source files. Note that it works only with source files specified with the src_file property, and not with source files specified with the c_src_files property. To use this command:
AVS/Express starts the specified editor for the selected object's source file and for all source files below the object in the object hierarchy. Using this command is equivalent to issuing a command line that starts the editor for all of those source files.
The example module ave, defined in V code as follows, averages two numbers:
module ave {
float+read+notify num1;
float+read+notify num2;
float+write res;
omethod+notify_inst upd = "ave_upd";
};
When the appropriate event occurs, this module performs its processing by triggering the upd method, which executes the C target function ave_upd. This function is defined in the file ave.c as follows:
#include <avs/om.h>
#include <avs/err.h>
/*
* ave_upd gets the module id, event_mask and.seq_num
*/
int ave_upd(OMobj_id obj_id,
OMevent_mask event_mask, /* triggering events */
int seq_num) /* sequence number of last exec */
{
double num1, num2, res;
if (OMget_name_real_val(obj_id,
OMstr_to_name("num1"),
&num1) != 1)
return(0); /* module failed... input not valid */
if (OMget_name_real_val(obj_id,
OMstr_to_name("num2"),
&num2) != 1)
return(0);
res = (num1 + num2) / 2;
OMset_name_real_val(obj_id,
OMstr_to_name("res"),
res);
For details on the C API routines used in this function, see C and C++ API Routines on page 9-5.
In the V code definition of the module ave, note that the method's type is omethod. Recall that you define a method as an omethod type to indicate that it executes a C target function.
Once you define a module, you must specify code management properties that tell AVS/Express what code it must generate to define your target function. 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 (see Providing Code Management Information on page 8-28).
Before you can compile a module, you must ensure that it will be compiled into the correct process (that is, executable) and correct the specification as necessary. You specify a process with the process property, which you can set either directly on the module or on the library in which you place it (see Defining Processes for Objects on page 10-12).
Suppose that you want to copy the module ave into the library Templates.USER, whose process property is set to compile the module into the user process and whose build directory is set to the user directory. You must revise the module definition as follows to specify the source file ave.c, which contains your C function, and your own build directory:
module ave<src_file="ave.c", build_dir="my_build_dir"> {
float+read+notify num1;
float+read+notify num2;
float+write res;
omethod+notify_inst upd = "ave_upd";
};
You can now compile the process containing the module ave.
When a target function executes (that is, when its method is triggered and calls it), the function receives three arguments: the object ID of its module, the event mask (which indicates the events that triggered the method), and a sequence number.
Through calls to the C API, the target function gets and sets the values of the module's subobjects. If any inputs are not set, the function returns a 0 value, to indicate that the module has failed. If the module fails, any modules with required dependencies on its outputs do not run until it executes successfully. If the module succeeds, it returns 1.
All of the basic rules for developing modules (see Module Overview on page 8-4) apply to modules written to use C++ target functions, with a few minor differences as documented in this section.
If a method for your module executes a C++ function, you specify its type as cxxmethod. You assign the notify_inst, notify, req, and notify_deinst properties to a cxxmethod object just as you do for other method types; however, supplying a string value for object is optional and, if present, the string value is handled differently than for other method types.
For details, see Supplying C++ Source Code on page 8-19.
When a module has a method of type cxxmethod, AVS/Express automatically generates a C++ class for it. Each cxxmethod object defines a C++ method that belongs to this class and whose name is the same as the name of the cxxmethod object. Each C++ method takes event_mask and seq_num parameters:
- The event_mask parameter specifies which events caused this method to be invoked. This parameter allows you to let multiple events (for example, instancing and parameter value change) trigger a single method and to differentiate programmatically between the event cases. The event_mask object is a bitmask that specifies a combination of the flags: OM_EVENT_INST (instanced event), OM_EVENT_VAL (value changed), and OM_EVENT_DEINST (deinstanced event).
- The seq_num parameter specifies the sequence number assigned the last time that the method was executed. You can use this to determine when a parameter is changed by passing it as the first argument to the parameter's changed method.
For more information on using this parameter, see Determining When Parameters Change on page 9-57.
Suppose, for example, that you define the module mod1 as follows:
For this module, you must implement a C++ method like the following:
This routine returns either 1 (success) or 0 (failure). If it fails, AVS/Express prohibits any modules with required dependencies on the outputs of this method from running until it executes successfully.
When you define a cxxmethod object, you can supply the source code for the corresponding C++ method in any of the following ways:
- Use the AVS/Express prototype generation facility. This technique, which produces a prototype C++ method that you can edit, is useful when when you do not already have source code.
- Specify the source code in the module definition itself, as the string value of the cxxmethod object. This technique is useful for very small routines with limited external dependencies.
- Supply your own source code and detach the cxxmethod object from AVS/Express prototype generation. This technique is useful when you have your own make facilities for your projects.
When AVS/Express compiles a process containing a module that includes a cxxmethod object, it can generate a prototype for the corresponding C++ method. You enable this prototype generation facility by setting the src_file property either on the module's cxxmethod object, the module, or the module's containing library, and leaving the value of the cxxmethod object unset. At compilation time, AVS/Express starts looking for the src_file property at the level of the cxxmethod object, ascending the object hierarchy if necessary, and chooses the first src_file property that it encounters. The specified file is used as the holder of the source code for the cxxmethod object. (If AVS/Express does not find a src_file property, it prints a warning message.)
When AVS/Express finds a src_file property, it scans the specified file for a method named module_name::method_name (for example, mod1::update). If such a method exists, AVS/Express does not generate code; however, if such a method does not exist, AVS/Express produces a prototype C++ method and places it at the end of the file. The prototype contains code only for those parameters that the cxxmethod object reads and/or writes; if the cxxmethod object sets neither the read nor the write flag on a parameter, AVS/Express does not generate code for it. The generated code varies depending on the parameter types, as follows:
- For a scalar parameter, it generates only a comment indicating the name of the parameter and the state of the read/write flags.
- For an array parameter, it generates prototype code that gets a pointer to the array and frees the pointer.
- For a group or group array parameter, it generates either the name of the parameter with the state of the read/write flags or, for some data types, a more complete description of which subobjects of the group need to be set and in what order.
You can edit the generated source code as necessary using the Network Editor's Edit Source facility.
Suppose that you define a module named test as follows:
module test<src_file="test.cxx"> {
Mesh_Unif+Scalar+notify+read &input;
int+write output;
cxxmethod update;
};
When you compile the process that contains this module, AVS/Express generates the following prototype code in the source file test.cxx:
int
test::update(OMevent_mask event_mask, int seq_num)
{
// input (Mesh_Unif+group read)
// input.ndim (int)
// input.dims (int [])
int *input_dims = (int *)input.dims.ret_array_ptr(OM_GET_ARRAY_RD);
// input.nspace (int)
// input.npoints (int)
// input.points (float [])
float *input_points = (float *)input.points.ret_array_ptr(OM_GET_ARRAY_RD);
// output (OMXint write)
/***********************/
/* Function Body */
/***********************/
printf("I'm in method: test::update\n");
ARRfree((char *)input_dims);
ARRfree((char *)input_points);
// return 1 for success
return(1);
}
Once the prototype code for a C++ method has been generated, the only way to have AVS/Express regenerate it is to rename or remove the method module_name::method_name. AVS/Express does not attempt to keep the prototype code up to date, which allows you complete freedom to modify the prototype source code.
Unlike module definitions s that use C or FORTRAN target functions, AVS/Express allows you to define a module with a C++ method entirely in V code, bypassing the need to create and maintain subsidiary files. To do this. you embed the source code in the module definition itself by supplying as the value of the cxxmethod object a string value that contains the body of the C++ method. At compilation time, AVS/Express places the code in the current output source file. By default, AVS/Express creates a default output source file named process.cxx, but you can specify a file explicitly with the out_src_file property. This embedded-source technique is useful for very small routines with limited external dependencies, but it has one disadvantage: any user who can edit the module definition also has access to its source code.
The string value that specifies your C++ source code may need to be long, so AVS/Express allows you to specify unescaped strings using V syntax rather than the standard double-quote string syntax. In V code, a string begins with the characters <" and ends with the characters ">. Note that your source code must include, as well as the primary code, a return statement (usually return(1), to indicate success).
The following example completely defines the module by_itself:
module by_itself {
int+read+notify a;
int+write b;
cxxmethod+notify_inst update = <"
if (a > b) b = a;
return(1);
">;
};
You can also use the embedded-source technique to define a module that calls a subroutine defined elsewhere. In the following example, the source code for the cxxmethod object references a user-written function named user_subroutine:
module subroutine_call {
int+read+notify ip1, ip2, ip3;
int+write ret_val;
cxxmethod+notify_inst update = <"
ret_val = user_subroutine(ip1, ip2, ip3);
if (ret_val > 0) return(1);
return(0);
">;
};
Because C++ requires that every function be defined before it is used, you must use the cxx_hdr_files property to tell AVS/Express which header files include this definition.
For details, see Managing Dependencies on Other Code on page 9-60.
If you do not want to use the src_file property mechanism to specify source code for your module's C++ method, you must set the use_src_file property to 0 on the module's cxxmethod object, the module, or the module's containing library. However, you must still tell AVS/Express how to get the source code. You can do this in either of the following ways:
- Set the link_files property to specify a prebuilt library or a prebuilt .o file or a combination of the two.
- Set the cxx_src_files property to specify one or more C++ source files, one of which contains the source code for the C++ method.
This section presents an example AVS/Express module that contains a cxxmethod object. (For details of how data is manipulated with this example, see C++ API Usage Notes on page 9-48.) The module, named EXquad_solver, implements a simple quadratic equation solver that takes three parameters, a, b, and c, which are the coefficients of a simple quadratic polynomial. If there are any defined roots for the specified coefficients, the module places them into the parameters r1 and r2 and returns the status message "Roots are real". If there are no defined roots, it sets r1 and r2 to 0 and returns the status message "Roots are imaginary." The module's method is called update_roots.
The EXquad_solver module is defined in V code as follows:
module EXquad_solver<
src_file="quad_mod.cxx", // specify file containing module src
out_hdr_file="quad_gen.h" // specify file to place
generated class
> {
float+read+notify+req a, b, c;
float+write r1, r2;
string+write status;
cxxmethod+notify_inst+req update_roots;
};
In this definition, note the following:
- The notify_inst parameter is set on the update_roots method, so it is triggered whenever the module is instanced. This allows the module to run correctly if it is instanced with predefined values for a, b, and c.
- The notify parameter is set on the parameters a, b, and c, so the update_roots method is triggered whenever a, b, or c is changed.
- The req (required) flag is set both on the update_roots method and on the parameters a, b, and c. As a result, the update_roots method is triggered only if a, b, and c have defined values, even when the trigger is the instance event. (The req attribute applies to instance events as well as to value-changed events.)
When the appropriate event occurs and the update_roots method is triggered, it executes the following C++ method, defined in the file quad_mod.cxx:
#include "quad_gen.h" // specified as out_hdr_file
// on EXquad_solver object
int
EXquad_solver::update_roots(OMevent_mask event_mask, int seq_num)
{
float op = b * b - 4 * a * c;
if (op < 0) {
status = "Roots are imaginary";
r1 = r2 = 0;
}
else {
op = sqrt(op);
float div = 2 * a;
if (div != 0.0) {
status = "Roots are real";
r1 = (-b + op) / div;
r2 = (-b - op) / div;
}
else {
status = "Roots are infinite";
r1 = 0;
r2 = 0;
}
}
return(1);
}
The variables a, b, c, r1, r2, and status are part of the C++ object that is an implicit argument to this C++ method. You can also reference these values as follows:
The example module ave, defined in V code as follows, averages two numbers:
module ave {
float+read+notify num1;
float+read+notify num2;
float+write res;
fmethod+notify_inst upd = "ave_upd";
};
When the appropriate event occurs, this module performs its processing by triggering the upd method, which executes the FORTRAN target function ave_upd. This function is defined in the file ave.f as follows:
C
C Source for fortran function ave_upd
C
integer function ave_upd(obj_id, mask, seq_num)
include 'avs/omf.inc'
integer obj_id(OIDSIZ)
integer mask
integer seq_num
integer name
integer num1_id(OIDSIZ), num2_id(OIDSIZ), res_id(OIDSIZ)
double precision num1, num2, res
ave_upd = 0
C
C Get the ids for each of our 3 arguments, num1, num2 and res
C Since we only access the first two values, we can use the flag
C OM_OBJ_RD. Since we modify the 3rd one we have to use OM_OBJ_RW.
C
name = OMFstr_to_name('num1')
if (OMFfind_subobj(obj_id,name,OM_OBJ_RD,num1_id) .NE. 1)
& return
name = OMFstr_to_name('num2')
if (OMFfind_subobj(obj_id,name,OM_OBJ_RD,num2_id) .NE. 1)
& return
name = OMFstr_to_name('res')
if (OMFfind_subobj(obj_id,name,OM_OBJ_RW,res_id) .NE. 1)
& return
C
C Get the double precision floating point values for num1 and num2
C Express always returns values in double precision format
C
if (OMFget_real8_val(num1_id,num1) .NE. 1) return
if (OMFget_real8_val(num2_id,num2) .NE. 1) return
res = (num1 + num2) / 2
if (OMFset_real8_val(res_id, res) .NE. 1) return
C
C Set return status to success
C
ave_upd = 1
return
end
For details on the various FORTRAN API routines used in this function, see FORTRAN API Routines on page 9-73.
In the V code definition of the module ave, note that the method's type is fmethod. Recall that you define a method as an fmethod type to indicate that it executes a FORTRAN target function.
Notice that upd's type is fmethod. This tells AVS/Express that you intend to use the FORTRAN API.
Notice that upd's type is fmethod. This tells AVS/Express that you intend to use the FORTRAN API.
Once you define a module, you must specify code management properties that tell AVS/Express what code it must generate to define your target function. 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 (see Providing Code Management Information on page 8-28).
Before you can compile a module, you must ensure that it will be compiled into the correct process (that is, executable) and correct the specification as necessary. You specify a process with the process property, which you can set either directly on the module or on the library in which you place it (see Defining Processes for Objects on page 10-12).
Suppose that you want to copy the module ave into the library Templates.USER, whose process property is set to compile the module into the user process and whose build directory is set to the user directory. You must revise the module definition as follows to specify the source file ave.c, which contains your C function, and your own build directory:
module ave<src_file="ave.f", build_dir="my_build_dir"> {
float+read+notify num1;
float+read+notify num2;
float+write res;
fmethod+notify_inst upd ="ave_upd";
};
You can now compile the process containing the module ave.
When a target function executes (that is, when its method is triggered and calls it), the function receives three arguments: the object ID of its module, the event mask (which indicates the events that triggered the method), and a sequence number.
Through calls to the FORTRAN API, the target function gets and sets the values of the module's subobjects. If any inputs are not set, the function returns a 0 value, to indicate that the module has failed. If the module fails, any modules with required dependencies on its outputs do not run until it executes successfully. If the module succeeds, it returns 1.
When you define a module (or library of modules), you must provide AVS/Express with information about the code upon which each group of modules depends. This includes, but is not limited to, information such as make commands, a build directory name, header files to be included, and source files to compile.
You provide this information by adding special code management properties to objects in the template hierarchy. You can define either a close relationship or an indirect relationship with this system:
- Define a close relationship by specifying directly the source necessary to build your objects.
- Define an indirect relationship by pointing AVS/Express to previously constructed libraries that you built with your own build system.
You typically place code management properties on modules or libraries, but you can place them directly on methods. When you place a code management property on a library, all objects in the library inherit it. When you place it on a module or method, only the module or method inherits it.
Once you assign code management properties, AVS/Express uses them to provide the following functionality:
- Automatic generation of makefiles to compile and link executables
- The ability to move modules from process to process by simply changing the process property on the module or library
For a list and descriptions of all code management properties, see Code Management Properties on page 14-13.
![]() |
![]() |
![]() |
![]() |