9 Advanced Module Writing

9 Advanced Module Writing

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.

9.1 Controlling Module Functions

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.

Spectrum of Module Complexity

Figure 9-1 Spectrum of Module Complexity


9.1.1 The Module Wrappers

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.

The Module Structure

Figure 9-2 The Module Structure


9.1.1.1 Module Control Wrapper

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 A, "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 Windows modules. The Windows MCW uses the Windows mechanism for handling scheduling.

9.1.1.2 Module Data Wrapper

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:

  • Extracting data from input ports and placing data on output ports.

  • Controlling the interface with the Module Control Wrapper for special callback events.

  • Coercing input data into a form that the module can handle.

9.1.1.3 The Generic Wrapper

The Generic Wrapper is not accessible by the module writer, except indirectly through the Hook Functions menu. It contains:

  • Hook function interfaces

  • User function table (where the name of the user function or MDW routine is declared to the MCW)

  • Declaration of the metatypes (IRIS Explorer's internal form of a data type) for the necessary IRIS Explorer and user-defined types. See Chapter 8, "Creating User-Defined Data Types" for more information on the latter.

  • Port checking code for run-time data constraint checking on lattices.

9.1.2 Working with Module Wrappers

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 a Windows MCW if so selected. You make these choices using the build options control panel. This is invoked by selecting Options... from the Module menu on the Module Builder window. It offers you options for expanding individual control over the interface between the user function and IRIS Explorer.

9.1.2.1 Modules Without MDW

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.

  1. Create the basic module, including input and output ports, the control panel, and the documentation, in the Module Builder.

    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.

  2. Open the Module menu from the main Module Builder window and select Options....

  3. Toggle the Write wrapper code button to No.

The user function must then create the access to input and output ports using API subroutines, such as:

  • cxInputPortOpen to open each port

  • cxInputDataGet to get data from an input port

  • cxOutputDataSet to put data on an output port

  • cxInputDataChanged to check for modified data on an input port

  • cxFireDataChanged to check for modified data on the Fire port

  • cxInputConnectsGet to determine the number of inputs to a port

  • cxFireConnectsGet to determine the number of inputs to the Fire port

  • cxOutputNoSync to prevent data being put on the Firing Done port

For details on these subroutines, see the IRIS Explorer Reference Pages.

9.1.2.2 Using the Windows MCW Option

When you build a module, you can choose whether to generate a default Module Control Wrapper or a Windows MCW. You will require a Windows MCW only if you have a DrawingArea widget, or otherwise require a window procedure for processing window messages.

The Windows MCW provides IRIS Explorer with the ability to recognize and manipulate Windows controls in a module. You can use a drawing area in a module as a Windows input window, to hold a Windows control, or as a general Windows graphics drawing area. An example of an IRIS Explorer module that contains Windows drawing areas and uses a Windows MCW is GenerateColormap.

The code examples in Section 9.1.3 illustrate how to incorporate some of these Windows controls into an IRIS Explorer module.

These are the advantages of using the Windows MCW for customized graphics:

  • It is the only way to incorporate such graphics into an IRIS Explorer module.

  • You may get better performance from the graphics because there is no traffic from the IRIS Explorer user interface to the module.

  • You have complete freedom in creating the user interface. You may even be able to use Windows code that has already been written for another application.

These are the disadvantages:

  • You have to manage the entire user interface for the window yourself, which takes a lot of work.

  • You cannot use parameter functions with widgets managed directly by the module.

  • The module binary file will be larger than that for an IRIS Explorer module, since it must be linked with Windows system files, and that consumes memory.

  • Your module may look different from other IRIS Explorer modules, and a different interface may confuse your users.

9.1.3 Some Examples Using Windows Controls

A few examples follow to show how to write modules that incorporate Windows controls into IRIS Explorer modules.

9.1.3.1 The Drawing Area as an Input Mechanism

This example shows how a Windows drawing area can be used as an input mechanism. It places a push button on a Windows 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 shows how an Windows drawing area can be used
 * as an input mechanism.
 */

#include <stdio.h>
#include <windows.h>

#define BUTTON_ID 100

/* Window procedure for the container window */
LRESULT CALLBACK
myWinEvent(HWND hWnd, UINT message,
     WPARAM wParam, LPARAM lParam)
{
  char str[99];
  static int count = 1; /* Number of times button pressed */

  switch (message)
  {
  case WM_SIZE:
    /* Need to resize button when drawing area is resized */
    MoveWindow(GetDlgItem(hWnd,BUTTON_ID),0,0,LOWORD(lParam),
        HIWORD(lParam),FALSE);
    return 0;
  case WM_COMMAND:
    switch (LOWORD(wParam))
    {
    case BUTTON_ID:
      /* Change button text every time it is pressed */
      sprintf (str,"Pressed %d times",count++);
      SetWindowText((HWND) lParam,str);
      return 0;
    }
  default:
    return DefWindowProc(hWnd, message, wParam, lParam);
  }
}

void xwidget(long window)
{
  static int first = 1;
  static HWND top;

  HWND button;
  HINSTANCE  hInstance;
  RECT       rect;
  WNDCLASS   wdraw;

  /* Do initialization stuff */
  if (first) {
    first = 0;

    /* Get the application instance from the drawing area window */
    hInstance = (HINSTANCE)GetWindowLong((HWND)window,GWL_HINSTANCE);

    /* Register container window class */
    wdraw.style         = 0 ;
    wdraw.lpfnWndProc   = (WNDPROC)myWinEvent;
    wdraw.cbClsExtra    = 0;
    wdraw.cbWndExtra    = 0;
    wdraw.hInstance     = hInstance;
    wdraw.hIcon         = 0;
    wdraw.hCursor       = NULL;
    wdraw.hbrBackground = GetStockObject(WHITE_BRUSH);
    wdraw.lpszMenuName  = NULL;
    wdraw.lpszClassName = "myclass";

    if (!RegisterClass (&wdraw))
      return;

    /* Place container window inside drawing area */
    GetClientRect((HWND)window, &rect);
    top = CreateWindow ("myclass",
      "window",
      WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
      0,
      0,
      rect.right,
      rect.bottom,
      (HWND)window,
      0,
      hInstance,
      NULL);
    ShowWindow (top, SW_SHOW);
    UpdateWindow (top);

    /* Place push button inside container window */
    button = CreateWindow("button","Press here",WS_CHILD | WS_VISIBLE,
      0,0,rect.right,rect.bottom,top,BUTTON_ID,hInstance,NULL);
  }
}

9.1.3.2 Using OpenGL in the Drawing Area

This example uses OpenGL to draw within a drawing area. The code is in %EXPLORERHOME%\src\MWGcode\Advanced\C\GLXWidget_C.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. There is no Fortran version of this module.

/*
 * Example module using OpenGL
 */

#include <stdio.h>
#include <windows.h>
#include <GL/glu.h>

/* Explorer includes */
#include <cx/PortAccess.h>
#include <cx/DataAccess.h>
#include <cx/UserFuncs.h>
#include <cx/UI.h>

/*
 * Widget action routine
 */
LRESULT CALLBACK
myWinEvent(HWND hWnd, UINT message,
           WPARAM wParam, LPARAM lParam)
{
  float p[2];            /* temporary storage for coordinates */
  short xsize, ysize;    /* size of GL widget */
  RECT rc;
  PAINTSTRUCT ps;
  HDC hdc;
  HGLRC hRC;
  char msg[100];
  int iPixelFormat;
  static PIXELFORMATDESCRIPTOR pfd = {
    sizeof(PIXELFORMATDESCRIPTOR),  // size of this pfd
        1,                          // version number
        PFD_DRAW_TO_WINDOW |        // support window
        PFD_SUPPORT_OPENGL |        // support OpenGL
        PFD_DOUBLEBUFFER,           // double buffered
        PFD_TYPE_RGBA,              // RGBA type
        24,                         // 24-bit color depth
        0, 0, 0, 0, 0, 0,           // color bits ignored
        0,                          // no alpha buffer
        0,                          // shift bit ignored
        0,                          // no accumulation buffer
        0, 0, 0, 0,                 // accum bits ignored
        32,                         // 32-bit z-buffer
        0,                          // no stencil buffer
        0,                          // no auxiliary buffer
        PFD_MAIN_PLANE,             // main layer
        0,                          // reserved
        0, 0, 0                     // layer masks ignored
  };

  /* get size of OpenGL window */
  GetClientRect(hWnd,&rc);
  xsize = rc.right;
  ysize = rc.bottom;
  switch (message)
  {
  case WM_LBUTTONDOWN:
    /* Report mouse position in Message widget */
    sprintf (msg,"button pressed at %d, % d",
        LOWORD(lParam),HIWORD(lParam));
    cxInWdgtStrSet("Message",msg);
    break;

  case WM_CREATE:
    hdc = GetDC(hWnd);

    /* choose the window's closest matching pixel format */
    iPixelFormat = ChoosePixelFormat(hdc, &pfd);

    /* update it */
    SetPixelFormat(hdc, iPixelFormat, &pfd);

    hRC = wglCreateContext( hdc );
    wglMakeCurrent( hdc, hRC );

  case WM_PAINT:
  /*
   * some sample OpenGL stuff
   */
    BeginPaint(hWnd, &ps);

    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();
    hdc = wglGetCurrentDC();
    SwapBuffers(hdc);
    EndPaint(hWnd, &ps);
    return 0;

  default:
    return DefWindowProc(hWnd, message,
        wParam, lParam);
  }
}

/*
 * Module initialization routine
 *
 * This routine must be listed as the "initialization" hook function
 */
void xex2_init()
{
  long wid;                     /* window id from UI */
  HINSTANCE  hInstance;
  RECT       rect;
  WNDCLASS   wdraw;
  HWND top;

  /* Get the UI drawing area widget id  */
  wid = cxParamLongGet((cxParameter *)
      cxInputDataGet(cxInputPortOpen("window")));

  hInstance = (HINSTANCE)GetWindowLong((HWND)wid,GWL_HINSTANCE);

  /* Register the draw area class */
  wdraw.style         = 0 ;
  wdraw.lpfnWndProc   = (WNDPROC)myWinEvent;
  wdraw.cbClsExtra    = 0;
  wdraw.cbWndExtra    = 0;
  wdraw.hInstance     = hInstance;
  wdraw.hIcon         = 0;
  wdraw.hCursor       = LoadCursor (NULL,IDC_CROSS);
  wdraw.hbrBackground = GetStockObject(WHITE_BRUSH);
  wdraw.lpszMenuName  = NULL;
  wdraw.lpszClassName = "myclass";

  if (!RegisterClass (&wdraw))
      return;
  GetClientRect((HWND)wid, &rect);

  /* create the OpenGL window */
  top = CreateWindow ("myclass",
      "glarea",
      WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
      0,
      0,
      rect.right,
      rect.bottom,
      (HWND)wid,
      0,
      hInstance,
      NULL);

  ShowWindow (top, SW_SHOW);
  UpdateWindow (top);
}

/*
 * Module execution function
 *
 */

long xex2()
{
    return 0;
}

Figure 9-3 shows this module's control panel, including the drawing area widget and the text box widget which is used to output information about mouse events in the drawing area.

The GLXWidget_C Module Control Panel

Figure 9-3 The GLXWidget_C Module Control Panel


9.1.3.3 Dealing with Mouse Events in the Drawing Area

This example uses standard Windows calls to draw within a drawing area in response to mouse events. The module has two other IRIS Explorer widgets which are used to change the line color and to clear the drawing area. The code is in %EXPLORERHOME%\src\MWGcode\Advanced\C\Scribble.c. The module resource file may also be found in this directory. There is no Fortran version of this module.

/*
 * This example shows how an Windows drawing area can be used
 * as a simple sketch pad. The line color can be changed and the
 * sketch can be cleared using standard IRIS Explorer widgets
 */

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

/* linked list of points. An x value of -1
   indicate the end of a line, in which case
   the y value indicates the color of the next line
*/
struct PointList
{
  int x;
  int y;
  struct PointList *next;
};

/* global variables */
struct PointList *head = NULL,*current = NULL;
HWND drawWnd;
HPEN RedPen,GreenPen,BluePen;
int PenNo,screenx,screeny;

/* function to add a point to global list */
void AddPoint(int x, int y)
{
  if (current == NULL)
  {
    current = malloc (sizeof(struct PointList));
    head = current;
    current->x = x;
    current->y = y;
    current->next = NULL;
    return;
  }
  if ((x == -1) && (current->x == -1))
  {
    current->y = y;
    return;
  }
  current->next = malloc (sizeof(struct PointList));
  current = current->next;
  current->x = x;
  current->y = y;
  current->next = NULL;
}

/* function to delete all points from list */
void ClearPoints()
{
  struct PointList *ptr,*last;

  ptr = head;
  while (ptr != NULL)
  {
    last = ptr;
    ptr = ptr->next;
    free(last);
  }
  current = head = NULL;
}

/* function to select line color */
HPEN SelectPen(HDC hdc, int color)
{
  switch (color)
  {
  case 1:
      return SelectObject(hdc,RedPen);
  case 2:
      return SelectObject(hdc,GreenPen);
  case 3:
      return SelectObject(hdc,BluePen);
  default :
      return NULL;
  }
}

/* window procedure to handle messages to draw area */
LRESULT CALLBACK
myWinEvent(HWND hWnd, UINT message,
           WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  static int xPos,yPos,color,width,height;
  RECT rc;
  PAINTSTRUCT ps;
  struct PointList *ptr;
  HPEN OldPen = NULL;

  GetClientRect(hWnd,&rc);
  width = rc.right;
  height = rc.bottom;

  switch (message)
  {
  case WM_LBUTTONDOWN:
    xPos = LOWORD(lParam);
    yPos = HIWORD(lParam);
    AddPoint(xPos*screenx/width,yPos*screeny/height);
    SetCapture(hWnd);
    return 0;
  case WM_LBUTTONUP:
    ReleaseCapture();
    AddPoint(-1,PenNo);
    return 0;
  case WM_MOUSEMOVE:
    if (GetCapture() != hWnd) return 0;
    if ((LOWORD(lParam) > width) || (HIWORD(lParam) > height))
      return 0;
    hdc = GetDC(hWnd);
    OldPen = SelectPen(hdc,PenNo);
    MoveToEx(hdc,xPos,yPos,NULL);
    xPos = LOWORD(lParam);
    yPos = HIWORD(lParam);
    AddPoint(xPos*screenx/width,yPos*screeny/height);
    LineTo(hdc,xPos,yPos);
    if (OldPen != NULL) SelectObject(hdc,OldPen);
    ReleaseDC(hdc,hWnd);
    return 0;
  case WM_PAINT:
    /* draw all stored lines */
    ptr = head;
    hdc = BeginPaint(hWnd,&ps);
    while (ptr != NULL)
    {
      color = -1;
      while ((ptr != NULL) && (ptr->x == -1))
      {
        color = ptr->y;
        ptr = ptr->next;
      }
      if (ptr != NULL)
      {
        if (color != -1)
        {
          MoveToEx(hdc,ptr->x*width/screenx,
           ptr->y*height/screeny,NULL);
          if (OldPen != NULL) SelectObject(hdc,OldPen);
          OldPen = SelectPen(hdc,color);
        }
        else LineTo(hdc,ptr->x*width/screenx,
            ptr->y*height/screeny);
        ptr = ptr->next;
      }
    }
    EndPaint(hWnd,&ps);
    return 0;
  default:
    return DefWindowProc(hWnd, message,
        wParam, lParam);
  }
}

/* initialisation hook function */
void init (  )
{
  HWND parent;
  HINSTANCE  hInstance;
  RECT       rect;
  WNDCLASS   wdraw;


  parent = (HWND)  cxParamLongGet((cxParameter *)cxInputDataGet(
      cxInputPortOpen("Window")));

  hInstance = (HINSTANCE)GetWindowLong(parent,GWL_HINSTANCE);

  /* Register the draw area class */
  wdraw.style         = 0 ;
  wdraw.lpfnWndProc   = (WNDPROC)myWinEvent;
  wdraw.cbClsExtra    = 0;
  wdraw.cbWndExtra    = 0;
  wdraw.hInstance     = hInstance;
  wdraw.hIcon         = 0;
  wdraw.hCursor       = LoadCursor (NULL,IDC_ARROW);
  wdraw.hbrBackground = GetStockObject(WHITE_BRUSH);
  wdraw.lpszMenuName  = NULL;
  wdraw.lpszClassName = "myclass";

  if (!RegisterClass (&wdraw))
    return;

  GetClientRect(parent, &rect);
  drawWnd = CreateWindow ("myclass",
    "window",
    WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
    0,
    0,
    rect.right,
    rect.bottom,
    parent,
    0,
    hInstance,
    NULL);

  ShowWindow (drawWnd, SW_SHOW);
  UpdateWindow (drawWnd);

  /* create pens */
  RedPen = CreatePen(PS_SOLID,0,RGB(255,0,0));
  GreenPen = CreatePen(PS_SOLID,0,RGB(0,255,0));
  BluePen = CreatePen(PS_SOLID,0,RGB(0,0,255));

  /* get size of the screen in pixels */
  screenx = GetSystemMetrics(SM_CXSCREEN);
  screeny = GetSystemMetrics(SM_CYSCREEN);
}

/* Module execution function */
void compute (int LineColor, int Clear )
{
  if (Clear)
  {
    /* delete points and repaint draw area */
    ClearPoints();
    InvalidateRect(drawWnd,NULL,TRUE);
  }
  /* change to selected line color for next line */
  PenNo = LineColor;
  AddPoint(-1,LineColor);
}

/* remove hook function */
void destroy()
{
  /* clean up */
  ClearPoints();
  DeleteObject(RedPen);
  DeleteObject(GreenPen);
  DeleteObject(BluePen);
}

Figure 9-4 shows this module's control panel, including the drawing area widget and the two widgets which are used to control some aspects of the drawing area.

The Scribble Module Control Panel

Figure 9-4 The Scribble Module Control Panel


9.2 Sharing Module Executables

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.

9.2.1 Types of Shared Executable

A combination module determines its actions in two ways. It can:

  • keep a table of known actions and make a selection from the action table based on the module name which invoked the executable. ImageVision Library modules such as AddImg and XorImg act in this way.

  • make use of an interpreter module that reads a script or program provided on an input port, then performs the actions dictated by the script. The LatFunction module functions on this principle.

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.

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.

9.2.2 Using an Alternate Executable

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 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:

  • Select whether you wish to have such an alternate executable.

  • Type in the name of the executable file.

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.

9.3 Building Loop Controller Modules

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.

9.3.1 Using the Loop Controller API

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.

9.3.2 Using Generic Controller Modules

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 Section 2.7.1) and run the Module Builder on the new module.

