![]() |
![]() |
![]() |
![]() |
9 Integrating User Code
In this chapter, you create a component that performs its work by calling a C-routine.
- Start AVS/Express
- Create EchoReader
- Save EchoReader as a Template
- Generate C-code
- Compile the Process
- Test EchoReader
You can integrate C, Fortran, or C++ code into an AVS/Express application in essentially two ways:
- Create a module - You can create an AVS/Express module that calls a C, Fortran, or C++ routine to perform its processing.
- Manipulate instances of objects - You can create a C, Fortran, or C++ routine that accesses AVS/Express, creating, destroying, and/or modifying instances of existing AVS/Express objects. This allows you, for example, to build a C-based application that calls AVS/Express in server mode to carry out some aspect of the application, such as visualizing a set of data.
In this chapter, you create an AVS/Express module that calls a C routine. The module, part of the Echo Sounding application, reads echo-sounding data from a file.
AVS/Express offers several techniques for integrating C code into a module. In this chapter, you use the Add_Module tool to add the method and parameters of the module.
If you are no longer in AVS/Express, start it now with the myproj project.
- Depending on where you saved myproj, you may need to specify another pathname
- Visualization Edition users type:
- Developer Edition users type:
- AVS/Express begins and loads your project.
If you are still in AVS/Express from the last chapter, delete the current application workspace and load a new one.
- Note: Do You Have A C++ Compiler?
If you do not have a C++ compiler, you can still compile the sample module in this chapter. (You will be executing it in an external process called user, which you can build using a C compiler.) But you must start AVS/Express with the -nocxx command-line option. On UNIX systems: express -project myproj -nocxx
If you need to do this and you are still in AVS/Express from the last chapter, exit and restart AVS/Express, this time specifying the -nocxx option.
You define EchoReader as a module, similar to Echo. In this case, though, EchoReader will perform its processing by calling a C routine- in this case a C-routine that we provide for you, echo.c. However, EchoReader will be able to read any C-routine you instruct it to.
EchoReader also requires one input parameter, the name of the data file. In this tutorial we also provide that file. It is called echo.dat. Both echo.dat and echo.c are found in <install_dir>\getstart\.
The echo.dat data file looks like this:
# header lines, each beginning with '#'
#
60
3828
0.0133 -71.1667 42.8333
0.0133 -71.1467 42.8333
0.0133 -71.1267 42.8333
...
- The first number (60) is the distance in feet between the source and receiver.
- The second number (3828) is the number of data points in the file.
- All subsequent numbers are the actual data points. Each data point has three components: time in seconds (0.0133), longitude (-71.1667), and latitude (42.8333).
Accordingly, EchoReader requires four output parameters: DistanceSR, the number of data points, time, and location.
You begin by creating EchoReader in the Network Editor.
- Select the File->New Application command, select ScratchPad from the New Application dialog, and click OK.
![]()
- You now have a module named EchoReader. The next step is to add the method.
1. Click on the "Type field" and select C function (o method) to replace the default C++ member function (cxx method) because in this case we will be adding C-code. The o-method refers to C-code.
- This puts you into the screen where you can add a method object, identified by the lightning-bolt icon. You use a method object to associate EchoReader with a C routine.
3. Once on the add_method page (pictured below) click on the Object name field and type "update". This will name the method you are creating.
- Notice that the method object appears and alters in the network editor as the Add_Module tool does its work.
4. Specify properties for the update method. Under Method Type select Method runs when object is instanced.
6. In the C-function name field enter echo_reader. This will be the name of the function update calls.
![]()
- The Add_Module Tool then displays an editor page for the parameter.
![]()
4. Expose an input port - Set the toggles Object is an exported parameter and Display input port on.
- Note: These toggles are independent of one another. The toggle Object is an exported parameter determines whether the parameter is visible or not when the module is opened in Display Params mode, and the Display input port makes the parameter's port available at the module interface. You can set either one without setting the other.
![]()
You use this page to assign attributes that define the interaction between the selected parameter and the method you select in the upper panel. In the current example, update is the only method and so is automatically selected.
6. Set the parameter's method attributes - For filename, click the toggles for notify, read, and req to set these attributes for filename.
- Attributes are boolean characteristics for an object. The notify attribute says that the update method should be notified when the parameter's value changes. Typically, a notification results in the method's execution. The read attribute indicates that this is an input parameter. The req attribute indicates that this parameter is required, meaning that the update method should execute only when the parameter has a value. More specifically, it means that the Object Manager will generate an OMget.xxx call rather than an OMset.xxx call.
7. Click on Done. This will return you to the add_parameter page and allow you to either move on by clicking Next> or add more parameters. In this case we will add more parameters
- Select the parameter - Select float on the Type menu.
- Click on Add to move to the add_parameter page.
- Rename the parameter - In the Object name field, change NewParameter to
distance_sr.- Expose an output port - Set the toggles Object is an exported parameter and Display output port on.
- Click on Next> to move on to attribute page.
- For distance_sr, click the toggle for write.
- The write attribute indicates that the object is an output parameter.
- As you have seen, when a module calls a C routine, you indicate which data parameters are input and which are output. The input parameter, filename, gets the read, notify, and req attributes. All the output parameters in this example-distance_sr, num_values, time, and location-get the write attribute.
- Select the parameter - Select int on the Type menu.
- Click on Add to move to the add_parameter page.
- Rename the parameter - In the Object name field, change NewParameter to num_values.
- Export the parameter - Set the toggle Object is an exported parameter on.
- You only use the num_values parameter inside of EchoReader to set the size of the time and location arrays that you specify next. As a result you do not need to display a port for num_values to objects outside EchoReader. Here, we only export the parameter itself so that all the EchoReader parameters always appear in the Network Editor.
- Select the parameter - Select float on the Type menu.
- Click on Add to move to the add_parameter page.
- Rename the parameter - In the Object name field, change NewParameter to time.
- Expose an output port - Set the toggles Object is an exported parameter and Display output port on.
- Set parameter dimensions - Change the time parameter to a one-dimensional array sized to num_values.
- Click on the toggle Object is an array.
- In the Dimensions field, enter num_values between the brackets:
- Notice that the title bar for the time subobject in both the Add_Module Tool and the Network Editor displays a pair of brackets after the object name, indicating that the object is an array.
- The time parameter needs to be an array large enough to hold num_values elements, where num_values is the number of data points. Since num_values is one of EchoReader's parameters, the size of time depends on the current value of num_values. If num_values changes, so does the size of time.
- Click on Next> to move on to attribute page.
- Click the toggle for write.
- Select Done.
- Select the parameter - Select float on the Type menu.
- Click on Add to move to the add_parameter page.
- Rename the parameter - In the Object name field, change NewParameter to
location.- Expose an output port - Set the toggles Object is an exported parameter and Display output port on.
- Set parameter dimensions - In this case we will be changing the location parameter to a two-dimensional array.
- Click on the toggle Object is an array.
- In the Dimensions field, enter num_values between the brackets, and enter an open bracket, 2, and a close bracket:
- Press Enter to assign the dimensions. Notice that the title bar of the location subobject displays two sets of brackets, indicating a two-dimensional array.
- Like the time parameter, the location parameter needs to be an array large enough to hold num_values elements. But the location array also requires an additional dimension to store the longitude and latitude for each data point.
- Click on the Next> button to move to step three of the Add Module Process.
For a module that calls a C routine, you need to supply certain code-related information. For EchoReader you set properties and attributes on the module and its parameters to indicate:
- the C routine's name (already set)
- the C routine's source file and directory
- the C routine's process (As you will see, you specify a process because you want EchoReader to execute in the user process, but you will be placing EchoReader in a library that sets the default process to express.)
- interactions between parameters and the method (For example, if the method is notified when there is a change in a parameter.) (already set)
- which data parameters are input and which are output (already set)
You provide this information to AVS/Express through a set of code management properties. You can set these properties using step three of the Add Module Process.
- Note: You could have set the code management properties, the attributes, and the parameter and method properties in the Object Editor. In the Add_Module Tool we set them all at once so that you know you haven't missed anything.
For EchoReader, we are going to set the process property to "user", the build_dir property to "echo", and the src_file property to "echo.c". This is all done in Step 3 of the Add_Module Process.
1. Set the process to user - From the option menu Process in which object resides, select the user option.
- The process property specifies the routine's process.
- This field specifies the name of the source file. It sets the src_file property for the module.
- The build_dir property specifies the source-file directory. You specify the directory relative to the project directory. The current project directory is displayed as the Directory for source above the Source filename field. The build directory you specify in build_dir is appended to this path.
- This field sets the build_dir property for the module.
- Note: VERY IMPORTANT: The file called "echo.c" does not yet exist in your source directory. To have this example work you will need to copy echo.c from <install_dir>\getstart\ to <install_dir>\myproj\echo\. The example will NOT work unless you do this. However, before you do that, proceed through the next two sections. The instructions on how to set the echo.c file can be found in the section Modify echo.c on page 9-19
In this section, you save EchoReader as a template and save your project.
- Select Project->Save.
- Deleting ScratchPad reveals your previous application workspace, though it is currently in the closed state within Applications.
You can write the C code for EchoReader from scratch. Or you can allow AVS/Express to generate a skeleton version of the code, which you can then modify. In this section, you use the generate-and-modify technique.
You can edit a module's source file from AVS/Express. If you request to do so but the source file does not yet exist, AVS/Express generates a skeleton version of the source file, which you can then modify.
- AVS/Express highlights EchoReader in blue.
- AVS/Express attempts to open an editor for EchoReader's source file. AVS/Express looks in the project directory for a directory called echo and a source file called echo.c. This is what you specified for EchoReader. But echo and echo.c do not exist, so AVS/Express, after prompting you with a pop-up dialog, creates them and a skeleton version of the code. AVS/Express then opens echo.c in an editor. (On UNIX systems, it is the editor specified by the EDITOR environment variable. In Windows it will be your default editor- usually Visual Studio.)
Here is the top portion of echo.c:
#include "user.h"
int
echo_reader(OMobj_id EchoReader_id, OMevent_mask event_mask,
int seq_num)
{
/***********************/
/* Declare variables */
/***********************/
char *filename = NULL;
double distance_sr;
int num_values;
int time_size;
float *time;
int location_size;
float *location;
/***********************/
/* Get input values */
/***********************/
/* Get filename's value */
if (OMget_name_str_val(EchoReader_id, OMstr_to_name("filename"),
&filename, 0) != 1)
...
If you scroll through the file, you see that it contains several Object Manager API calls, such as OMget_name_str_val and OMset_name_real_val. Calls such as these get the module's input parameter, set the module's scalar output parameters, and get pointers to the module's array parameters. (You will soon see a version of the routine with more detailed comments.)
Either now or when you compile the process later, you may find that something is incorrect with the way you defined EchoReader. For example, if you forget to set the update object to the name of the target routine, AVS/Express will not generate the routine for you.
If for any reason you need to edit EchoReader's definition, do the following. You may want to practice these steps now, even if EchoReader's definition appears correct.
2. Load a ScratchPad application workspace by selecting File->New Application and selecting ScratchPad from the New Application dialog.
- You can skip this step if you are just practicing.
8. Maximize the DefaultApplication workspace by double-clicking on it or selecting the Maximize popup command.
You now modify the generated code, primarily by adding code to read a data file and populate the output parameters.
To save you time, a modified version of echo.c is available in the express directory. You can copy that version, rather than making the changes manually.
If you had attemped to run the previous version of EchoReader after compiling, quit express. Delete the file, lib\pc\echo\echo.obj, and restart express.
install_dir is the directory in which you installed AVS/Express.
Here is the modified code for echo.c, including detailed comments on how it works:
#include "user.h"
#include <stdio.h>
/***************************************************************/
/* echo_reader: reads a data file into the echo_reader module. */
/* */
/* Most of the code you see here was generated by AVS/Express. */
/* This includes the function declaration, the OM calls */
/* to access the module's data parameters, and the variable */
/* definitions related to those calls. */
/* */
/* You add routine-specific code, in this case, code to open a */
/* data file, read its values, and populate the time and */
/* location arrays. */
/***************************************************************/
/***************************************************************/
/* AVS/Express generates the function declaration you */
/* see below. The function's first input parameter is the key */
/* one for the purposes of most routines. AVS/Express assigns */
/* a unique object ID to each object in the object hierarchy. */
/* The first input parameter contains the object ID of the */
/* calling module. From this ID, you can access the module's */
/* data parameters. The other two function parameters provide */
/* additional information, which you do not need for this */
/* module. */
/***************************************************************/
int
echo_reader(OMobj_id echo_reader_id,
OMevent_mask event_mask, int seq_num)
{
/***********************/
/* Declare variables */
/***********************/
char *filename = NULL;
double distance_sr;
int num_values;
int time_size;
float *time;
int location_size;
float *location;
int i;
char Buff[512];
FILE *fp;
/************************************************************/
/* A routine typically begins by getting the module's input */
/* data parameters. In this case, it gets the value of */
/* parameter filename. */
/* */
/* Most API calls have the prefix OM, for Object Manager. */
/* The call to OMget_name_str_val, generated for you by */
/* AVS/Express, looks in the module identified by */
/* echo_reader_id for a parameter named "filename", and */
/* places the value of that parameter in the variable */
/* filename. */
/************************************************************/
if (OMget_name_str_val(echo_reader_id,
OMstr_to_name("filename"),&filename,0)
!= 1)
filename = NULL;
/***********************/
/* Function's Body */
/***********************/
/* Code you supply to open the data file. */
fp = fopen(filename, "r");
if (fp == NULL) {
fprintf(stderr, "cannot open file %s\n", filename);
return(0);
}
/* Code you supply to strip off header (lines beginning with #) */
/*(assumes there is a blank line after last header line) */
while (fgets(Buff, sizeof(Buff), fp))
if (Buff[0] != '#') break;
/* Code you supply to read distance SR and the number of */
/* data points. These are the first two values in the */
/* input file. */
fscanf(fp, "%lf", &distance_sr);
fscanf(fp, "%d", &num_values);
/***********************************************************/
/* AVS/Express also generates OM calls to set the value of */
/* the module's scalar output parameters. For example, */
/* the first call to OMset_name_real_val looks in the */
/* module identified by echo_reader_id for a parameter */
/* named "distance_sr" and places into that parameter the */
/* value of distance_sr. */
/***********************************************************/
/* Set distance_sr's value */
OMset_name_real_val(echo_reader_id,
OMstr_to_name("distance_sr"), distance_sr);
/* Set num_values's value */
OMset_name_int_val(echo_reader_id,
OMstr_to_name("num_values"), num_values);
/************************************************************/
/* For array parameters, AVS/Expresss generates OM calls to */
/* return a pointer to the array. For example, the first */
/* call to OMret_name_array_ptr looks in the module */
/* identified by echo-reader_id for a subobject named */
/* "time" and returns a pointer to the array. The argument */
/* OM_GET_ARRAY_WR tells the Object Manager that you intend */
/* to write to the array. After the call, the fourth */
/* argument, time_size, contains the number of elements in */
/* the array. */
/************************************************************/
/* Get a pointer to the time array */
time = (float *)OMret_name_array_ptr(echo_reader_id,
OMstr_to_name("time"), OM_GET_ARRAY_WR, &time_size, NULL);
/* Get a pointer to the location array */
location = (float *)OMret_name_array_ptr(echo_reader_id,
OMstr_to_name("location"), OM_GET_ARRAY_WR, &location_size, NULL);
/* Code you supply to read the data into the arrays */
for (i=0; i<num_values; i++)
fscanf(fp, "%f %f %f", time+i, location+2*i, location+2*i+1);
/************************************************************/
/* The Object Manager keeps track of array references. A */
/* call to OMret_name_array_ptr tells the Object Manager */
/* that the routine needs to reference the array. A */
/* subsequent call to ARRfree, generated by AVS/Express, */
/* tells the Object Manager that the routine is finished */
/* with the array. */
/************************************************************/
if (filename) free(filename);
if (time != NULL) ARRfree((char *)time);
if (location != NULL) ARRfree((char *)location);
return(1);
}
Workspace 1, the library where you placed EchoReader, is set up so that modules compiled in it belong by default to the express process. But you cannot compile the express process when you are in AVS/Express because the express process is running. So if you want to compile EchoReader within AVS/Express, you need to specify that EchoReader belongs to an external process. AVS/Express provides one, called user.
In a previous step, you set EchoReader's process property to "user", thereby overriding the default for the library. You are now ready to compile the user process.
- AVS/Express highlights EchoReader in blue.
- Selecting a template object indicates the process you wish to compile. AVS/Express compiles the process specified by the process property for the selected object. You could have selected any object that belongs to the user process.
- AVS/Express compiles the user process. This includes generating a makefile, compiling the code and linking the process. A window appears that displays informational messages and any error messages that may be generated.
- At the end of a successful compilation, the following message appears:
3. If the process compiles without error messages, remove the window by pointing to it and pressing Return or any other key.
- If you have errors, edit echo.c and compile the process again.
In Chapter 11, Creating and Compiling an Application, you will use EchoReader along with Echo in an actual application, one that includes a user interface and a data viewer.
For now, simply test EchoReader on its own. You can use as input a sample data file located in the express installation directory.
- The file is called echo.dat and is located in install_dir/getstart.
- For example, if the installation directory is /home/express, you would enter the following. Remember to enclose the string in quotes. In Windows you need to either use foward slashes, "c:/Express/getstart/echo.dat" or double back slashes "c:\\Express\\getstart\\echo.dat"
- Closing the object applies the assignment.
- EchoReader's update method is notified that filename has changed, so it executes. The routine reads the data file and places the data into the output parameters.
- The title bars of these parameters indicate that they now have values:
- A scrollable text window appears containing time's values.
- A scrollable text window appears containing location's values.
![]() |
![]() |
![]() |
![]() |