2 Understanding the Module Builder

2 Understanding the Module Builder

This chapter explains in detail how to use the Module Builder to construct a module. It describes how you:

  • create a user function for the module

  • define include files and libraries for the module

  • define data input and output ports

  • specify the function arguments and link them to the ports

  • configure the build environment

  • use the build options to build and install the completed module

It also includes some examples of user functions for simple modules.

Advanced topics are marked with star. As an ordinary user, you can usually skip these passages on a first reading.

2.1 Creating a User Function

The user function or subroutine is a program that details what you want the module to do. You can write it yourself or use a program that already exists. The user function file contains the algorithm that actually performs the data transformation. It also contains calls to any API subroutines you are using in the module.

You can write the program in C++, C, or Fortran 77; however, all user functions must export a C-style user interface. Writers of C++ user functions must qualify their routines accordingly. For example, you can qualify a routine called funcName() as

extern "C" funcName()
Note

In the IRIS Explorer environment, you must place Fortran code into a file with a .f suffix and C code into a file with a suffix of .c. You can put C++ code into a file with a suffix of .C, .c++, .cxx or .cpp. Note that the Module Builder may modify the suffix of the file which is passed to the compiler; this is only important when debugging the module.

You can write the user function at any time, but you cannot build the module without it. You can set up other parts of the module, such as the input and output ports and connections, save the module resources, and then pull up the half-finished module and build it later, when the user function is complete. You can also generate a prototype and build the module later.

2.1.1 Using the IRIS Explorer API

The IRIS Explorer application programming interface (API) is a set of functions in C and corresponding subroutines in Fortran that give the module writer greater control over module behavior. For example, instead of the Module Builder generating code to connect the user function to the ports, the writer can use the API to do so. The writer can also build more complex modules by using the API to make specific calls to other parts of IRIS Explorer. The API is described fully in the IRIS Explorer Reference Pages.

Note

If your user function is written in C++, use the C API. Since C++ is a superset of C, the C API can be called directly by C++.

2.1.1.1 star Geometry Modules in Fortran

If you try to build a geometry module in Fortran, you can only access the Geometry library through the IRIS Explorer API subroutines. If you want to use the Open Inventor geometry library directly you must build geometry modules in C or C++, and you will then also need a Developer's Licence for Open Inventor on your platform.

2.1.1.2 star The Scripting API

If you are writing a module, you can use cxScriptCommand to issue scripting commands from within the module. When you call cxScriptCommand, a message transmission and several context switches result, so performance is improved if you can bundle expressions in a single string.

For more information on the syntax of cxScriptCommand and the Skm language, refer to Appendix B, "The Skm Language" in the IRIS Explorer User's Guide (Windows NT/2000).

2.2 Running the Module Builder

You use the Module Builder to create the module resources file and the control panel for a module and to build and install the module so that it can be launched in the Map Editor.

There are a number of ways in which the Module Builder may be started. One way is via the Programs entry on the Start menu. Alternatively, you can start up Module Builder through its command line mbuilder, which allows you to specify a module filename at startup. You can obtain a command line through the Run option accessible from the Start menu. You can also type the mbuilder command in an MS-DOS Command Prompt window.

The Module Builder starts up, and its main window appears on the screen. The menu bar at the top of the window provides access to various commands, which are briefly outlined below:

File

Creating new modules, opening existing modules and saving modules. This functionality is duplicated in the Toolbar, which appears just beneath the menu bar. For more information on these options, see Section 2.2.1.

Prototypes

Creating templates for the module source and help files. For more information, see Section 2.6.

Module

Compiling, installing, debugging and deleting the module executable. More information on these options is contained in Section 2.7.

Type

Compiling and installing user-defined data types. See Chapter 8, "Creating User-Defined Data Types" for more information.

View

Controls the visibility of various parts of the main window.

Help

Gives access to help on the Module Builder.

2.2.1 Opening Modules

Upon startup, the Module Builder's main window appears on the screen. If it has been started without a filename, the main window comes up with default_module_name in the Module Name slot. In this window, you set the variables for the module resources file, a binary file called <modulename>.mres, which contains all the information required to build the module.

Module Builder Main Window

Figure 2-1 Module Builder Main Window


The .mres file template is created as you apply the information from each of the Module Builder windows (see Section 2.2.2) and is saved when you first select Save Resources from the File menu.

To use the Module Builder to look at an existing module resources file, use the Open command on its File menu. Alternatively, specify the name of the file on the command line; for example, to look at the structure of an existing module such as GenLat, type:

mbuilder %EXPLORERHOME%\modules\GenLat.mres

where the environment variable EXPLORERHOME is defined at run-time to point to the base of the IRIS Explorer installation directory. The Module Builder opens with the information pertinent to the module displayed in its windows.

2.2.2 Moving Around the Windows

Five windows can be opened from the main Module Builder window, but only one besides the main window can be active at a time. If you try to open more than one, a dialogue box appears and tells you which window is already open. The active window may be buried under other windows or it may be iconified. To re-display the open window, click on the corresponding button in the main Module Builder window.

To select a text type-in slot in any of the Module Builder windows, click on the slot. The active slot is outlined in black and has a cursor in it. You can type into the active slot.

When you have entered information in a window, you can use one of the buttons at the bottom of the window:

Cancel

Cancels all the information before it can be saved

Apply

Saves the information and leaves the window open (only available on some windows)

OK

Saves the information and closes the window

Changes are not incorporated into the module template until you click on Apply or OK.

To save the template as a modulename.mres file, you must use Save Resources on the File menu. You should do this often, to avoid losing your work.

2.3 The Main Window

Filling out the Module Builder main window is equivalent to setting the variables required by the language compiler and link editor.

2.3.1 The Module Name

The modulename can be:

  • the name of an existing module that you want to examine. For example, the command

    mbuilder LatToGeom.mres
    
    opens the Module Builder with LatToGeom. As you move through the Module Builder windows, you have access to the module's internal structure, called the module resources.
  • the name for a new module that does not yet exist, but that you are about to create. The Module Builder main window appears with the new name in the Module Name slot, but all other windows are empty.

If you want to look at a different module resource file or to create another new module, use the File menu in the Module Builder main window. To select another modulename.mres file, click Open... and select the file from the file browser that appears. Alternatively, the Module Builder remembers the most recently accessed .mres files and places their names on the menu (see Figure 1-3); you can reload any of these by clicking on the name. To create a new module entirely from scratch, select New.

2.3.1.1 Naming Modules

Enter the name of a module in the Module Name slot (see Figure 2-1). To enter or change the name of a module in the slot, click the text slot to highlight it and type a new name.

Each module name must begin with an alphanumeric character. Since they are Windows files, you should follow the appropriate file-naming conventions.

2.3.1.2 Modifying Existing Modules

If you are modifying an existing module to create a new one, you should save the modified resource file with a new module name and leave the original modulename.mres files intact.

Note

The source to almost all of the IRIS Explorer modules is distributed in %EXPLORERHOME%\src for you to examine, modify and use as the basis for new modules of your own. Even if the source of a module is not available, you can make modifications to the module resource files and change its appearance via the Control Panel Editor, for example.

2.3.1.3 Saving Modules