The While module has the ports listed in Table 9-1:

Table 9-1 While Module Ports

Input Ports Output Ports
Condition Final Value 0
Initial Value 0 Final Value 1
... ...
LoopIn Value 0 LoopOut Value 0
LoopIn Value1 LoopOut Value1

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.

9.3.2.1 The For Module

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.

9.3.3 The cxGeneric Data Type

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.

9.4 Installing Interpreter Modules

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.

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-2 lists three examples:

Table 9-2 Interpreter Module Files

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

These are the rules that the Module Builder follows during installation.

  • If there is no file, then no addition is made to the list of files to install.

  • If it finds a modulename.interp file, then all alternate executable modules relying on modulename will have the contents of the text or file-browser widget added to their file list.

  • If the modulename.interp file has anything other than NULL for suffix, then in the absence of the named widget, the file modulename.suffix will be added to the installation list.

  • If the modulename.interp file has NULL for suffix, then no other files will be added to the installation list.

9.5 Hook Functions

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:

  • creating the module

  • initializing the module

  • destroying the module

  • making a connection to an input or output port in the module

  • breaking a connection to an input or output port in the module

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:

  • Write the hook functions you want and define them in the user function

  • List each hook function in the Hook Functions window. This is invoked via the Hook functions... option on the Module menu of the Module Builder main window.

