This chapter discusses some advanced aspects of module writing. It covers some of the more esoteric functions of the IRIS Explorer product, which you may be interested in if you are writing complex and powerful modules that push the capabilities of IRIS Explorer beyond everyday levels. Some of the topics describe advanced techniques, such as writing Hook Functions, and some are more explanatory, such as the section on Understanding Reference Counting. An in-depth knowledge of how IRIS Explorer operates allows you to make the most of the benefits that IRIS Explorer offers.
You need not read this chapter unless you are interested in learning more about IRIS Explorer or in implementing these techniques.
IRIS Explorer provides the module writer with many opportunities to make a choice between letting IRIS Explorer control a particular process in the module, or taking charge and running that process directly from the user function. As a result, you can write modules that operate at varying levels of independence from IRIS Explorer. Where the module writer settles on the continuum from total control to very little depends on current needs and programming expertise.
The major divide is between modules with a Module Data Wrapper (MDW) and those without. If you build a module without an MDW, you must make sure that your user function performs all the actions that the MDW would otherwise have done.
Figure 9-1 shows the range of module complexity. The modules in division 1 contain code that works anywhere, and is reusable in other programs and systems. Division 2 modules contain code, possibly written for other purposes, that can be made to work exclusively in IRIS Explorer without a lot of hard work, by adding cx* API routines and the IRIS Explorer data type structures to the basic function or subroutine. Modules in division 3 are written specifically for IRIS Explorer, designed to take advantage of all the flexibility and potential in the system, and the code is not reusable at all in other systems.
IRIS Explorer uses three controlling interfaces or "wrappers"
to mediate between the module's user function and the rest of the system.
They are the Module Control Wrapper (MCW), the Module Data Wrapper (MDW),
and the Generic Wrapper. The relationship between wrappers and user or
computational function is shown in
Figure 9-2.
Every module has a Module Control Wrapper (MCW), which is the module
data manager. The MCW contains the firing algorithm and provides interfaces
to input and output functions, including port and widget settings. The
MCW also manages all communication with other modules.
The firing algorithm is described in
Appendix B - The
Firing Algorithm. Information about the MCW's interface capacity is
contained in the API routines for all the input and output functions.
The Module Builder constructs two kinds of MCWs, the default and a special
form for X modules. The X Windows MCW uses the X
mechanism for handling scheduling.
The Module Data Wrapper (MDW) performs conversions between IRIS Explorer and
user-defined data types on the ports and the data format required by the user
function. You can thus develop modules with customized interfaces without having
to interact with the IRIS Explorer data types. Since you can create IRIS
Explorer modules from existing code, it is easy to fold a function into a module
without writing a line of source code yourself. Similarly, it is possible to
convert a library of routines into a library of modules with minimal effort.
The connection between the data type and the subroutine is mediated by the
Module Data Wrapper (MDW), which translates input data into a form the user
function can process, and then translates the result into a suitable form for
output. The Module Builder generates an MDW for a module by default. However,
you can choose not to have an MDW, in which case you must explicitly incorporate
the MDW functions in your user function. These include:
The Generic Wrapper is not accessible by the module writer, except indirectly
through the Hook Functions menu. It contains:
In general, when you build a module, the Module Builder writes code for the
Module Data Wrapper and generates a linkage to a default Module Control Wrapper
(MCW), or an X Window System
MCW if so selected. You make these choices using the build options control panel. This is invoked by
selecting Options... from the Build menu on the Module Builder
window. It offers you options for expanding individual control over the
interface between the user function and IRIS Explorer.
If you stipulate no automatically generated MDW, you must construct the
interface between the user function and the input and output ports yourself. To
build a module that has no Module Data Wrapper, follow these steps.
You must define the user function name in the Function Arguments window,
but you can skip the function definitions and the Connections window, since
the information in these panes is used to generate the MDW.
The user function must then create the access to input and output ports using
API subroutines, such as:
For details on these subroutines, see the IRIS Explorer Reference Pages.
When you build a module, you can choose whether to generate a default Module
Control Wrapper or
an X Windows MCW. You will require an X Windows MCW only if you have a
DrawingArea widget, or otherwise need access to the
X server.
The X Windows MCW
provides IRIS Explorer with the ability to recognize and manipulate
X Windows
widgets in a module. You can use
a drawing area in a module as
an X input window, to hold an X Window or Motif widget, or as a general
X graphics drawing area. An example
of an IRIS Explorer module that contains
X Windows
drawing areas and uses an X Windows MCW is GenerateColormap.
The code examples in Examples Using
X Widgets later in this Chapter illustrate how to
incorporate some of
these widgets into an IRIS Explorer module.
These are the advantages of using X Windows for customized graphics:
IRIS Explorer provides some API subroutines to help you handle X Window
System widgets efficiently. They are described in the IRIS Explorer Reference Pages and
summarized in Table 9-1.
This example shows how an X drawing area can be used as an
input mechanism. It places a Motif button in an X drawing area and
reports whenever the button is pressed. The code is in
$EXPLORERHOME/src/MWGcode/Advanced/C/XDrawInput.c. The
module resource file may also be found in the same
directory.
Note: It is not possible to write this module in Fortran.
This example uses a GLXWidget to create a drawing area. The code is in
$EXPLORERHOME/src/MWGcode/Advanced/C/GLXWidget.c.
The module resource
file may also be found in this directory.
Note that in order to create a module that calls OpenGL directly it is necessary to
have a linkable version of the OpenGL libraries for your platform.
Note: There is no Fortran version of this module.
You can save a significant amount of disk space by combining several related
modules into a single executable image. This means you have only one executable
file for all the related modules. If these modules were combined into one in the
GUI, you could end up with a very complicated module control panel. To avoid
this, IRIS Explorer allows each module sharing a single executable to have its
own control panel. Each control panel is defined in a separate module resources
file with the same name as the module itself.
This section describes how to create a single executable, or combination
module, in the Module Builder and suggests what part of the library API to call
from the combined module code.
A combination module determines its actions in two ways. It can:
Both AddImg and XorImg have module resource files that describe
their control panels and ports. In addition, on the Build Options control panel,
AddImg has Alternate Executable selected, with the name
XorImg displayed in the executable name field. The XorImg module
does not have Alternate Executable selected, so its name field is
identical to the module name. It is clear that both AddImg and
XorImg share the XorImg executable.
When the modules are built, the Module Builder causes the XorImg
executable to be compiled, linked, and installed along with
XorImg.mres. The rules for AddImg cause only its module
resources file to be installed, thus referring to the XorImg binary. When
either module is started, the same executable is run. Enough information is
provided to the XorImg binary that it can determined whether it was
invoked as AddImg or XorImg, and, consequently, which set of
functionality to provide.
Module Builder generates a Makefile which may
also be executed outside the Module Builder.
The XorImg user function includes a call to the API routine
cxModuleNameGet, which returns the name of the module that was invoked:
in this case, AddImg or XorImg. The user function then compares
the name to a table of known operations to determine whether to add or
xor two images. The module can also use this name to determine its
expected port names and to access its port data.
The DataScribe modules provide another example of the use of alternate
executables. Each customized DataScribe module is simply a module resource file
that expects an input script and knows which set of ports to query, based on the
contents of the script. Every DataScribe module refers to the generic DataScribe
module for its executable. The DataScribe module uses the input script, not
cxModuleNameGet, to direct its activities, which are to interpret the
input script and carry out specific actions on data files or IRIS Explorer
ports.
An ordinary module, called MyModule for example, can use an executable
name derived from its mres file, MyMod.mres. But some modules,
notably the ImageVision Library(tm) modules, refer to a different
executable. Since many modules can refer to a single executable, those modules
need not build the executable; some other similarly named module does that. For
example, XorImg is the module that builds the executable XorImg;
all the other ImageVision Library modules refer to it.
The Alternate Executable item in the Build Options window of the
Module Builder lets you:
In the case of a combined executable, each module has its own module
resources file, but only one of the modules creates and installs an executable.
The executable is then responsible for determining which operation to invoke
upon firing.
There is no difference in programming between the user code of a loop
controller and that of an ordinary module. The loop control work is handled in
the MCW. There are certain API routines, discussed below, that simplify writing
loop controller modules.
However, there are some capabilities a loop controller should have. The
module must have at least one input and at least one output port. It needs to
know that the loop continues by default if new data is sent into the loop, and
the loop stops if no new data enters the loop. If the module is to continue or
terminate a loop, it must be able to evaluate the condition for the loop. The
module should also be able to break its loop, to prevent the loop from iterating
indefinitely.
Note: A module that flushes multiple data sets, such as
Streakline, should not be given loop controller status. Multiple data
flushes from a controller module can lead to an exponential increase in loop
data that will paralyze your system.
There are four API subroutines you can use when writing a loop controller
module: cxLoopBreak, cxLoopControlArc, cxLoopCtlr and
cxLoopIteration. The three routines, cxLoopCtlr,
cxLoopControlArc and cxLoopIteration, are Boolean queries that
deal respectively with loop controller status of the module, control arc status
of a connection, and looping, as opposed to non-looping, invocation of an
iteration. You can use these calls to get information about the actions and
state of a loop controller module.
The cxLoopBreak routine halts a current loop iteration, as well as
marking all pending iterations for cancellation by the loop controller. If the
controller puts out "end-of-loop" data, those datasets should be
assigned to the output ports when cxLoopBreak is called, as the user
function will not be called again for that loop iteration. In programming a loop
controller module, you must use the API routine cxLoopBreak to
terminate iteration of a loop. This causes the MCW to cancel all pending
iterations of the loop and return the module to a quiescent state.
Data is put on the synchronisation port Loop Ended by the MCW when the
iteration of a loop is terminated. You can prevent data being put on this port,
as well as the Firing Done port, by calling cxOutputNoSync.
You may also terminate a loop by sending out a sync frame, which is a frame
with no new data, on the looping outputs. You must take care here, as the Map
Editor user might wire a loop on ports you did not expect to be used, causing
the module not to terminate the loop. If the synchronisation port Firing
Done is wired into the loop, as data is always put on this port when the
module fires successfully, then the only way to terminate the loop is by calling
cxLoopBreak. It is safest to use cxLoopBreak in all cases.
The While and Repeat loop controller modules simply examine the
condition for the loop, which is a parameter value, and copy a pointer over from
the input to the output port. These are "typeless" modules which have
cxGeneric input and output ports, and do not interrogate their generic
inputs.
You can copy the modulename.mres file of these modules from
directory $EXPLORERHOME/modules and modify each one slightly to create
loop controllers that pass additional data sets through, or accept particular
data types. Just rename the module and augment the port list. In the Build
Options control panel, set the alternate executable to be the name of the module
you copied, e.g. While, (see Selecting Build Options in Chapter 2) and run
the Module Builder on the new module.
The While module has the ports listed in Table 9-2:
Substitute your own name for the "Value N" part of the port
name. The module must match initial/final and loop in/loop out values. It can do
this by finding the keywords "Initial", "Final",
"LoopIn" and "LoopOut" on the port names.
The For loop is a specific case of the While loop, and the For module
is designed to obviate the need for parameter functions. The For module
has sliders for changing initial, final and current parameter values and
parameter logic, and it also has a Refire button. The module evaluates a
current value and waits for the return value. The loop index uses double
precision floating point values. The current value is put out even when the loop
terminates, so that other downstream modules can query it.
The cxGeneric data type is found on input and output ports in the
generic loop controller modules, which are For, While,
Repeat and Trigger. You can wire any port types into or out of a
cxGeneric port.
The purpose of cxGeneric is to allow passage of all data types through
a module. It is actually more of an encapsulation system than a true type, since
it does not operate on the data at all.
When you install an interpreter module, you can list any extra files you want
installed with the module in the Module Builder. The build options control panel has a text slot for
listing them. Any module can cause extra files to be sent to
$EXPLORERUSERHOME/modules
when the Build and Install command is issued from the Module
Builder (or a make install command is issued from outside it).
The Module Builder will check for information pertaining to interpreter
modules, that is, those with an alternate executable name. Such a module may
have a file $EXPLORERHOME/lib/modulename.interp which lists the
interpreter module, optional file suffix to use, and widget normally containing
the name of the interpreter script file. Table
9-3 lists three examples:
These are the rules that the Module Builder follows during installation.
Hook functions allow you to gain control of a module when specific events
occur in the Module Control Wrapper (MCW). The MCW passes control to the hook
function whenever these events occur. A hook function can be linked to any one
of the following events in an IRIS Explorer session. These events are:
Once you have control through the hook function, you can call a subroutine to
carry out an activity related to the main user function. For example, the
Render module has an initialization hook function that is triggered when
the module is launched in the Map Editor. This function initializes the Open
Inventor Library and the OpenGL drawing area, among other things. When the
subroutine has completed its task, control is returned to the MCW.
A hook function table is contained in The
Generic Wrapper of every module. The table is empty by default. To fill in
the default table with your own hook functions, you must:
Hook functions written in C++ must have a C linkage. You can do this by
declaring the function:
There are two forms of the calling sequence, one used in the initialization
and removal hook functions, and one used in the connection and disconnection
hook functions.
For the latter, the MCW passes the name of the affected port
(portName) and an integer that identifies that particular connection
(linkID). The linkID values are unique with respect to a given
port and increase throughout a single IRIS Explorer session.
The calling sequence of each type of hook function is given below. You can
give a hook function or subroutine any name you like; these are merely examples.
Creating a module:
The MCW calls this function when the module is created, before connections
are made and parameters are delivered. Parameter values are meaningless at this
time.
Connecting input ports:
The MCW calls this function after the user has connected the input port
called portName to some output.
Hook functions are ordinary procedures, which you must write before the MCW
can use them. The hook functions and user function must both have either C or
Fortran linkage; mixing linkages is not permitted.
You can use the Module Builder to generate Hook
Function Prototypes. This is most easily done as follows:
Once the hook function names have been specified, the Module Builder will
automatically add prototypes for them (in the correct language) to the prototype user function file, if you ask for
one to be created. After that, you need merely fill in the body of the
functions. Here are some points to note when you are writing a hook function.
Note: If you have old modules that define cxHookTable, you must
remove this definition from your source code. If you do not, you will see a
multiply-defined symbol. This may make debugging more difficult and might even
prevent the module from building.
The following example shows the use of hook functions to allocate storage
space at module initialization, to get information about port connections, and
to delete persistent lattice data in shared memory when the module is
destroyed. Code and resources files may be found in the directories
$EXPLORERHOME/src/MWGcode/Advanced/C and
$EXPLORERHOME/src/MWGcode/Advanced/Fortran.
IRIS Explorer transfers data between modules using shared memory if the
modules are on the same machine and the machine supports shared memory. The
advantage of using shared memory is that large quantities of data can be
transferred from one module to another with very little communication
overhead. All modules access the shared memory arena simultaneously, so only the
address of the data has to be transferred, not the data itself. However, the
data must be managed somehow, and this is done by means of reference
counting.
The reference count can be thought of as the number of places that wish to
keep a persistent copy of specific data. These places are also responsible for
decrementing the count when they have finished with the data.
All modules are responsible for collectively managing the data in shared
memory. One module creates data and sends it to other modules. When all the
modules are finished with that data, the space it occupies must be reclaimed.
IRIS Explorer uses reference counting to manage this process. With reference
counting, a module does not need to know anything about which other modules use
the data. It must simply perform certain bookkeeping actions on the data it
references, so that the system knows when the data should be reclaimed.
Reference counting is essential when using shared memory, but the mechanism
is also general enough to be used by a single process on private data. Thus,
modules executing on a machine that does not support shared memory can still use
the same code for managing data memory.
As long as every module does its bookkeeping correctly and consistently,
there will be no memory leaks, and data will not be reclaimed while a module
still refers to it.
Caution: The automatically generated Module Data Wrapper cannot
completely recover if the shared memory arena becomes full. It is possible that,
if there is not enough memory for the computational function or for the MDW
itself, the MDW may leak memory.
An integer is kept with each data object, to record the number of places
where the data is used. When a module receives data, it increments the integer
count to show that the data is being used in an additional place. When the
module is finished with the data, it decrements the reference count. When the
count becomes zero, the data is no longer needed and that module deletes the
data.
For example, the module's input and output ports store data for the module,
so they must manipulate the reference count. When the module puts data on the
output port, the output port decrements the count of the old data on the port
and increments the count of the new data. The output port retains use of the
data so it can be sent to modules as they are connected to the port.
When a module receives new data, the input port decrements the count on the
old data on that port and increments the count on the new data. The input port
retains use of the data until it is replaced, which means the data is available
every time the module fires, even if the data has not changed.
The API subroutines cxDataRefInc and cxDataRefDec are used
to increment and decrement the reference count.
IRIS Explorer implements a less rigid version of reference counting than that
defined above. Data objects are created with a reference count of 0 because
nothing has claimed responsibility for decrementing the count. If the count on
such an object is decremented immediately, it becomes -1. The object is deleted
and no error is reported.
The default count of 0 simplifies coding of the user function cases. A data
object is created and (usually) placed on the output port. The output port
increments the count and assumes responsibility for maintaining the count, thus
freeing the user function from that responsibility and allowing it to ignore the
data. If data objects were always created with a reference count of 1, then you
would have to decrement the count of each object after placing it in the output
port.
A user function may want to allocate a data object for temporary storage so
that the same data can be output more than once. In this case, the user function
does need to increment the count, ensuring that the data will persist as long as
the user function needs it. When it is no longer required, the user function
should decrement the count.
A module may have more than one reference to any data, so the value of the
reference count may be greater than the number of modules using it. It is easier
to code a module when you can allow it to have multiple references to the data,
because the user function does not have to maintain exactly one reference count.
All IRIS Explorer system and user-defined data types are reference-counted.
Data objects may contain references to other reference-counted objects. For
example, the cxLattice and cxPyramid data types contain references
to cxData, cxCoord, cxConnection, and cxLattice
objects. The reference-counting scheme handles arbitrary sharing of IRIS
Explorer data objects within other objects, just as data objects are shared
between modules. For example, a cxData object can be contained in more
than one cxLattice. Likewise, a cxLattice object can be contained
in more than one cxPyramid and referenced by more than one module.
The IRIS Explorer routines that set the contents of cxLattice and
cxPyramid, for example, cxLatPtrSet and cxPyrSet,
correctly manage the reference counts of the contained data types. When the
count of a cxLattice or cxPyramid reaches 0 or -1, the generic
data deletion routine uses the data type information to decrement the reference
count of each contained data type.
The automatically generated interface routines cxLatPtrSet,
cxPyrSet, and cxPyrLayerSet correctly manage the reference
counts for the data objects they contain. You are strongly encouraged to use
them.
The cxGeometry data type contains references to the field data of the
IRIS Explorer nodes within an Open Inventor scene graph. This reference counting
is managed within the geometry API and the IRIS Explorer node classes and you
will never need to manipulate these directly. Note that this is in addition to
the Open Inventor reference counting for nodes and referencing and deleting
nodes, which should be programmed as usual for an Open Inventor scene graph,
when using Open Inventor directly instead of the geometry API. It is important
to ensure that a scene graph is correctly deleted when a module exits, lest it
leak the shared memory in its nodes.
Module writers rarely have to manipulate reference counts, but there are
times when it is necessary. This happens most commonly when the module needs to
retain data that the system would normally have released. For example, a module
that outputs a moving average of the last three lattices delivered to it might
need to keep the lattices from two previous firings. When a new lattice arrives
on a wire, it decrements the reference count on the lattice that it
replaces. This can result in the memory for the old lattice being
deallocated. To stop this happening, the module should increment the reference
count of the lattices it wants to save. This process applies to all data types.
If a module increments the reference count to save data, it is very important
that it decrement the reference count to free the memory when the data is no
longer needed. Not properly balancing reference count increments and decrements
can result in memory leaks which degrade performance.
The shared memory arena is a fixed-size resource, whose upper limit is system
dependent. Because it is stored in a memory-mapped file, its size is limited by
the available disk space and the command line and configuration options that are
specified when the user starts IRIS Explorer; therefore, it is possible for data
memory allocation routines to fail, no matter how large the arena might be,
because the arena size may exceed the amount of available memory.
It is very important to keep the limitations of memory allocation in mind
when you write modules. The memory requirements of the system fluctuate, so many
out-of-memory situations are transient. IRIS Explorer tries to allocate memory
several times over a short period before returning an error flag, at which time
an error message is sent to the Map Editor, which in turn generates a pop-up
dialog box.
Modules should check the data allocation flag after every IRIS Explorer
routine that allocates data memory. The cxDataAllocErrorGet routine
returns TRUE (non-zero) if data could not be allocated. See the entry for
cxDataAllocErrorGet in the IRIS Explorer Reference Pages for a
list of the routines that allocate data memory. (For reasons of simplicity such
error checking has been omitted from many of the example programs.)
If a module ignores allocation errors, it may crash due to a bad pointer.
Even worse, it may cause another module to crash. This kind of module failure
can be very hard to diagnose. If a module crashes, it cannot decrement reference
counts to data to which it has references, so the data will be leaked. This
makes a bad situation even worse.
The IRIS Explorer modules provided with the system do not attempt memory
allocations after an error has been detected. They simply reclaim any memory
they have already allocated and return from the user function. Unfortunately,
the module may have successfully allocated several data objects, which it must
reclaim before returning. The recovery code can get quite complicated,
especially for a module that creates many data objects or creates
cxPyramid or cxGeometry objects. It helps a great deal if all data
is allocated in one place, where it is easier to keep track of what should and
should not be recovered. This is not always possible, but grouping code in
modules to streamline data allocation helps to eliminate programming errors.
If the memory requirements are smaller when no modules are firing, the
Fire Now option on the module pop-up menu will cause it to execute again.
If the module still does not have enough memory, you can free some data memory
by deleting modules that are consuming large portions of memory.
If a module runs out of memory, it can:
However, each failed attempt still generates the pop-up dialog box in the Map
Editor.
The following examples show how to recover from allocation errors for some of
the IRIS Explorer data types. They also illustrate some coding styles for
recovery.
Note: These examples are in C only because they are meant to
illustrate specific techniques, rather than provide reusable code. Templates for
these routines may be found in directory
$EXPLORERHOME/src/MWGcode/Advanced/C.
The first example allocates a cxParameter and two cxLattices.
It uses a single memory recovery routine, memCleanup, which takes
advantage of the fact that cxDataRefDec accepts a NULL
pointer; therefore, memCleanup can be called at any point in the
program.
The second example shows the use of recovery labels in the reverse order of
the data allocation. Execution falls through the labels, unwinding the
successful allocations.
The third example shows the memory for lattice data and coordinates allocated
separately. Recovery becomes more complicated in this case. Once
cxLatPtrSet stores data and coords into lat1,
lat1 has reference counts on data and coords and is
responsible for decrementing their counts. Once succesfully stored in the
lattice, they should not be decremented by the user function.
The fourth example shows a recovery strategy for the cxPyramid data
type. It is similar to that required by cxLattice.
The base lattice and the lattice/connection pair are managed similarly to the
data and coordinates for cxLattice. Once successfully stored in the
pyramid, they should not be decremented by the user function.
Also, cxPyrLayerSet allocates memory if the number of pyramid layers
is to be increased, so cxDataAllocErrorGet should be checked after a
call to cxPyrLayerSet if there is any chance that the number of layers
will increase. If cxPyrLayerSet fails, you must decrement the lattice
and connection manually because they were not successfully stored in the
cxPyramid.
The cxGeometry data type complicates error recovery even more, since
it contains a buffer of transcribed geometry specification commands. Each
geometry specification routine adds commands to this buffer, which may cause the
buffer to be enlarged, so cxDataAllocErrorGet must be checked after
each command. In addition, the actual buffer location is not associated with the
geometry object while primitives are added, so the cxGeoBufferClose
routine must be called before the geometry object can be reclaimed.
In this example, the module performs operations if the error flag is not
set. It can then check the flag at key points and perform cleanup if required.
The buffer may be expanded more than once for each geometry specification
command, so it may contain a partial command when the data allocation fails.
The partial command may confuse downstream modules so seriously that the
contents of the geometry must be thrown away.
Because the geometry interface is a delta protocol (only changes are
sent), any cxGeoDelete command will be lost after the local data
structures have been modified. The downstream modules will misinterpret future
geometry inputs and will appear corrupted. The best way to recover is to
disconnect and reconnect the geometry output that ran out of memory.
Note: cxGeoBufferClose should not be called twice on a
cxGeometry object.
Several subroutines in the API allow you to increase the flexibility of your
module. You can:
The use of these routines is described briefly in the next sections.
The subroutine cxInputAdd registers an open UNIX file descriptor
that the module will monitor. The file descriptor can reference any UNIX device
on the system that has a file descriptor. The module can request that it be
informed when the device is ready for reading or writing, or when an exception
condition exists. When one of these conditions is met, the Module Control
Wrapper calls the user callback routine, which passes the data collected from
the UNIX device to the IRIS Explorer module. cxInputAdd returns a
unique handle, which allows the module to distinguish more than one such input
device.
When you use cxInputAdd, you should note that the callback routine
is not called when the module is firing, only when it is quiescent.
The related subroutine, cxInputRemove, cancels the UNIX file
descriptor monitoring process. You can call it from the callback routine
specified in cxInputAdd.
cxInputAdd is a handy tool for monitoring, and collecting data from,
processes outside the IRIS Explorer environment. For example, you can start up
and communicate with a data base server from your IRIS Explorer module.
This module demonstrates the use of cxInputAdd. The module has a
single text type-in slot on its control panel, into which the user types a UNIX
command. The module runs that command and routes the output to the file
descriptor fd. When the output is available on the file descriptor, the
feedme routine is called. This routine simply copies this output to the
module's standard output. The code is in
$EXPLORERHOME/src/MWGcode/Advanced/C/InputAdd.c. The resources file may
be found in the same directory. To test the module type a command, for example
ls, in the text slot and observe the results.
Note: There is no equivalent Fortran version of this module.
You can use cxTimerAdd to time the occurrence of certain events that
relate to the module function. The module can request callback routines to be
called after a delay of a specified number of milliseconds, or at specified
intervals. For example, you can set the timer to operate once and then switch
off, or to reset itself and continue to run.
The callback is made when the timer expires, but the user function must be
quiescent at the time. If the timer expires while the user function is
executing, the callback will not be made until it has completed execution, and
the timing schedule will be thrown off.
You can use cxTimerRemove to cancel this routine.
The code for this example is in
$EXPLORERHOME/src/MWGcode/Advanced/C/Timer.c and
$EXPLORERHOME/src/MWGcode/Advanced/Fortran/Timer.f.
Resources files may be found in the same directories.
You can use cxFilenameExpand to expand the name of a file fully to
include the directory path. You can give it a string including, for example,
$EXPLORERHOME
or ${EXPLORERUSERHOME}, and it will expand the variable into the full
name. This is particularly useful because there is no standard C library routine
for doing this.
The code for the following example is in
$EXPLORERHOME/src/MWGcode/Advanced/C/FilenameExpand.c
and the resources file FilenameExpand.mres may be found in the same
directory.
Note: There is no equivalent Fortran version for this module.
This section explains how to set up a user directory in which modules can be
made. It assumes some knowledge of UNIX program development, specifically, the
make(1) program. IRIS Explorer uses an extension to make
called Imake which allows us to distribute a portable development
mechanism. With Imake, you do not directly create the Makefile
files used by make. Instead, Makefile files are generated
automatically from a file named Imakefile (see Appendix A - Using
Makefiles). IRIS Explorer takes this one step further.
Within the subdirectories where you develop modules, the Module Builder
creates the Imakefile for you from the information in the module
resource file, and then it creates a Makefile from that.
There are two paths to follow at this point:
Path 1 provides a good test of the installation of IRIS Explorer on your
machine and shows a working example of module compilation. Path 2 is more
direct, allowing you to begin module development immediately. In either case,
you must first set up a new, empty module build tree. Each method is described
in detail in the following sections.
The plan is to create a directory where you have write permission (for
example, beneath your home directory) and then develop modules in subdirectories
of that directory. One or more modules may be developed in each subdirectory and
you can build one or all of them with a single command.
First, reset the EXPLORERUSERHOME environment variable to the
directory you have chosen as the IRIS Explorer installation directory; and place
$EXPLORERHOME/bin in your execution path:
Note: Individual users do not always have write access to
/usr/local/explorer on a multi-user system. In this case, set
EXPLORERUSERHOME to a directory in which you do have write permission.
Decide where you want to do module development and create that directory.
~/explorer/modules is a good choice.
If ~/explorer does not already exist on your system, create it by
typing:
From here, you can proceed through one or the other, or both, of the
following sections, depending on your purpose.
If you decide to build the sample IRIS Explorer modules provided in source
format, you can compile them in your own source directory. To do this, first
copy the sources into your directory:
This assumes that explorer/modules is a directory in your home
directory where you will build these modules. Next, make a Makefile in
that directory:
Next, make sure that your EXPLORERUSERHOME environment variable is set to a
place where you want these modules installed, for example:setenv EXPLORERUSERHOME /usr/tmp/modules
mkdir $EXPLORERUSERHOME
Now, create Makefiles for those module subdirectories:
Finally, you compile, link and install these modules:
At the end, all of the sample modules should be compiled, linked, and
installed.
If you decide to set up your own build system, you create subdirectories in
which you can develop one or more modules. First copy the template
Imakefile into your development directory and make it writable:
Then you can make a subdirectory (whose name may or may not match the module
name you wish to use), and add that subdirectory to the list in
Imakefile which starts with the line
and is followed by a list of subdirectories containing modules. Any number of
directory names can be listed, as long as they exist and are IRIS Explorer
module development subdirectories.
You then build a new top-level Makefile, remake subdirectory
Makefiles, and then proceed with development of the new module.
For example,
There are several environment variables that affect the way modules are built
and installed. See Configuring the Build
Environment for more information on these variables.
Each time you add a directory to the MODULESUBDIRS list, you must recreate
the top-level Makefile with cxmkmf. Do not be tempted to edit
the Makefile directly.
Once you have done this, the following commands will work in each module
subdirectory: The typical development cycle is to write your module user function, use the
Module Builder to describe it, then use the Build option in the Module Builder
to build it. Alternatively, you can describe the module first, then use the
Module Builder to generate the function prototype. You can then fill in the code
for the user function, and after that, build the module.
From this point on, you can rebuild the module at any time using the
make all command. For more information, refer to Appendix A - Using Makefiles.
Figure 9-1 Spectrum of Module Complexity
The Module Wrappers
Figure 9-2 The Module Structure
Module Control Wrapper
Module Data Wrapper
The Generic Wrapper
Working with Module Wrappers
Modules Without MDW
Using the X-MCW Option
These are the disadvantages:
Subroutine
Function
cxXtAreaInitialize
Creates a drawing area that can later be bound to a module control
panel
cxXtAreaAttach
Attaches a widget hierarchy to a control panel drawing area
cxXtAreaResize
Updates the size of a widget hierarchy in the drawing area of a module
control panel
cxXtAreaCallbackAdd
Registers a callback function for a
cxXtArea
cxXtGLAreaInitialize
Creates an OpenGL window inside a control panel drawing area
cxXtGLAreaRedraw
Example Using X Widgets
/*
* This example shows how an X drawing area can be used
* as an input mechanism.
*/
#include <X11/Intrinsic.h>
#include <X11/Xm/PushB.h>
#include <cx/XtArea.h>
void xcallback(Widget w, void *client)
{
printf("button pressed\n");
}
void xwidget(long window)
{
static int first = 1;
static Widget top;
Widget button;
/*
* Do initialization stuff
*/
if (first) {
first = 0;
/*
* Get a Xt form and put a button on it
*/
top = cxXtAreaInitialize();
button = XtVaCreateManagedWidget("button",
xmPushButtonWidgetClass, top,
XmNleftAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM,
XmNtopAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_FORM,
NULL);
/*
* Add a callback then attach the form to the window
*/
XtAddCallback(button, XmNactivateCallback,
(XtCallbackProc) xcallback, (XtPointer) NULL);
cxXtAreaAttach(top, window);
}
cxXtAreaResize(top, window);
}
Graphics Library (OpenGL) Example
/*
* Example module using a GLXWidget
*/
#include <stdio.h>
/* X includes */
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
/* OpenGL Drawing Area Include */
#include <X11/GLw/GLwDrawA.h>
/* GL includes */
#include <GL/gl.h>
#include <GL/glx.h>
/* Explorer includes */
#include <cx/PortAccess.h>
#include <cx/DataAccess.h>
#include <cx/UserFuncs.h>
#include <cx/XtArea.h>
Widget top; /* top level form widget */
Widget da; /* drawing area widget */
/*
* Widget action routine
*/
void xaction(void *client, XEvent *ev)
{
float p[2]; /* temporary storage for coordinates */
short xsize, ysize; /* size of GL widget */
/* get size of OpenGL window */
XtVaGetValues(top, XmNwidth, &xsize, NULL);
XtVaGetValues(top, XmNheight, &ysize, NULL);
switch (ev->type)
{
case ButtonPress:
/*
* IMPORTANT NOTE: X origin is *upper* left;
* GL origin is *lower* left.
*/
printf("button pressed at %d, % d\n",
ev->xbutton.x, ysize - 1 - ev->xbutton.y);
break;
case Expose:
/*
* some sample OpenGL stuff
*/
glViewport(0, 0, (GLsizei)xsize, (GLsizei)ysize);
glClearColor(0.0, 0.317, 0.439, 0.627);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(40.0, 1.0, 0.5, 5.0);
glTranslatef(0.0, 0.0, -4.0);
glColor4b(255, 255, 255, 255);
glBegin(GL_LINE_LOOP);
p[0] = 0.0;
p[1] = 0.0;
glVertex2fv(p);
p[0] = 1.0;
p[1] = 1.0;
glVertex2fv(p);
p[0] = 1.0;
p[1] = -1.0;
glVertex2fv(p);
p[0] = -1.0;
p[1] = -1.0;
glVertex2fv(p);
p[0] = -1.0;
p[1] = 1.0;
glVertex2fv(p);
glEnd();
glFlush();
GLwDrawingAreaSwapBuffers(da);
break;
}
}
/*
* Module initialization routine
*
* This routine must be listed as the "initialization" hook function
*/
void xex2_init()
{
long wid; /* window id from UI */
XtTranslations translations; /* X translations structure */
int n;
Arg args[12];
XVisualInfo *vi;
static GLXContext glx_context;
/* X actions record */
static XtActionsRec actions[] ={"xaction",(XtActionProc) xaction};
/* Initialize an Xt area, get an empty form widget */
top = cxXtAreaInitialize();
/* Get the UI drawing area widget id */
wid = cxParamLongGet((cxParameter *)
cxInputDataGet(cxInputPortOpen("window")));
/* Define some actions and translations for our widget */
XtAddActions(actions, XtNumber(actions));
translations = XtParseTranslationTable(
"<BtnDown>: xaction()\n\
<Expose>: xaction()");
/* Put a drawing area widget in the form */
da = XtVaCreateManagedWidget("da",
glwDrawingAreaWidgetClass, top,
GLwNrgba, TRUE,
GLwNdoublebuffer, TRUE,
XmNtranslations, translations,
XmNleftAttachment, XmATTACH_FORM,
XmNtopAttachment, XmATTACH_FORM,
XmNrightAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_FORM,
NULL);
/* Attach the form to the drawing area window */
cxXtAreaAttach(top, wid);
/* initialise the OpenGL area */
XtSetArg(args[0], GLwNvisualInfo, &vi);
XtGetValues(da, args, 1);
glx_context = glXCreateContext(XtDisplay(da), vi, 0, GL_FALSE);
GLwDrawingAreaMakeCurrent (da, glx_context);
glDrawBuffer(GL_FRONT_AND_BACK);
}
/*
* Module execution function
*
*/
long xex2()
{
int windowPort; /* port number of window parameter */
long wid; /* window id from UI */
/* Get window port number and the widget ID of the drawing area */
windowPort = cxInputPortOpen("window");
wid = cxParamLongGet((cxParameter *)
cxInputDataGet(cxInputPortOpen("window")));
/* Resize the form widget to the UI drawing area size */
cxXtAreaResize(top, wid);
return 0;
}
Sharing Module Executables
Types of Shared Executable
Using an Alternate Executable
Building Loop Controller Modules
Using the Loop Controller API
Using Generic Controller Modules
Input Ports
Output Ports
Condition
Final Value 0
Initial Value 0
Final Value 1
...
...
LoopIn Value 0
LoopOut Value 0
LoopIn Value1
LoopOut Value1
The For Module
The cxGeneric Data Type
Installing Interpreter Modules
Interpreter Module
Module Executable
Suffix
Widget Name with embedded space
LatFunction.interp
LatFunction
.shp
Program File
GenericDS.interp
GenericDS
.scribe
Script File
MyInterp.interp
MyInterp
NULL
My Widget
Hook Functions
extern "C" my_hook_fcn( )
{ ...
}
Hook Function Calling Sequence
Writing Hook Functions
Figure 9-3 The Hook Function Window
Examples
C Version:
#include <stdio.h>
#include <cx/DataAccess.h>
cxLattice *permanentLattice = NULL;
/***********************************************************************
* This module shows how hook functions may be used
*
* Each hook function generates output, so the calling of each may be
* followed as the module starts up, is connected to other modules,
* is fired, and gets deleted.
***********************************************************************
*/
void HookFuncModule(cxLattice *lat)
{
/* You may complete the user function by adding lines of code here
* This output will appear every time the modules is fired
*/
printf("User function called.\n");
/* A private copy of the input lattice is stored by the user
* This storage would be leaked on module destruction, unless
* we increment the reference counter for this storage, and
* ensure to delete it in the hook destroy function
*/
if (permanentLattice == NULL)
{
permanentLattice = cxLatDup(lat,1,1);
cxDataRefInc(permanentLattice);
printf("increase the reference count for the private lattice\n");
}
}
#include <stdio.h>
#include <cx/DataOps.h>
#define MAXSTR 256
typedef char strng[MAXSTR];
strng *myLinkArray = NULL;
/***********************************************************************
* Initialisation hook function in C
*
* Some storage is allocated for use while the module exists
***********************************************************************
*/
void myInitFunc()
{
/* You may complete the initialisation function by adding code here
* for example to initialise the geometry library
* This output will only appear when the module is started up
*/
printf("Initialisation hook function called.\n");
/* Initialise the link array */
if (myLinkArray == NULL)
myLinkArray = (strng *) cxDataCalloc(100, sizeof(strng));
}
#include <stdio.h>
#include <string.h>
#define MAXSTR 256
typedef char strng[MAXSTR];
/***********************************************************************
* Connection hook function in C.
*
* This shows how one might save the port name associated with every
* connection. Storage allocation of "myLinkArray" is done in the
* initialisation hook function.
*/
void myConnectFunc(portName, linkID)
char *portName;
int linkID;
{
extern strng *myLinkArray;
/* Save the association of port and linkID. The link tag may be
* negative, indicating a widget connection or a parameter function
* (pfunc) connection. If so, ignore it.
*/
printf("linkID=%d\n", linkID);
if (linkID >= 0 && linkID < 100)
{
strcpy(myLinkArray[linkID], portName);
printf("link %d is named %s\n",linkID, myLinkArray[linkID]);
}
}
#include <stdio.h>
#include <cx/DataAccess.h>
/***********************************************************************
* Destroy hook function in C.
*
* This procedure shows how one might write a "destruction" hook
* function. When the map editor user destroys a module, this
* procedure, if defined, will be called.
*
* Typically, this is useful if the module caches old data inputs.
* This provides a place to put the code that can decrement the
* reference counts on the data.
***********************************************************************
*/
void myDestroyFunc(void)
{
extern cxLattice *permanentLattice;
int i;
/* Here, one might adjust the reference counts on any cached data
* In this case, there is only one lattice
*/
cxDataRefDec(permanentLattice);
printf("User destroy hook function called.\n");
}
Fortran Version:
SUBROUTINE HKFUNC(LAT)
C
INCLUDE '/usr/explorer/include/cx/DataAccess.inc'
C
C This module shows how hook functions may be used
C
C Each hook function generates output, so the calling of each may be
C followed as the module starts up, is connected to other modules,
C is fired, and gets deleted.
C
C .. Scalar Arguments ..
INTEGER LAT
C .. Scalars in Common ..
INTEGER PRVLAT
C .. Local Scalars ..
INTEGER FIRST
C .. External Subroutines ..
EXTERNAL CXDATAREFINC
C .. Common blocks ..
COMMON /CACHE/PRVLAT
C .. Data statements ..
DATA FIRST/1/
C .. Executable Statements ..
C
C You may complete the user function by adding lines of code here
C This output will appear every time the modules is fired
C
PRINT *, 'User function called.'
C
C A private copy of the input lattice is stored by the user
C This storage would be leaked on module destruction, unless
C we increment the reference counter for this storage, and
C ensure to delete it in the hook destroy function
C
IF (FIRST.EQ.1) THEN
PRVLAT = CXLATDUP(LAT,1,1)
CALL CXDATAREFINC(PRVLAT)
PRINT *, 'increase the reference count for the private lattice'
FIRST = 0
END IF
C
RETURN
END
SUBROUTINE MYINIF
C
C Initialization hook function in Fortran
C
C .. Arrays in Common ..
LOGICAL MLSTAT(100)
C .. Local Scalars ..
INTEGER I
C .. Common blocks ..
COMMON /CONN2/MLSTAT
C .. Executable Statements ..
C
C You may complete the initialisation function by adding code here
C for example to initialise the geometry library
C This output will only appear when the module is started up
C
PRINT *, 'Initialisation hook function called.'
C
C Initialise the link tags to .FALSE.
C
DO 20 I = 1, 100
MLSTAT(I) = .FALSE.
20 CONTINUE
C
RETURN
END
SUBROUTINE MYCONF(PRTNAM,LNKTAG)
C
C This connect hook function shows how one might save the
C port name associated with every connection.
C The name is saved in a common variable.
C The initialisation hook function sets array MLSTAT to .FALSE.
C
C .. Scalar Arguments ..
INTEGER LNKTAG
CHARACTER*(*) PRTNAM
C .. Arrays in Common ..
LOGICAL MLSTAT(100)
CHARACTER*32 MLARAY(100)
C .. Local Scalars ..
INTEGER I
C .. Common blocks ..
COMMON /CONN1/MLARAY
COMMON /CONN2/MLSTAT
C .. Executable Statements ..
C
C The LNKTAG may be negative, indicating that the connection is
C either a widget or a parameter function (pfunc) in which case
C this code ignores it.
C
IF (LNKTAG.GE.0 .AND. LNKTAG.LT.100) THEN
MLARAY(LNKTAG+1) = PRTNAM
MLSTAT(LNKTAG+1) = .TRUE.
END IF
C
DO 20 I = 1, 100
IF (MLSTAT(I)) THEN
PRINT *, 'Link number', I - 1, ' is named ', MLARAY(I)
END IF
20 CONTINUE
C
RETURN
END
SUBROUTINE MYDSTF
C
C Destroy hook function in Fortran.
C
C This procedure shows how one might write a "destruction" hook
C function. When the map editor user destroys a module, this
C procedure, if defined, will be called.
C
C Typically, this is useful if the module caches old data inputs.
C This provides a place to put the code that can decrement the
C reference counts on the data.
C
C The name of this procedure must be entered into the hook function
C table in the module builder.
C
C .. Scalars in Common ..
INTEGER PRVLAT
C .. External Subroutines ..
EXTERNAL CXDATAREFDEC
C .. Common blocks ..
COMMON /CACHE/PRVLAT
C .. Executable Statements ..
C
C Here, one might adjust the reference counts on any cached data
C In this case ther is only one lattice
C
CALL CXDATAREFDEC(PRVLAT)
20 CONTINUE
PRINT *, 'User destroy hook function called.'
C
RETURN
END
Understanding Reference Counting
Managing Data in Shared Memory
How Reference Counting Works
How IRIS Explorer Implements Reference Counting
Reference-Counting Data Types
Reference Counts for Contained Data Types
Manipulating Reference Counts
Shared Memory Arena
Allocating Memory Efficiently
Recovering from Memory Errors
Examples of Memory Recovery
Example 1
#include <cx/DataAccess.h>
#include <cx/DataOps.h>
cxParameter *parm = NULL;
cxLattice *lat1 = NULL;
cxLattice *lat2 = NULL;
void memCleanup()
{
cxDataRefDec( parm );
cxDataRefDec( lat1 );
cxDataRefDec( lat2 );
}
void userFunction()
{
parm = cxParamNew();
if (cxDataAllocErrorGet())
return; /* no cleanup required */
lat1 = cxLatNew(...);
if (cxDataAllocErrorGet())
{
memCleanup();
return;
}
lat2 = cxLatNew(...);
if (cxDataAllocErrorGet())
{
memCleanup();
return;
}
/*
* Et cetera...
*/
}
Example 2
#include <cx/DataAccess.h>
#include <cx/DataOps.h>
void userFunction()
{
cxParameter *parm = NULL;
cxLattice *lat1 = NULL, *lat2 = NULL;
parm = cxParamNew();
if ( cxDataAllocErrorGet() )
goto parm_Failed;
lat1 = cxLatNew(...);
if ( cxDataAllocErrorGet() )
goto lat1_Failed;
lat2 = cxLatNew(...);
if ( cxDataAllocErrorGet() )
goto lat2_Failed;
/* ... */
/* normal return */
return;
lat2_Failed:
cxDataRefDec( lat1 );
lat1_Failed:
cxDataRefDec( parm );
parm_Failed:
return;
}
Example 3
#include <cx/DataAccess.h>
#include <cx/DataOps.h>
void userFunction()
{
cxLattice *lat1, *lat2;
cxData *data;
cxCoord *coords;
lat1 = cxLatRootNew(...);
if( cxDataAllocErrorGet() ) return;
data = cxDataNew(...);
if( cxDataAllocErrorGet() )
{
cxDataRefDec( lat1 );
return;
}
coords = cxCoordNew(...);
if( cxDataAllocErrorGet() )
{
cxDataRefDec( lat1 );
cxDataRefDec( data );
return;
}
/* cxLatPtrSet doesn't allocate memory */
cxLatPtrSet( lat1, data, NULL, coords, NULL );
lat2 = cxLatNew(...);
if( cxDataAllocErrorGet() )
{
cxDataRefDec( lat1 ); /* NOT data or coords */
return;
}
/* Etcetera .. */
/* normal return */
return;
}
Example 4
#include <cx/DataAccess.h>
#include <cx/DataOps.h>
void userFunction()
{
cxPyramid *pyr;
cxLattice *base, *layer, *other;
cxConnection *conn;
base = cxLatNew(...);
if( cxDataAllocErrorGet() ) return;
pyr = cxPyrNew(...);
if( cxDataAllocErrorGet() )
{
cxDataRefDec( base );
return;
}
cxPyrSet( pyr, base ); /* no allocation */
layer = cxLatNew(...);
if( cxDataAllocErrorGet() )
{
cxDataRefDec( pyr ); /* not base */
return;
}
conn = cxConnNew(...);
if( cxDataAllocErrorGet() )
{
cxDataRefDec( pyr );
cxDataRefDec( layer );
return;
}
cxPyrLayerSet( pyr, i, conn, layer );
if( cxDataAllocErrorGet() )
{
cxDataRefDec( pyr );
cxDataRefDec( layer ); /* layer and conn not in pyr */
cxDataRefDec( conn );
return;
}
other = cxLatNew(...);
if( cxDataAllocErrorGet() )
{
cxDataRefDec( pyr );
return;
}
/* Etcetera .. */
/* normal return */
return;
}
Example 5
#include <cx/DataAccess.h>
#include <cx/DataOps.h>
#include <cx/Geometry.h>
void userFunction()
{
cxGeometry *geo;
geo = cxGeoNew();
if ( cxDataAllocErrorGet() )
return;
cxGeoBufferSelect( geo );
if ( !cxDataAllocErrorGet() )
cxGeoRoot();
if ( !cxDataAllocErrorGet() )
cxGeoDelete();
if ( cxDataAllocErrorGet() )
goto cleanup_selected;
cxGeoXformPush();
if ( !cxDataAllocErrorGet() )
cxGeoLinesDefine(...);
if ( !cxDataAllocErrorGet() )
cxGeoColorAdd(...);
if ( !cxDataAllocErrorGet() )
cxGeoXformPop();
if ( cxDataAllocErrorGet() )
goto cleanup_selected;
cxGeoBufferClose( geo );
if ( cxDataAllocErrorGet() )
goto cleanup;
/* ... */
/* normal return */
return;
cleanup_selected:
cxGeoBufferClose( geo );
cleanup:
cxDataRefDec( geo );
return;
}
Extending the Power of Modules
Communicating with UNIX Processes
An Example Module
/*
* Sample module to demonstrate the use of cxInputAdd()
*
* This module takes a single text type-in, into which should be typed a UNIX
* command. It runs that command, and routes the command's output to its own
* standard output.
*/
#include <stdio.h>
#include <cx/DataTypes.h>
#include <cx/ModuleCommand.h>
#ifdef __cplusplus
extern "C" {
#endif
void selectTestFunc (
unsigned char *command )
{
FILE *fd;
void *handle;
void feedme();
if (strlen(command) == 0) {
return;
}
/*
* First, send the user's command off for invocation, and hang on to a
* descriptor for the pipe.
*/
if ((fd = popen(command, "r")) == NULL) {
cxModAlert("Command failed.");
return;
}
/*
* Now, add this file descripter to the scheduler's select set. When
* data arrives from the command, "feedme" will be called by Explorer.
*/
handle = cxInputAdd(fileno(fd), cx_InputReadMask,
feedme, (void *) fd);
return;
}
/*
* Input available callback.
*
* This is called when the file descriptor from the popen() call above is
* available. The cient data (data) argument is just the FILE * form of the
* descriptor.
*/
void feedme( data, fd, handle )
void *data;
int fd;
void *handle;
{
char buf[BUFSIZ];
int len;
/*
* Read from the pipe. If no data remains on the file descriptor
* (EOF) then remove the file descriptor from the select set.
* Otherwise, just write the data to the window.
*/
len = read(fd, buf, sizeof buf);
if (len <= 0) {
printf("closing pipe\n");
pclose((FILE *) data);
cxInputRemove(handle);
} else {
write(1, buf, len);
}
}
#ifdef __cplusplus
};
#endif
Timer Events
C Version:
/*
* Trigger module -- demonstrates cxTimerAdd()
*
* This module fires every X seconds, where X is the value on
* the dial widget on its control panel
*/
#include <cx/DataTypes.h>
#include <cx/Timer.h>
/*
* Callback routine for the timer.
* All this does is causes the module to fire.
*/
void myTrigger(void *handle)
{
printf("Timer trigger called\n");
cxFireASAP();
}
/*
* Main user function.
*
* secs -- number of seconds to delay (from a dial)
* newtime -- non-zero if the firing resulted from the dial changing
*/
void trigger ( long secs, long newtime )
{
static void *lastTimer = NULL;
/*
* If the dial changed, remove any old interval timers and register a
* new one.
*/
if (newtime) {
if (lastTimer != NULL) {
cxTimerRemove(lastTimer);
}
lastTimer = cxTimerAdd(secs * 1000, 1, myTrigger, &lastTimer);
}
printf("triggered\n");
}
Fortran Version:
SUBROUTINE TRIGGR(SECS,NEWTIM)
C
C User function.
C Trigger module -- demonstrates cxTimerAdd()
C
C This module fires every X seconds, where X is the value on
C the dial widget on its control panel
C
C SECS - number of seconds to delay (from a dial)
C NEWTIM - non-zero if the firing resulted from the dial changing
C
C include "cx/Timer.inc"
INCLUDE '/usr/explorer/include/cx/Timer.inc'
C
C .. Scalar Arguments ..
INTEGER NEWTIM, SECS
C .. Local Scalars ..
INTEGER LSTTIM
C .. External Subroutines ..
EXTERNAL CXTIMERREMOVE, MYTRG
C .. External Functions ..
EXTERNAL CXTIMERADD
C .. Save statement ..
SAVE LSTTIM
C .. Data statements ..
DATA LSTTIM/0/
C .. Executable Statements ..
C
C If the dial changed, remove any old interval timers
C and register a new one.
C
IF (NEWTIM.NE.0) THEN
IF (LSTTIM.NE.0) THEN
CALL CXTIMERREMOVE(LSTTIM)
END IF
LSTTIM = CXTIMERADD(SECS*1000,1,MYTRG,0)
END IF
C
PRINT *, 'TRIGGERED'
RETURN
END
SUBROUTINE MYTRG
C
C Callback routine for the timer.
C All this does is causes the module to fire.
C
C .. External Subroutines ..
EXTERNAL CXFIREASAP
C .. Executable Statements ..
C
PRINT *, 'TIMER TRIGGER CALLED.'
CALL CXFIREASAP
C
RETURN
END
Expanding Filenames
#include <stdio.h>
#include <string.h>
#include <cx/DataAccess.h>
void expand(char *filename)
{
/* terminate early if filename is empty */
if ( strlen(filename) == 0 )
return;
/*
The string returned by cxFilenameExpand must be copied
if it is to be saved for future use. Subsequent calls
to cxFilenameExpand will write over this buffer.
*/
printf("delivered filename: %s\n",filename);
printf("expanded filename: %s\n",cxFilenameExpand(filename));
/* increment counter for %n */
cxFilenameIndexIncrement();
}
Making Subdirectories for Module Development
Creating the Directory
setenv EXPLORERUSERHOME /usr/local/explorer
set path = $EXPLORERHOME/bin $path
mkdir ~/explorer/modules
mkdir ~/explorer
Building Sample IRIS Explorer Modules
cp -r $EXPLORERHOME/src/MWGcode/* ~/explorer/modules
cd ~/explorer/modules
cxmkmf
make Makefiles
make install
Setting Up a Personal Module Tree
cp $EXPLORERHOME/lib/Imakefile.modules Imakefile
chmod +w Imakefile
MODULESUBDIRS = \
cd DIR
mkdir mycontour
vi Imakefile
[add mycontour to the MODULESUBDIRS list]
cxmkmf
make Makefiles
cd mycontour
make all
Last modified: Feb 23 17:00 1999
[ Documentation Home ]