By default, IRIS Explorer saves newly created modules in the working directory. The module is saved in the form of a modulename.mres file when you select:

  • Save Resources from the File menu (saves the contents of the windows as module resources), or

  • Build or Build and Install from the Module menu (saves the resources, compiles and links the source code and places the executable either in the working directory (Build) or in the installation directory (Build and Install)

2.3.2 The User Function File

Enter the name of the file or files that contain your computational, or user, function in the User Func File text slot. For example, the Gradient3D module has one user function file, called Gradient3D.f.

Note

Your module may use an alternate executable, that is, a user function connected with another module (see Section 2.7). If so, don't enter the name of the user function in the User Func File slot, since it will be entered on the build options control panel.

2.3.2.1 Using Several Function Files

If the user function is large and complex, you can break it down into smaller sub-functions, each of which is in its own file. In the User Func File slot, list two or more files with a space between each name.

You can mix languages (C++, C, and Fortran), source files (filename.cpp, filename.c, or filename.f), and object files (filename.o) in this slot. For example, Render, which is a very complex module, uses render.C, event.C, init.C, SoSceneViewer.C, SvManipList.C, cxGeoObject.C, and several other files.

The module builder uses standard make rules to determine file dependencies and to build the necessary object files from C, C++, and Fortran source files.

2.3.2.2 star Writing Fortran User Functions

In IRIS Explorer, *.inc files have no cpp directives in them at all. To use #include syntax, name your source file *.fi, and the IRIS Explorer Makefiles will filter it for you. If your Fortran compiler naturally supports cpp, you can still use *.f. To use include statement syntax, name your source file *.f.

Use of certain data types requires that you explicitly include certain files. If you use cxLattice, you may need to include Typedefs.inc. If you use cxPyramid, you must include cxLattice.inc. If you use DataTypes.inc, however, it includes Typedefs.inc implicitly.

2.3.3 The Include Files

The Module Builder automatically passes the include directories such as %EXPLORERHOME%\include to the compiler when compiling the module's source code. However, you may want to include directories or files from other places for a particular module.

Use the Includes slot to do this.

Enter the name of each included file as you would list it in a compile command. Use the form -I<directory>; for example, -I..\myInclude. Thus, to build the DisplayImg module required the following include command:

-I$(CXSYSINCLUDE) $(ILINC)

which appears in the Module Builder Includes slot for the module (see Figure 2-1). Here, CXSYSINCLUDE and ILINC are environment variables, set when the module was built. They contain the names of directories, where the required include files for the module are to be found.

If you include a system-defined variable by itself, it takes the form ${VARIABLE}; for example, ${GEOMETRYINCS}. You can also use the -D option for variables; for example, the Render module uses -DEXPLORER. For more information on these options, refer to the documentation for your compiler.

2.3.4 Associated Libraries

Some modules, such as those handling graphics, geometry, or mathematical procedures, and those written in Fortran, must be linked to related libraries. IRIS Explorer automatically links certain default libraries with the module.

If you have selected Fortran as the language for the user function (see Section 2.5) or if you have a file in your User Func File list with the suffix .f or .fi, IRIS Explorer will link with the Fortran default libraries; these are indicated by the system-defined variable ${FORTRANLIBS}.

If you have specified the cxGeometry data type on an input or output port, IRIS Explorer will link with the geometry libraries; these are indicated by the system-defined variable ${GEOMETRYLIBS}. An example is the Render module which has a Geometry input port.

The system variables ${FORTRANLIBS} and ${GEOMETRYLIBS} include any default (Fortran and geometry) libraries that are machine-dependent.

You may wish to:

  • add libraries in addition to the default libraries

  • use the libraries defined by ${FORTRANLIBS} and ${GEOMETRYLIBS} in a module that does not automatically include them

  • add your own libraries to a module

Use the Libraries slot to do this.

Enter the name of each library as you would list it in a link command. Use the form -l<library> for individual libraries, and -L<directory> to add directories to the library search path. For example, to build a module that makes use of a copy of the NAG C library which has been installed in C:\usr\local\lib\nagc.a, enter:

-LC:\usr\local\lib -lnagc

Another example is provided by the DisplayImg module, which (see Figure 2-1) uses the following options to link with extra libraries:

$(LIBGLW) -limagelib $(ILLIB)

where ILLIB and LIBGLW are environment variables, set when the module was built, that contain the names of libraries required by the module.

For more information on the -l and -L options, refer to the compiler documentation.

star If you are creating a module with a user-defined data type (UDT) on an input or output port, IRIS Explorer links the associated libraries with the module; however, if you use the UDT inside a module that has no UDT port, to create a modified lattice, for example, you must specify the associated library in the Libraries slot. User-defined data types are described in Chapter 8, "Creating User-Defined Data Types".

2.3.5 Example Maps

This slot allows you to type in the name of a map that illustrates the use of the module. If no path is specified, the module path given in the configuration settings is searched. This map will appear as the designated Example Map when you right-click on the module's name in the Librarian. For example, DisplayImg has an example map called imaging.map (see Figure 2-1) which illustrates the way in which the module can be used.

2.4 Creating Ports

The module ports allow you to get data into and out of the module. They constitute the interface between the module itself and other modules, the external interface. You can define up to 40 input or output ports for a module, each of which must have a unique name. If the Module builder detects a duplicate name, it renames the second port and issues a message.

Note

IRIS Explorer automatically adds new synchronisation ports called Fire and Firing Done to all modules when they are placed in the Map Editor (see Section 2.6.2 in the IRIS Explorer User's Guide (Windows NT/2000)). In addition, if the module is a loop controller, it also adds a Loop Ended synchronisation port. To prevent confusion, you should therefore avoid using these names for the ports which you define when you build the module.

2.4.1 Defining Input Ports

Figure 2-2 shows the Input Ports window for VectorGen. This module has two ports, Input and Scale Factor. When you define an input port, you give the Module Builder the information that it needs to allow legal connections between this port and output ports on other modules.

2.4.1.1 Input Port Characteristics

Three fields need to be defined: the port name, its data type, and its status.

Input Ports Window

Figure 2-2 Input Ports Window


The port name goes in the text type-in slot. You can give a port any name you wish, as long as it is unique within the module. Descriptive names, such as Color Input, make the module easier to use than, say, In1. The name appears on the Input Ports menu of the module control panel in the Map Editor.

The data type on the port provides crucial information about data structures. It denotes the form in which incoming data must be cast for the port to accept it. The center column of option menus lists the available data types, of which you select one.

For more details on IRIS Explorer data types, see Chapters 3 through 8.

IRIS Explorer has seven built-in data types. These are listed on the data type pop-up menu shown in Figure 2-2. In addition, you can create your own data types (see Chapter 8, "Creating User-Defined Data Types") and if any of these have been added, they will also appear on the data type pop-up menu for both the Input and Output Ports. For example, both the pop-up menu in Figure 2-2 and the pop-up menu for the Output Ports window in Figure 2-6 shows an additional user-defined data type called gnBase.

Users can connect only ports with the same data types, except for cxGeneric[2]. For example, if Input has the cxPyramid data type on its port, it can receive only a connection from a pyramid output port on another module. IRIS Explorer data types are all pointers to C structures, so they can be linked to Fortran user subroutines because Fortran can accept variables passed as pointers.

If you select the:

  • cxLattice data type for a port, the Lattice Constraints window appears (see Figure 2-3).

  • cxPyramid data type for a port, the Pyramid Constraints window appears (see Figure 2-5).

The Map Editor uses the constraint options for type checking when modules are connected to one another in a map. If the incoming connection passes a lattice that is incompatible with the options defined on the receiving port, a warning message appears and the connection is not made.

The status flag menu lets you specify whether the port is optional or required. If an input port is:

Required

It must be wired up and have data on the port before the module will fire. This implies that it must also have a connection from an upstream module. (This is the default.)

Optional

It need not be wired up for the module to fire. However, once it is wired up, it behaves like a required port and it must have data on the port before the module will fire.

2.4.1.2 star Firing Sequences

The actual firing sequence of a module is controlled by the firing algorithm. For more detailed information on the passage of data into and out of ports, refer to Appendix A, "The Firing Algorithm".

2.4.2 Setting Lattice Constraints

The cxLattice data type is flexible enough to accommodate a variety of lattice types. You can more narrowly define the kind of data that a single lattice port can accept by setting options in the Lattice Constraints window (see Figure 2-3).

Lattice Constraints Window

Figure 2-3 Lattice Constraints Window


You can set any restrictions that make sense for your lattice port. This applies to both input and output ports which accept or produce lattices.

For example, Figure 2-3 shows the lattice definition for a port called Input, which is expecting a colormap. The colormap lattice is a 1D lattice with three (RGB) or four (RGBA) floating point data variables per node. It has uniform coordinates and it must contain both data and coordinate values.

Table 2-1 shows how these specification are applied in the Lattice Constraints window.

Table 2-1 Colormap Lattice Constraints

Colormap Lattice Specs Button Positions
A 1D lattice Button #1 in the Num Dimensions field is ON
Three or four data variables Buttons #3 and #4 in the Num Data Variables field are ON
Float data values The Float button in the Primitive Data Type list is ON
Uniform coordinates The Uniform button in the Coord Type list is ON
Curvilinear coordinate variables per node Buttons 1 through 3 in the Num Coord Dimensions list are ON
Required values The Required button is ON for both the Data Structure and Coord Structure fields

2.4.2.1 Defining Lattice Constraint Fields

Each field shown in the Lattice Constraints window defines a part of the lattice data type structure. The limits you set in this window should be consistent with the way the lattice is defined in the user function.

For more details on the lattice data type, see Chapter 3, "Using the Lattice Data Type".

Click the check boxes and radio buttons (or boxes) to activate the options. A tick or a dot (as opposed to an empty check box or radio button) indicates that the option is allowed.

Num Dimensions

Your port can accept lattices ranging from 1D upward. You can restrict it to accept just one set of dimensions, such as 3D only, or you can let it accept, for example, both 2D and 3D lattices, depending on how specific or how complex the user function is.

Num Data Variables

You can specify as many data variables as the user function can handle; for example, temperature, pressure, and altitude.

Primitive Data Type

You can specify any or all of the primitive data types. You can allow the port to accept lattice data of all primitive types, even though the user function accepts only one type of data such as bytes, for example.

In such cases, the Module Builder performs type coercion on the incoming data to convert it to the primitive type that the user function argument requires (see Section 2.4.2.2).

Note

The lattice primitive data types are C data types. If your user function is written in Fortran, select the C equivalents of the Fortran data types the user function expects (see Table 3-1).

Coord Type

You can specify any or all of the coordinate types. They are described in detail in Chapter 3, "Using the Lattice Data Type".

Num Coord Dimensions

If the module accepts or produces a curvilinear lattice, you must specify whether the lattice can have one, two, or three coordinates per node of the lattice.

Data Structure and Coord Structure

These options let you refine the port status, which is determined in the Input or Output Ports window (Required or Optional). You can stipulate whether the lattice must have both data and coordinate values (the default) or whether one or the other may be optional. For example, a module such as Interpolate is concerned only with coordinate values, not with data, so you can set the Data Structure on the output port lattice to Optional.

star You can use the Data Optional/Coords Optional setting to allow the module to accept a lattice that has coordinate values but no data values. This lets you describe the geometry of a shape (such as a sphere or a pyramid mesh) by using coordinate values only. You can then feed in a lattice containing data values only that show, for example, the variation in stress levels over the pyramid surface.

2.4.2.2 star Automatic Type Coercion of Arrays

If you are working with lattice data and coordinate arrays, you can use the automatic type coercion facility. You can write a module's user function to operate on a single data type, such as a float, but allow the module to accept any primitive type as input on a specific port. The Module Builder then generates code to coerce data on that input port from its native primitive type to the primitive type required by the function argument to which the port is connected.

The Module Builder applies its coercion rules automatically during port and argument specification, based on the selections made in the Lattice Constraints window and the function arguments.

At run-time, the data is converted into the desired type, copied into memory, then passed to the computational function. The coercion proceeds as a series of individual coercions. Figure 2-4 shows the traversal path and the coercion pairs.

Sequence of Type Coercions

Figure 2-4 Sequence of Type Coercions


The C language process is:

D => F => LI => SI => B, or double to float to long int to short int to byte.

The Fortran language process is:

double precision => real => integer => integer*2 => character*1.

Out-of-range data is clipped to the acceptable range. Such coercions as

B => SI => LI => F => D cause no loss of precision.

The following paragraphs describe the coercion process:

Double to Float

Although converting from double to single precision involves a significant loss of precision, it is used when single and double versions of internal module code are not both available.

Float to Long Int

Float to Long Int coercion clips to the range of signed long integers, using the integer representation of the host computer and rounded to the nearest representable integer.

Long Int to Short Int

The short int type stores integers between 0 and 65535. The translation maps [0,65535] to [0,65535] and clips integers outside the range to 0 or 65535.

Short Int to Byte

The byte type stores integers from 0 to 255. Bit data and encodings of other data can also use this type, but in type coercion, a byte is used in integer form. The translation from integer to byte maps [0,255] to [0,255] and clips integers outside the range to 0 or 255.

Note

This feature makes module writing and integration much easier for the module writer, but it also requires time and a great deal of memory on the part of IRIS Explorer. It may be more efficient to write a module so that it accepts the correct data type without any coercion. This way, you will avoid the considerable memory bloat that is the cost of coercion.

2.4.2.3 Returning to the Port Window

Click OK to have your constraints accepted and to close the window. You are returned to the Input Ports window. Click OK to accept the port specifications and to return to the Module Builder main window.

2.4.3 Setting Pyramid Constraints

You can use the cxPyramid data type in a variety of ways. Some pyramids are built with every layer specified, and others are constructed in compressed form, with reference made to a standard structure in a pyramid dictionary for the compressed layers. The Pyramid Constraints window (see Figure 2-5) lets you specify whether the port can accept compressed or uncompressed pyramids and also establishes the level at which compression can take place.

For details on pyramid compression, see Chapter 4, "Using the Pyramid Data Type".

2.4.3.1 Defining Pyramid Constraint Fields

Click on the check boxes and radio buttons to make your selections.

Num Layers

You can specify a pyramid with any number of layers. For example, if you select 2 and 3, the port will accept 2D and 3D pyramids only.

Base Lattice

The base lattice is the lattice at the base of the pyramid. You can stipulate that the base lattice be required, optional, or absent.

Pyramid Constraints Window

Figure 2-5 Pyramid Constraints Window


Pyramid Compression mode

You can select one or more compression modes. You can set cx_compress_none ON and the other two options OFF to prevent the module from accepting or producing any compressed pyramids. The cx_compress_unique option accepts compressed pyramids based on a single type of element in a pyramid dictionary, such as a brick, and cx_compress_multiple accepts those made up of two or more elements, such as bricks and tetrahedra.

Compression Dimension

Indicates the level at which compression takes place, for example, at the 1D, 2D, or 3D level. The levels below this are specified only indirectly by reference to a pyramid dictionary.

Figure 2-5 shows these default settings for pyramid constraints:

  • The pyramid must have four layers of lattices with connectivity information, that is three sets of lattice data and connections, and the base lattice.

  • The base lattice, which is the 0th level lattice, is required.

  • The port can accept pyramids in all three compression modes, i.e., uncompressed pyramids and compressed pyramids containing a variety of elements.

  • The port can accept pyramids that have been compressed at the third level. That is, compressed pyramids must be 3D structures.

2.4.3.2 Returning to the Port Window

Click OK to have your constraints accepted and to close the window. You are returned to the Input Ports window. Click OK to accept the port specifications and return to the Module Builder main window.

2.4.4 Defining Output Ports

To display the Output Ports window (see Figure 2-6), click the Output Ports button on the Module Builder main window. The Map Editor uses the output port definition to decide whether a legal connection can be made between it and a given input port on another module.

Output Port Window

Figure 2-6 Output Port Window


2.4.4.1 Defining Port Characteristics

The Output Ports window resembles the Input Ports window, except that it has only two fields, the port name and the data type.

The example in Figure 2-6 shows one output port with the port name Output. The data type it produces is cxGeometry, this meaning that the output port must be connected to an input port that accepts geometry data. For more details on the Geometry data type, see Chapter 5, "Using the Geometry Data Type".

If you are creating an output port that produces a lattice, you set the data type to be cxLattice and the Lattice Constraints window appears again. Set the constraints for the output lattice in exactly the same way as you would set those for the input lattice.

2.4.4.2 star Checking the Port Status

The Output Ports window does not require you to set the status flag because output ports are optional by definition; the module's firing pattern depends on the state of the input ports, not the output ports. A module that has the correct connections and data on its input ports will fire whether or not any output ports are wired.

You can write a smart module, that is, one that can track whether its outputs are wired and block certain computations if they are not connected. For example, the Render module has a camera output port for which data is calculated and output only if the port is wired.

You can write code for a module with two output ports to check which output is wired and compute the data for only that one.You can then write a hook function for connect output and call the API function cxFireASAP from that hook function so that when you wire up the previously unwired output, the module will fire and send the new data downstream.

For more details on hook functions see Chapter 9, "Advanced Module Writing".

2.5 Defining Function Arguments

When you define the function arguments, you are specifying the interface between the module itself, defined here as the Module Control Wrapper (MCW), and the user function. The Module Builder uses this information to generate bridging code, which takes the form of the Module Data Wrapper (MDW). This is the module's internal interface.

You use the Build Options control panel to specify whether or not you want the Module Builder to write the MDW for you.

star You can take control of the interface and write your own interface code, in which case you do not need an MDW. If you write your own bridging code, you need not define the function arguments. Simply leave the Function Args and Connections windows blank.

For more details on writing wrapperless modules see Chapter 9, "Advanced Module Writing".

You must enter the function name in the Func Name slot in this window (see Figure 2-7 and Figure 2-8) even if the module has no MDW; otherwise, the module will not build.

2.5.1 Creating an Argument List

The Module Builder requires you to list each function argument and its correct type and referencing style so that the MDW can be written correctly. To display the Function Arguments window (see Figure 2-7), click the Function Args... button on the Module Builder main window.

The examples in Figure 2-7 and Figure 2-8 show the argument lists for the VectorGen and ReadPhoenics modules. The fields in the Function Args window are described below.

2.5.1.1 Func Name

The Func Name is the name of the user function or subroutine. It is repeated at the bottom of the window, along with the complete list of arguments. The Module Builder uses this field to create a call to the function.

Type the name in the text type-in slot exactly as it appears in the user function file.

2.5.1.2 Language

The Language button of the Function Args window allows you to select the language in which the user function or subroutine is written. The Module Builder uses this field to determine the calling conventions for the wrapper code (which is generated in C).

Select C, Fortran, or C++ from the Language pop-up menu.

Function Arguments Window for a C Function

Figure 2-7 Function Arguments Window for a C Function


2.5.1.3 Arg Name

The Arg Name section of the Function Args window lists the function argument names, entered in the correct calling sequence. The Module Builder uses this list to establish the calling sequence of arguments for the function when it creates the Module Data Wrapper (MDW).

  • Type the name of the first argument and press <Enter> to get a new slot.

  • Use the Edit menu to copy or reposition function arguments in the list.

  • Press <Enter> to update the calling sequence when you have entered a new argument, or use OK if all the new arguments have been added.

  • To remove an argument from the calling sequence, simply delete the name of the argument from the list.

You can enter up to 45 function arguments per function. Each argument must have a unique name. The Module Builder renames duplicate arguments.

Function Arguments Window for a Fortran Subroutine

Figure 2-8 Function Arguments Window for a Fortran Subroutine


2.5.1.4 Type

The Type section of the Function Args window lets you select a data type for each argument variable. These differ for C/C++ and Fortran.

Figure 2-7 shows the data type options menu for a C function. The menu is the same for C++ functions. Figure 2-8 shows the options for a Fortran subroutine.

Select one option from the menu for each argument.

Table 2-2 shows the relationship between the C and Fortran primitive data types that IRIS Explorer recognizes.

Table 2-2 Primitive Data Types in C and Fortran

C and C++ Fortran
char (byte) character*(1)
short integer*2
int integer
long integer
int logical
float real
double double precision
pointer integer
cx* integer

star In the C and C++ options menu, IRIS Explorer data types or subsidiary types (cx<DatatypeName>) are followed by an asterisk (*). They are automatically passed by reference because they are arrays written in C. In Fortran, you refer to the IRIS Explorer data types by using integers that contain a pointer to the C data structure. The C structures that are referenced can be manipulated through the Fortran API, but not directly in the syntax of the language. The pointers are uniformly treated as opaque handles that are passed around in integer variables.

2.5.1.5 References

The References section of the Function Args window lets you set the method whereby the argument variable is passed. Figure 2-7 shows the data type options menu for a C function. The menu is the same for C++ functions. Figure 2-8 shows the data type options menu for a Fortran function.

The choices are by value or reference in C or C++, by reference only in Fortran. The reference menus are shown in Figure 2-9 and Figure 2-10, and described in more detail in Table 2-3 which shows the choices for C, C++, and Fortran.

Note

The term references is used here in the sense of a reference in the C language, rather than a reference in C++, which is slightly different.

Function Argument Reference Menu for C

Figure 2-9 Function Argument Reference Menu for C


Function Argument Reference Menu for Fortran

Figure 2-10 Function Argument Reference Menu for Fortran


Table 2-3 Function Argument References

C and C++ Fortran Purpose
Scalar Scalar Used for passing in a single value. Scalars are passed by value in C and C++
Array Array Used for passing in or modifying a data array. Expects multiple values. Arrays are passed by reference in C and C++.
& Scalar [a] Used for passing a value out by means of the argument list. Passes a pointer to a scalar.
& Array Array Pointer Used for passing out an array by passing a pointer to an array reference, such as an array allocated in the function. Also useful for returning a pointer to allocated storage.

[a] Because Fortran always passes parameters by reference, there is only one option, Scalar, for passing Fortran scalar values.


2.5.1.6 Return Val?

The choices in the Return Val? section of the Function Args window indicate whether the user function or subroutine returns a value. If it does, you must select a type and reference as you did for each argument. The return value can be used when you set up connections from the function arguments to the output ports (see Section 2.5.2).

When you have entered and defined all the function arguments in their correct calling sequence, click OK to save the argument list and close the window.

2.5.2 Connecting Arguments to Ports

The user function arguments, once defined, must be associated with the input and output ports so that incoming data goes to the correct function arguments for processing before being passed in turn to an output port.

To display the Connections window, click on the Connections button on the Module Builder main window. The Connections window displays graphically the links between inputs, function arguments, and outputs. When you first create a module, no connection wires are shown. The wires appear after you have used the mouse to make a link between an argument and a port.

Figure 2-11 shows the connections between the function arguments for VectorGen and its ports. There are two input ports, Input and Scale Factor, and one output port, Output. The function arguments are listed in the center column. The open Port menu in the center of the window is the Connections menu for Scale Factor, which passes the parameter data type.

There are also three pseudo input ports listed under Input Ports. These are described in Section 2.5.2.5.

Connections Window

Figure 2-11 Connections Window


2.5.2.1 Understanding the Connections Menu

Each port has a Connections menu that gives its data type structure and lists the constituent elements of the data type. The number of dots in front of each element name indicates how many levels below the top-level data structure you have penetrated.

For example, in Figure 2-11 it is clear from the menu that a link has been made between the Value element in the Parameter structure and the Scale Factor function argument. This means that any data value passed from an upstream module to the Scale Factor input port of VectorGen is sent in turn to the Scale Factor function argument. The Value element has one dot in front of it; it is one level below the Parameter structure and on a par with the Type element.

Lattice, pyramid, and pick data types have several substructures that can be wired to or from function arguments. Figure 2-12 shows the Connections window for AtomicSurf, which has an input port, Molecule, that accepts the pyramid data type. Part of the Connections menu for this port is shown. As you can see, Pyramid Structure, which includes the entire contents of the data type, is connected to the function argument ipyr.

The other items on the menu are the substructures of the pyramid data type (more accurately, of the base lattice of the pyramid. For more on the pyramid structure, see Chapter 4, "Using the Pyramid Data Type"). They include:

  • individual scalars, such as the number of variables per node (Num Data Variables)

  • arrays such as the dims vector (Num Dimensions) and the data and coordinate arrays (Data Array and Coord Array)

  • C data and coordinate structures, such as Data Structure and Coord Structure, of which the scalars and arrays are constituents

You can write function arguments that accept data from any one of these substructures if you wish.

You can use a pointer to the C structure to pass a complete IRIS Explorer data type directly to a function argument if the user function is written in Fortran or the function argument is broken into smaller elements of the data type.

Connections Menu for the Molecule Port

Figure 2-12 Connections Menu for the Molecule Port


2.5.2.2 Creating a Link

The physical mechanism of wiring ports and functions together and disconnecting them again is exactly the same as that used in the Map Editor. Click on an input port with the right mouse button, select the element to be wired, and then click on the function argument (or output port). The Connections menu for function arguments usually has only one item, Select (unless it is an array, or if it has a connection from a pseudo input port). To break a connection, simply reverse the process.

The --> icons in the Connections menus show which elements of an input or output port are associated with each other or with function arguments.

You can make only one connection to a given function argument from an input port, and only one connection from a given function argument to an output port; however, you can make several connections from an input port or to an output port. For example, the Input input port in Figure 2-11 has four connections, one to each of four function arguments.

All function arguments must have a connection to either an input or an output port, but all ports do not necessarily have to be connected.

2.5.2.3 Highlighted Connections

Figure 2-13 shows how connection options are highlighted in the Connections window. When you select a Port menu item or function argument as the first member of a connection, the selected item turns white, and the available options from which you can select the second member of the connection (i.e., every one in the other two columns in the Connections window) turn dark grey.

Highlights in the Connections Window

Figure 2-13 Highlights in the Connections Window


2.5.2.4 Passing Default Values

In many cases, you can connect the top-level data structure on the input port directly to the top-level data structure on the output port in order to pass default values. For example, in a module that accepts a lattice on an input port and outputs a lattice on an output port, you can connect the cxLattice structure at the top of the Input Port menu to the cxLattice structure at the top of the Output Port menu.

In this way, all data that is not affected by a function argument will be passed by default from the input port to the output port. The members of cxLattice that were operated on by function arguments will acquire new data that is passed in turn to the output port to replace the old data. See, for example, the Connections window for the ChannelSelector module in Figure 1-10, where most of the parts of the input lattice are passed directly to the output lattice, while nDataVar and the data part of the output lattice are set explicitly via the function arguments.

2.5.2.5 Using Pseudo Input Ports

At the bottom of the Input Ports list are the three so-called pseudo input ports, <<Storage>>, <<Constant Value>>, and <<Extern>> (see Figure 2-14 and Figure 2-15). They cannot be wired directly to output ports, unlike the regular input ports. In most circumstances you would not need to use the facilities of pseudo-ports, but they can be particularly useful if you do not wish to re-write or are unable to change your existing source code. For example, this would be the case if your existing source code has function parameters that have constant values in the context of your module. You would set these values using the <<Constant Value>> pseudo-port.

You can connect the pseudo-ports to function arguments to do these things:

  • allocate storage for the user function: <<Storage>>

  • set function arguments to constant values: <<Constant Value>>

  • star allow the user function to reference an external function: <<Extern>>

Once you have wired the <<Storage>> or <<Constant Value>> ports to a function argument, the argument's Connection menu acquires a second item. To set the argument, click on it after the connection has been made.

The second item, Set Storage or Set Constant Value, when selected, invokes the Set argument value window. Figure 2-14 and Figure 2-15 show the connection between the <<Constant Value>> input port and the ilat function argument in AtomicSurf, the ilat Connections menu, and the value set (NULL, which is the default). See Section 2.5.2.6 for more information about temporary storage.

You can enter any legal C expression in the text slot, including references to library or user-supplied routines that can be resolved at link time. It is a good idea to comment on your entries for future reference.

<<Extern>> can only be wired to function arguments whose variable name refers to the name of another function or subroutine. This function must be in a module source file and referenced in the User Func File slot in the Module Builder main window (see Figure 2-1).

Setting a Constant Value for a Function Argument; bringing Up the Argument Value Window

Figure 2-14 Setting a Constant Value for a Function Argument; bringing Up the Argument Value Window


Setting a Constant Value for a Function Argument; setting the Value in the Window

Figure 2-15 Setting a Constant Value for a Function Argument; setting the Value in the Window


You can use <<Extern>>:

  • to reference numerical analysis routines. For example, you can specify the name of a routine that executes a particular action, such as a convergence test. The name of the function argument should be identical to the function name. The function argument is then the function returning a long scalar.

  • in an integrator module, to pass in either a function to be integrated, or a derivative function.

Here is an example of how to use <<Extern>>. Suppose you have a function named calcDerivative and you want the user function to use this derivative calculating function in its global optimization calculation. You would need to:

  • set a function argument name to calcDerivative

  • wire <<Extern>> to the calcDerivative function argument

  • treat that function argument as a function or subroutine in the user function.

2.5.2.6 star Allocating Storage Space

You can use the <<Storage>> pseudo input port to allocate temporary storage space which may be used inside the user function. The space can be used as follows:

  • For temporary storage in the user function. This space is allocated by a <<Storage>> connection to a function argument but not wired to an output port. This may be especially useful for Fortran programmers who wish to use dynamically allocated memory.

  • To hold data that is entered in the user function and then assigned into an output port, where the storage allocation is not done explicitly inside the user function.

Two API routines are useful for computing the size of arrays for wiring to the storage allocator (see the IRIS Explorer Reference Pages for details):

  • cxDimsProd computes the total number of data variables in a lattice.

  • cxDimsSum computes the length of the perimeter coordinate vector for a lattice.

After the user function has been invoked, the allocated storage is freed. The Module Data Wrapper automatically frees work space that has been held temporarily by the user function. The Module Builder uses reference counting to maintain temporaries assigned to output ports as part of the output port list.

You must free any temporaries that you create inside the user function.

2.5.2.7 Mapping Rules

These are the basic rules to follow when connecting ports and arguments:

  1. The mapping to an output port must describe the contents of the output data structure fully. Every element in an output port should have a source, either a function argument or an input port; otherwise the Module Data Wrapper may not have enough information to build the data set and errors will appear (not immediately, where they can be picked up, but later, and apparently mysteriously).

  2. On Port menus, you can wire to the data type structure itself or to all its component elements. For example, instead of wiring to Pyramid Structure in Figure 2-12, you can wire to all the substructures listed as separate elements. Similarly, you can wire to Data Structure or you can wire to Num Data Variables, Primitive Data Type, and Data Array, which are subelements of the Data Structure component.

  3. Wire input ports to function arguments and/or output ports. Ports can have multiple connections (one to each part of the data structure on the Port menu). Function arguments can have only one input and only one output (but need not have any outputs).

  4. If you make more than one connection to a function argument or a Port menu element, you may duplicate and/or overwrite data as it is passed, causing errors. In particular, if you wire more than one connection to an output port element, you run the risk of leaking memory.

  5. Every function argument must be connected to an input port, a pseudo input port, or an output port. You can only leave one unconnected if you do not intend to use it.

  6. You can wire an input lattice data type to an output data type, but the values of nDim (Num Dimensions) and dims (Dimensions Array) cannot be changed. This is a mistake easily made because the data and coordinate substructures (cxData and cxCoord) each have a copy of these values.

2.5.2.8 star Checking the State of Input Ports

At the bottom of each Input Port menu is the Data Changed item (see Figure 2-11). You can use it to record whether the module received new data on that particular port during the most recent firing. Depending on the answer, the user function may then act to collect new data from the port, or ignore the old data. Data Changed returns a long integer with the value 0 (to indicate old data) or 1 (to indicate that new data has arrived on the port).

You can wire Data Changed to any scalar function argument.

2.5.2.9 star Copying Arrays

A function argument that is referenced as an array has a Copy option added to its Connections popup menu. Depending on whether the function is to overwrite the contents of the array, this allows you to choose between passing a pointer to the array itself (see Figure 2-16) and making a copy of the array and passing a pointer to the copy (see Figure 2-17).

Connections Popup Menu for an Array Function Argument; Passing the Array Itself

Figure 2-16 Connections Popup Menu for an Array Function Argument; Passing the Array Itself


Connections Popup Menu for an Array Function Argument; Passing a Copy of the Array

Figure 2-17 Connections Popup Menu for an Array Function Argument; Passing a Copy of the Array


This copy facility is important if you intend to modify the contents of an argument whose original values come from an input port. Since data from an input port resides in shared memory, it cannot be written into without polluting the data space of other modules and causing a module in the map to fail.

When the Copy option is selected, it tells the Module Builder to make a working copy of the array so that you can modify it. This is useful even if you intend to use it only as a temporary work space, without writing it to an output port.

For example, in a module that manipulates the grid data of a lattice without changing the coordinates, data will be overwritten; therefore, the Copy option should be selected. If not, a pointer to the array is passed to the user function, which is usually more inefficient.

Scalars are always copied before being passed to the user function.

2.6 Creating File Prototypes

You can use the Prototypes menu in the Module Builder main window (see Figure 2-18) to create a prototype user function and a help file for your new module.

The Prototypes Menu

Figure 2-18 The Prototypes Menu


2.6.1 The User Function Prototype

The Module Builder will create a user function prototype for you if you have not already written it, or do not have existing code for it. You must already have named the function, selected its language and laid out the calling sequence of the function arguments in the Function Args window. The Module Builder uses this information to write a stub function that is the user function as described, but without any action.

When you select Create function prototype from the Prototypes menu, a window appears, in which you enter the name you want for the new user function (see Figure 2-19). The file is created in the current working directory (usually the same directory as the other module files). If a file with the name you have selected already exists, the prototype file is renamed <filename>.user.

Function Prototype Window

Figure 2-19 Function Prototype Window


This example shows a basic prototype for a user function myModule.c. The module has lattice input and output ports and parameter input ports. The user function is written in C and has 12 function arguments.

#include <cx/cxLattice.api.h>
#include <cx/cxParameter.api.h>

#ifdef __cplusplus
 extern "C" {
#endif

 void MyModulec (
 long    dim,
 long    dim,
 long    dim,
 long    *dims,
 long    nDataVar,
 double  *dataArray,
 long    nCoordVar,
 float   *coordArray,
 long    *nDimOut,
 long    *dimsOut,
 double  *dataOut,
 float   *coordOut );

#ifdef __cplusplus
}
#endif

/* ------------------------------------------- */
/* ------------------------------------------- */

 void MyModulec (
 long         Idim,
 long         Jdim,
 long         Kdim,
 long         *dims,
 long         nDataVar,
 double       *dataArray,
 long         nCoordVar,
 float        *coordArray,
 long         *nDimOut,
 long         *dimsOut,
 double       *dataOut,
 float        *coordOut )
{
}

Once you have the prototype file, you can open it and finish writing the user function at your leisure. If you create the prototype file before you have defined the function in the Module Builder, the file will not contain the function arguments.

2.6.2 Hook Function Prototypes

You can generate a function prototype for each hook function you want to write for a module. To create the hook function, use the Hook functions... option on the Module menu (for details, refer to Section 9.5). Then select Create function prototype from the Prototypes menu to create the hook function prototypes.

Here are examples of the two types of hook function prototype in Fortran.

C Hook function for IRIS Explorer module actions. /*Remove hook /*
      SUBROUTINE FREMOVE()
      print *,'Remove hook function called.'

      RETURN
      END

C Hook function for IRIS Explorer module actions. /* Connection /*
      SUBROUTINE FCONNIN( PORTNAME, LINKTAG)
      CHARACTER*(*) PORTNAME
      *INTEGER LINKTAG
      print *,'Connect input hook function called: ',
     1*PORTNAME, LINKTAG

      RETURN
      END

2.6.3 The Help File

If you want the Module Builder to create a help file template for you, select Create document prototype from the Prototypes menu. The help file is saved in the working directory with the name <modulename>.doc.

A skeleton file contains the port names, data types, and other variables that you have specified during module building. You should add any descriptive information you want users to read when they invoke help for your module. This information should include a description of the module's algorithm, ports, known problems or limitations, and related modules. Be careful not to disturb any line beginning with %%.

This example shows what the prototype help file for myModule.c, called myModule.doc, would look like.

myModule
%%------------ DESCRIPTION ------------
<Replace this with your module description>
%%------------ INPUTS ------------
%PORT Input 1-D
%TYPE Lattice
%CONSTRAINT 1-D
%CONSTRAINT curvilinear
<Describe the purpose of the port here>
%PORT I dimension
%TYPE Parameter
<Describe the purpose of the port here>
%PORT J dimension
%TYPE Parameter
<Describe the purpose of the port here>
%PORT K dimension
%TYPE Parameter
<Describe the purpose of the port here>
%%------------ WIDGETS ------------
%%------------ OUTPUTS ------------
%PORT Output 3-D
%TYPE Lattice
%CONSTRAINT 3-D
%CONSTRAINT curvilinear
<Describe the purpose of the port here>
%%------------ PROBLEMS ------------
<Describe any known problems/limitations here>
%%------------ SEEALSO ------------
<List associated or related modules here>

If you are modifying an existing module and changing some aspect of its control panel (such as using different widgets or new ports), select Update document from the Module menu and its submenu Options (see Figure 2-20) to update the existing <modulename>.doc file and preserve any existing annotation.

2.7 Building Modules

Control over module building is provided by the Module menu (see Figure 2-20). This gives a way to set defaults for the module and to build, install and clean the executable. The resource and document files may be updated from this menu. Finally, it also allows you to switch on debugging when the module is built. All the menu options except Hook functions... are discussed below. Hook functions are discussed in Section 9.5.

The Module Menu

Figure 2-20 The Module Menu


2.7.1 Selecting Build Options

You can set a number of options in the Build Options control panel (see Figure 1-19). This is invoked by selecting Options... from the Module menu. The options on the control panel are:

Write Wrapper Code

Lets you select an automatically generated Module Data Wrapper. The default is Yes.

MCW type

Lets you select the type of Module Control Wrapper (MCW) for linking. Most modules link against the default MCW, but a few manipulate windows in the Windows environment independently of the IRIS Explorer graphical user interface (see examples in Section 9.1.2). These modules should be linked against the Windows-MCW. The MFC-MCW option allows MFC classes (such as CString) to be used.

Alternate Executable

Lets you specify whether your module has its own executable or whether it will use the same executable as some other modules in order to save disk space. For more information, refer to Section 9.2. The default is No.

Additional Files to Install

Lets you stipulate any script or program files you want installed with your module. Used when you are writing an interpreter such as a LatFunction-based module. For more information, see Section 2.7.2 and also Chapter 10, "Module Prototyping with Shape".

Loop Controller Status

Lets you determine whether or not the module is a loop controller, and if so, whether it controls a while loop or a repeat loop. For more information on loop controller modules, see Section 9.3. The default is None.

Collaborative Status

Lets you determine whether or not the module can be used as a collaborative module. For more information on collaborative modules, see the IRIS Explorer Collaborative User's Guide (Windows NT/2000).

2.7.2 Building and Installing Modules

The remaining options on the Module menu are used to create, install and delete the module executable, and to update the resource and help files. Output from these operations appears in the Log Window at the bottom of the Module Builder main window.

Build

Saves the module resources and uses them to generate wrapper code for the module. It then compiles and links the wrappers and the module source to create a module executable. The executable is created in the current working directory (usually the same location as the module resource file).

Build and Install

As Build, except the module is, in addition, installed in %EXPLORERUSERHOME%\modules. The environment variable defines the directory that is the root of your personal installation tree. More information on this, including how to set its value, can be found in Section 2.8. If this was not set when the Module Builder was started, then it tries to install the module in %HOMEDRIVE%%HOMEPATH%\explorer\modules. Finally, if this value cannot be determined, then the Module Builder will install in %EXPLORERHOME%\modules, which is the location of the system modules.

star Some modules, particularly those that contain an interpreter that reads a script file, have auxiliary files that need to be installed when the module is installed. For instance, LatFunction-based modules have a script file (named in the Program File widget) that should be installed to go along with the module. This is automatically handled by the interpreter option when you specify an alternate executable, but you can use the Build Options control panel to install other files as well if you wish (see Section 2.7.1).

Update Document

Updates both the modulename.mres file and the document help file (modulename.doc). You must have write permission on both module files to do this. The document file is overwritten and the original .doc file is saved as a .bak file. If you have information in the old file that you want retained in the new file, you must edit the new .doc file accordingly. These are ASCII files, so this is easily done. The modulename.mres file is overwritten without any backup file being saved.

Clean

Deletes the module executable, together with all object files (*.obj), in the current working directory.

2.7.2.1 star Building Modules Outside of the Module Builder

The Module Builder is an easy-to-use tool which enables the creation of IRIS Explorer modules in the Windows environment. However, some users may prefer to do some of this work with a text-based command line via the MS-DOS Command Prompt window. For the sake of completeness then, this section briefly describes a set of commands which duplicate some of the functionality of the Module Builder. They can be used to build and install a module executable, starting from the module resource file (which can only be created using the Module Builder). The commands invoke MS-DOS batch files, which can be found in %EXPLORERHOME%\bin.

Each of these commands takes the module name as its single parameter, e.g.:

cxmkmf myModule

The commands are:

cxmkmf

Builds a module Makefile, using the module resource file.

cxmake

Compiles and links the module source to produce the module executable, using the module Makefile.

cxinstall

As cxmake, except the module is, in addition, installed in %EXPLORERUSERHOME%\modules. The script will exit with a warning message if you have not set the environment variable.

cxclean

Deletes the module executable, together with all object files (*.obj), in the current working directory.

2.8 Configuring the Build Environment

The Module Builder handles the details of the module building and installation environment, and also the creation of user-defined data types. The way in which these are defined, and the use of the Module Builder to build the types is described in Chapter 8, "Creating User-Defined Data Types". New modules and types are placed by the Module Builder in a personal installation tree whose location is given by the EXPLORERUSERHOME environment variable.

2.8.1 Defining EXPLORERUSERHOME

An environment variable such as EXPLORERUSERHOME may be defined in a number of ways. One way is via the Settings entry on the Start menu. The interface for setting environment variables is accessed by clicking on the Environment tab in the System control panel. Changes to environment variables that get made via the control panel are retained after logging out.

Alternatively, if you are running the Module Builder from an MS-DOS Command Prompt window, you can set the environment variable before starting mbuilder using the command:

set EXPLORERUSERHOME=c:\users\foo\explorer

Changes to environment variables that get made via a command prompt window are forgotten when the window is terminated. Irrespective of the way in which it is set, a new value for EXPLORERUSERHOME is only picked up by subsequent instantiations of the Module Builder. If there is no value set for this variable, the Module Builder uses %HOMEDRIVE%%HOMEPATH%\explorer.

This is the directory structure created under %EXPLORERUSERHOME%:

%EXPLORERUSERHOME%\
modules\

All the necessary components for modules, including the module resource (modulename.mres) file and the module executable.

types\

The description files for user-defined types. These always have the filename suffix .type.

lib\

The automatically generated API library for user-defined types.

include\cx\

The C and Fortran header files for user-defined types.

2.8.2 Debugging a Module

IRIS Explorer modules are compiled by default with the compiler's optimizer option turned on. To create a debuggable module executable from within the Module Builder, switch on the Debug option on the Module menu (see Figure 2-20) before building it. Alternatively, if building the module from an MS-DOS Command Prompt window, debugging may be switched on by setting the DEBUG environment variable before you issue the cxmkmf command (see Section 2.7.2.1):

set DEBUG=1
cxmkmf
cxmake myModule

Having created the debuggable module executable (by whichever route), you can then use it to debug the module. To do this, start IRIS Explorer and bring myModule (along with any other modules that you may require) into the Map Editor. This starts the module running as a process called myModule, to which you can attach your debugger. For example, when using the Microsoft Developer Studio you can attach the debugger by clicking on the Build option and selecting Start Debug and then Attach to Process... from the sub menu. This will bring up a window which displays all user processes. Search for the line containing the name of your executable (which is usually the same as the module name) in this display. By default, the processes are ordered with the most recently started one at the top, so yours should be at or near the top of the list. It will start with a string like:

myModule

Double-clicking on this string will attach the debugger to the process. Load the source of your module into the debugger and you can start your debugging session.

Note

It is important to ensure that you load the correct source file(s). The Module Builder creates new versions (often via a preprocessing step) of some types of source file, and it is these that get passed to the compiler, and which must be loaded into the debugger. More specifically, for a Fortran source file such as myModule.f a new file called myModule.tmp.f is created by using the C++ preprocessor to expand any macros and conditional compilation directives. C++ source files with names such as foo.C or bar.c++ are copied by the Module Builder to foo.cxx and bar.cxx respectively; this is because Microsoft Visual C++ only accepts file names with extensions of .cxx (or .cpp). C source files, which have a suffix of .c are passed directly to the compiler by the Module Builder. Thus, you should only load source files with extensions of .tmp.f, .c, .cxx or .cpp into the debugger. The new files created by the Module Builder are deleted when either the Clean or cxclean command is issued.

Set a breakpoint at a suitable point within your module (for example, the entry point to the user function), then fire the module by passing it new input data. The debugger will stop execution at your breakpoint, and you can begin to debug your code.

Debugging information about a module may also be generated by selecting the Replace (Debug) option from the module's pop-up menu or by selecting the Cancel option when replacing a module that has died. (This is different from the Cancel button on the Application Error pop-up message, which opens the Visual C++ debugger. Click OK on this messagebox, then click Cancel on the "Do you want a new ... module to replace it?" messagebox.) When the module is enabled and fired, debugging information showing which IRIS Explorer API functions have been called and their return values is displayed in the Debugging window.

2.9 Examples of Simple Modules

Here are user functions for two simple modules, coded in C and in Fortran. The first one is a channel selecting module, and the second is a simple pass-through module. The source code for these modules is in %EXPLORERHOME%\src\MWGcode\ModuleBuilder\C\* and %EXPLORERHOME%\src\MWGcode\ModuleBuilder\Fortran\*.

2.9.1 The ChannelSelect Module

This module selects a channel from an input lattice having multiple channels. It is a variant on the simple module used as the example in Chapter 1, "Building a Module".

Example 2-1 C Version of the ChannelSelect Module:

/* Example module that does a channel select */

#include <cx/Typedefs.h>
#include <cx/DataTypes.h>
#include <cx/DataAccess.h>
#include <cx/UI.h>
#if (defined(_AIX) || defined(__sun))
#include <cx/cxOs.h>
#else
#include <cx/cxString.h>
#endif

void channel(long which, cxLattice *inLat, cxLattice **outLat)
{
  long xLo, xHi, i, nData, dataLen, w;
  long nDim,nDataVar,hasData,hasCoord,nCoordVar,*dims;
  cxCoord *coord;
  cxPrimType primType;
  cxCoordType coordtype;
  cxData *data;
  char *oldData, *dataArray;

  /* Get all the information about the input lattice */
  cxLatDescGet(inLat,&nDim,&dims,&hasData,&nDataVar,&primType,
               &hasCoord,&nCoordVar,&coordtype);
  cxLatPtrGet(inLat,&data, (void **)&oldData,&coord,NULL);

  /* Reset the range of the slider
   * based on the number of input data variables
   */
  xLo = 1;
  xHi = nDataVar;
  cxInWdgtLongMinMaxSet( "Which Data", xLo, xHi);

  /* Reset range of widget if off-scale */
  w = which;
  if(w > xHi)
    {
    w = xHi;
    cxInWdgtLongSet("Which Data",xHi);
  }
  if(w < xLo)
    {
    w = xLo;
    cxInWdgtLongSet("Which Data",xLo);
  }

  /* Make a new lattice of the correct size */
  w -= 1;
  nDataVar = 1;
  *outLat = cxLatDataNew(nDim,dims,nDataVar,primType);

  /* Move the coordinate pointer from the old to the new lattice */
  cxLatPtrSet(*outLat,NULL,NULL,coord,NULL);

  /* Get the information about the new data array */
  cxLatPtrGet(*outLat,NULL,(void **)&dataArray,NULL,NULL);
  nData = cxDimsProd(nDim,dims,nDataVar);
  dataLen = cxDataPrimSize(data);

  /* Copy the data from the old array to the new array */
  oldData += dataLen * w;
  for(i=0;i<nData;i++)
    {
      bcopy(oldData,dataArray,dataLen);
      oldData += dataLen * xHi;
      dataArray += dataLen;
    }
}

Example 2-2 Fortran Version of the ChannelSelect Module:

C     Example module that does a channel select
C
      SUBROUTINE CHAN(WHICH,INLAT,OUTLAT)
C
      INCLUDE '/usr/explorer/include/cx/DataAccess.inc'
C
C     .. Scalar Arguments ..

#if defined(IS_64BIT)
      INTEGER*8     INLAT, OUTLAT, WHICH
      INTEGER*8     NDV, NDIM, P0, PTYPE, CTYPE, DLEN, HASCRD,HASDAT
      INTEGER*8     NDATA, NCV, XHI, W
      INTEGER*8     DIMS(1)
#else
      INTEGER       INLAT, OUTLAT, WHICH
      INTEGER       NDV, NDIM, P0, PTYPE, CTYPE, DLEN, HASCRD,HASDAT
      INTEGER       NDATA, NCV, XHI, W
      INTEGER       DIMS(1)
#endif

C     .. Local Scalars ..
      INTEGER            I, IER, J, K, L
C     .. Local Arrays ..

      CHARACTER          DARRAY(1), OLDDAT(1)
C     .. External Subroutines ..
      EXTERNAL           CXINWDGTLONGMINMAXSET, CXINWDGTLONGSET
C     .. External Functions ..
      EXTERNAL           CXDATAPRIMSIZE, CXDIMSPROD, CXLATDATANEW,
     *                   CXLATDESCGET, CXLATPTRGET
C     .. Intrinsic Functions ..
      INTRINSIC          MAX, MIN
C     .. Pointers to Lattice Structures ..
      POINTER (PDIMS,DIMS)
      POINTER (PDATA,DATA)
      POINTER (PCOORD,COORD)
      POINTER (POLDAT,OLDDAT)
      POINTER (PARRAY,DARRAY)
C     .. Executable Statements ..
C
C     Get all the information about the input lattice
C
      IER = CXLATDESCGET(INLAT,NDIM,PDIMS,HASDAT,NDV,PTYPE,HASCRD,
     *      NCV,CTYPE)
      P0 = 0
      IER = CXLATPTRGET(INLAT,PDATA,POLDAT,PCOORD,P0)
C
C     Reset the range of the slider
C     based on the number of input data variables
C     Reset range of widget to be inside the scale
C
      W = MIN(MAX(1,WHICH),NDV)
      CALL CXINWDGTLONGMINMAXSET('Which Data',1,NDV)
      CALL CXINWDGTLONGSET('Which Data',W)
C
C     Make a new lattice of the correct size
C
      W = W - 1
      XHI = NDV
      NDV = 1
      OUTLAT = CXLATDATANEW(NDIM,DIMS,NDV,PTYPE)
C
C     Move the coordinate pointer from the old to the new lattice
C
      P0 = 0
#ifdef WIN32
      IER = CXLATPTRSET(OUTLAT,P0,P0,PCOORD,P0)
#else
      IER = CXLATPTRSET(OUTLAT,P0,%VAL(0),PCOORD,%VAL(0))
#endif
C
C     Get the information about the new data array
C
      P0 = 0
      IER = CXLATPTRGET(OUTLAT,P0,PARRAY,P0,P0)
      NDATA = CXDIMSPROD(NDIM,DIMS,NDV)
      DLEN = CXDATAPRIMSIZE(PDATA)
C
C     Copy the data from the old array to the new array
C
      DO 40 I = 1, NDATA
         K = DLEN*(I-1)
         L = DLEN*((I-1)*XHI+W)
         DO 20 J = 1, DLEN
            DARRAY(K+J) = OLDDAT(L+J)
 20      CONTINUE
 40      CONTINUE
C
      RETURN
      END

2.9.2 The Pass-Through Module

This module copies all lattice input data to the output lattice, doubling the data values if they are of type float.

Example 2-3 C Version of the Pass-Through Module:

/* Example module to show the use of some lattice API functions.
 *
 * Input lattice coordinates are copied to the output lattice
 * Input lattice float data values are doubled for the output lattice
 * Other input lattice data values are copied to the output lattice
 */

#include <cx/DataAccess.h>
#include <cx/DataTypes.h>
#include <cx/cxLattice.api.h>
#include <cx/Typedefs.h>

void pass(cxLattice *inlat, cxLattice  **outlat)
{
  float          *outData;
  long            nDim, *dims, nDV, nCD;
  long            num_pts, i, hasData, hasCoord;
  cxData         *Data;
  cxPrimType      ptype;
  cxCoordType     ctype;

  /* Extract descriptive information about inlat */
  cxLatDescGet(inlat, &nDim, &dims, &hasData, &nDV, &ptype, &hasCoord,
               &nCD, &ctype);

  /* Duplicate the coordinates and data of inlat to outlat
   * if the input data are not of type float
   */
  if (ptype != cx_prim_float)
    *outlat = cxLatDup(inlat, 1, 1);
  else
    {
      /* Duplicate the coordinates of inlat to outlat
       * if the input data are of type float
       */
      *outlat = cxLatDup(inlat, 0, 1);

      /* Extract pointers to data structure and float data in inlat */
      cxLatPtrGet(inlat, &Data, NULL, NULL, NULL);

      /* Insert a data structure into outlat equal to that of inlat */
      cxLatPtrSet(*outlat, Data, NULL, NULL, NULL);

      /* Extract pointers to the float data in outlat */
      cxLatPtrGet(*outlat, NULL, (void **) &outData, NULL, NULL);

      /* Double all data elements for outlat */
      num_pts = cxDimsProd(nDim, dims, nDV);
      for (i = 0; i < num_pts; i++)
        *(outData + i) = 2.0 * (*(outData + i));
    }
}

Example 2-4 Fortran Version of the Pass-Through Module:

C     Example module to show the use of some lattice API functions.
C
C     Input lattice coordinates are copied to the output lattice
C     Input lattice float data values are doubled for the output lattice
C     Other input lattice data values are copied to the output lattice
C
      SUBROUTINE PASS(INLAT,OUTLAT)
C
      INCLUDE '/usr/explorer/include/cx/DataAccess.inc'
      INCLUDE '/usr/explorer/include/cx/Typedefs.inc'
C
C     .. Scalar Arguments ..
#if defined(IS_64BIT)
      INTEGER*8       INLAT, OUTLAT
      INTEGER*8       NDV, NDIM, P0, PTYPE, CTYPE,  HASCRD, HASDAT
      INTEGER*8       NDATA, NCV,DATPTR, NUMPTS
      INTEGER*8       DIMS(1)
#else
      INTEGER         INLAT, OUTLAT
      INTEGER         NDV, NDIM, P0, PTYPE, CTYPE,  HASCRD, HASDAT
      INTEGER         NDATA, NCV, DATPTR, NUMPTS
      INTEGER         DIMS(1)
#endif
C     .. Local Scalars ..
      INTEGER         I, IER
C     .. Local Arrays ..
      REAL            OUTDT(1)

C     .. External ..
      EXTERNAL        CXDIMSPROD, CXLATDESCGET, CXLATDUP, CXLATPTRGET,
     *                CXLATPTRSET
C     .. Pointers to Lattice Structures ..
      POINTER (POUTDT,OUTDT)
      POINTER (PDIMS,DIMS)
C     .. Executable Statements ..
C
C     Extract descriptive information about inlat
C
      IER = CXLATDESCGET(INLAT,NDIM,PDIMS,HASDAT,NDV,PTYPE,HASCRD,
     *      NCV,CTYPE)
C
C     Duplicate the coordinates and data of inlat to outlat
C     if the input data are not of type float
C
      IF (PTYPE.NE.CX_PRIM_FLOAT) THEN
         OUTLAT = CXLATDUP(INLAT,1,1)
      ELSE
C
C        Duplicate the coordinates of inlat to outlat
C        if the input data are of type float
C
         OUTLAT = CXLATDUP(INLAT,0,1)
C
C        Extract pointers to data structure and real data in inlat
C
         P0 = 0
         IER = CXLATPTRGET(INLAT,DATPTR,P0,P0,P0)
C
C        Insert a data structure into outlat equal to that of inlat
C
         P0 = 0
#ifdef WIN32
         IER = CXLATPTRSET(OUTLAT,DATPTR,P0,P0,P0)
#else
         IER = CXLATPTRSET(OUTLAT,DATPTR,%VAL(0),P0,%VAL(0))
#endif
C
C        Extract pointers to the real data in outlat
C
         P0 = 0
         IER = CXLATPTRGET(OUTLAT,P0,POUTDT,P0,P0)
C
C        Double all data elements for outlat
C
         NUMPTS = CXDIMSPROD(NDIM,DIMS,NDV)
         DO 20 I = 1, NUMPTS
            OUTDT(I) = 2.0*OUTDT(I)
   20    CONTINUE
      END IF
C
      RETURN
      END

[2] cxGeneric is a special case, a data type which can be wired to any other data type. It is described in Chapter 9, "Advanced Module Writing".