Hook functions written in C++ must have a C linkage. You can do this by declaring the function:

extern "C" my_hook_fcn( )
{ ...
}

9.5.1 Hook Function Calling Sequence

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:

C:

void CreateHook(void);

Fortran:

subroutine CreateHook

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:

C:

void ConnectInput(const char *portName, int linkID);

Fortran:

subroutine ConnectInput(portName, linkID)
character*(*) portName
integer linkID

The MCW calls this function after the user has connected the input port called portName to some output.

9.5.2 Writing Hook Functions

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:

  1. Select Hook functions... from the Module menu in the Module Builder window to open the Hook Functions window (see Figure 9-5).

  2. Type the name you wish to give each hook function in the slot next to its description. For example, you may type myDestroyFunc as the function name in the slot next to Remove Hook.

  3. When you have finished listing the hook functions for the module, click on OK. When you select Build or Build and Install from the Module menu, the Module Builder creates the hook function table in the Generic Wrapper.

    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.

The Hook Function Window

Figure 9-5 The Hook Function Window


Here are some points to note when you are writing a hook function.

  • When you request that a module be deleted, a ‘destroy’ request is sent to the module. If the module is firing, the ‘destroy’ request is not processed until the firing is completed, at which point the removal hook function will be called. However, if you delete the module a second time before it finishes firing, you kill the module's process and the removal hook function cannot be called.

  • If the module retains cached data, you must decrement the reference counts when the module is deleted. The module removal hook is responsible for removing any persistent data in shared memory. If the process is killed, however, this cleanup does not take place.

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.

9.5.3 Examples of the Use of Hook Functions

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.

Example 9-1 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");
}

Example 9-2 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     Connect hook function in Fortran.
C
C     This 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

9.6 Understanding Reference Counting

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.

9.6.1 Managing Data in Shared Memory

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.

9.6.2 How Reference Counting Works

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.

9.6.3 How IRIS Explorer Implements Reference Counting

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.

9.6.4 Reference-Counting Data Types

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.

9.6.4.1 Reference Counts for Contained Data Types

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.

9.6.4.2 Manipulating Reference Counts

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.

9.6.5 Shared Memory Arena

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.

9.6.5.1 Allocating Memory Efficiently

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.

9.6.5.2 Recovering from Memory Errors

A new mechanism for dealing with memory errors has been implemented for release 5.0 of IRIS Explorer. You are referred to cxDataManAbortOnError in the IRIS Explorer Reference Pages for full details.

9.7 Extending the Power of Modules

Several subroutines in the API allow you to increase the flexibility of your module. You can:

  • time the occurrence of events, using cxTimerAdd

  • expand a filename into the directory path, using cxFileNameExpand

The use of these routines is described briefly in the next sections.

9.7.1 Timer Events

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.

Example 9-3 C Version of a Module Using cxTimerAdd:

/*
 * 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");
}

Example 9-4 Fortran Version of a Module Using cxTimerAdd:

      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

9.7.2 Expanding Filenames

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. There is no equivalent Fortran version for this module.

Example 9-5 Code to Demonstrate the Use of FilenameExpand

#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();
}

Website Feedback

If you would like a response from NAG please provide your e-mail address below.

(If you're a human, don't change the following field)
Your first name.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.