Programmer APIEmpty
Some example source code is shown here, the collaborative code is highlighted in blue and the comments are in green. The modules shown here are MShareLat, a completely new module, and MShareGraph3D which is based on the Graph3D module but allows for collaborative work. The lines of code of MShareGraph3D shown in black are the original module code of Graph3D. This demonstrates how little additional code was required to convert it to be a collaborative module.

All of the source code shown here, plus code for all of the other collaborative modules, is available in $EXPLORERHOME/src/

MShareLat
// Collab Includes
#include <cx/cxCollab.h>

// Explorer Includes
#include <cx/Typedefs.h>
#include <cx/Info.h>
#include <cx/cxLattice.api.h>
#include <cx/DataAccess.h>
#include <cx/PortAccess.h>

#ifdef __cplusplus
    extern "C" {
#endif
/*
 *                User Function
 */
void ShareLat(long pass,cxLattice **OutLat)
{
    static cxCollab collab_info;//Create a static instance of the collaboration
                                //class so that connection state and other info isn't
                                //lost between each invocation of this function.
    static int firsttime = 1;


    if (firsttime == 1) {
        firsttime = 0;
        //Register a single collaborative port: name, source type, datatype
        collab_info.portRegister("In_Lat",INPUT,LATTICE);
        return;
    }

    // Check reserved widgets looking for connect/disconnect instructions
    collab_info.checkWidgets();

    // If connected then can do things
    if (collab_info.isConnected()) {
        // Check for data to read
        if (collab_info.newData("In_Lat")) {
            *OutLat = collab_info.getLattice("In_Lat");
            cxDataRefInc((void *)*OutLat);
        }
    }

    // Do stuff in here if need to, sing, dance etc . . .

    // If connected then can do things
    if (collab_info.isConnected()) {
        // Check for data to send
        int port = cxInputPortOpen("In_Lat");
        if (cxInputDataChanged(port)) 
            collab_info.sendData("In_Lat");
    }

    if ((pass) && (cxInputDataChanged(cxInputPortOpen("In_Lat")))) {
        *OutLat = (cxLattice *)cxInputDataGet(
                cxInputPortOpen("In_Lat"));
    }

} // end User function

#ifdef __cplusplus
}
#endif

==============================================================================
MShareGraph3D

// Collab Includes
#include <cx/cxCollab.h>

/* Inventor Includes */
#include <Inventor/nodes/SoGroup.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/fields/SoSFVec3f.h>
#include <Inventor/nodes/SoComplexity.h>
#include <Inventor/nodes/SoShapeHints.h>

/* Explorer Includes */
#ifdef WIN32
#include <float.h>
#else
#include <values.h>
#endif
#include <cx/DataAccess.h>
#include <cx/DataTypes.h>
#include <cx/Geometry.h>
#include <cx/Lookup.h>
#include <cx/PortAccess.h>
#include <cx/UI.h>


// A convenient way to list all of the ports that we are going
// to register for collaboration, along with their port types and data types
char *port_names[] = {"Graph Type","Color By What","Height Scale",
                                "Surface Scale","Treat Negatives As ?"};
int port_dataTypes[] = {PARAMETER,PARAMETER,PARAMETER,PARAMETER,PARAMETER};
int port_types[] = {INPUT,INPUT,INPUT,INPUT,INPUT};
int num_ports = 5;

#ifdef __STDC__
extern "C" void graph3d(cxGeometry **geom)
#else
extern "C" void graph3d(geom)
     cxGeometry **geom;
#endif
{
    static cxCollab collab_info; // class instance for collaborative elements
    static int firsttime = 1;

    cxCoord *coord;
    cxCoordType coordtype;
    cxData *data_struct;
    cxErrorCode err;
    cxLookup *clut;
    cxPrimType primType;
    double smin, smax;
    float color_array[4], color_value, *coordvals, cyl_point[6], *data,
    delx, dely, dx, dy, max_val, min_val, point[3], radius, scalefactor,
    size, s_min = 0.01, s_max = 1.0, xmin, ymin; 
    int port;
    long *dims, flag, i, j, nDataVar, nDim;
    int cmap_flag;
    int ngtve;
    double val;
    

    // Ports handled internally
    cxLattice *lat, *cmap;
    long color_by_what, graph_type, n_vals;
    double h_scale, s_scale;
    cxParameter *Scale_Param;

    lat = (cxLattice *)cxInputDataGet(cxInputPortOpen("Data In"));
    cmap = (cxLattice *)cxInputDataGet(cxInputPortOpen("ColorMap"));

    // Check to see if we are connected. If we are then check to see if any
    // new data has arrived on any of the ports that we registered as being
    // collaborative. If we find new data (or the "sync values" button has
    // been pressed) compile a list of port names for which data should be
    // sent. Then send the data. 
    if (collab_info.isConnected()) {

        char **portNames = new char * [num_ports];
        int port,numPorts=0;
        int sendAll = FALSE;
        port = cxInputPortOpen("Sync Values");
         if (cxInputDataChanged(port))
            sendAll = TRUE;

        // Compile list of port names for which data is new
        for (int i = 0; i < num_ports; i++ ) {
            port = cxInputPortOpen(port_names[i]);
            if ((cxInputDataChanged(port)) || (sendAll)) {
                int len = strlen(port_names[i])+1;
                portNames[numPorts] = new char [len];
                bzero(portNames[numPorts],len);
                strcpy(portNames[numPorts],port_names[i]);
                numPorts++;
            }
        }

        // If some new data, then send to collaborators
        if (numPorts > 0) 
            collab_info.sendData(numPorts,portNames);
        //collab_info.sendData(portNames[0]);

        // Tidy Up
        for (i = 0; i < numPorts; i++ )
            free(portNames[i]);
        free(portNames);

        // If we were just sync(ing) values, then stop having sent the
        // data values.
        if (sendAll)
            return;

    }

    long dummy = cxParamLongGet((cxParameter*)cxInputDataGet(
                                cxInputPortOpen("Sync Values")));
    graph_type = cxParamLongGet((cxParameter*)cxInputDataGet(
                                cxInputPortOpen("Graph Type")));
    color_by_what = cxParamLongGet((cxParameter*)cxInputDataGet(
                                cxInputPortOpen("Color By What")));
    n_vals = cxParamLongGet((cxParameter*)cxInputDataGet(
                                cxInputPortOpen("Treat Negatives As ?")));
    h_scale = cxParamDblGet((cxParameter*)cxInputDataGet(
                                cxInputPortOpen("Height Scale")));
    s_scale = cxParamDblGet((cxParameter*)cxInputDataGet(
                                cxInputPortOpen("Surface Scale")));
    if (firsttime == 1) {
        firsttime = 0;
        // Register (once only) the names of ports that we want to be 
        // collaborative
        collab_info.portRegister(num_ports,port_names,
                                            port_types,port_dataTypes);
        // consume this value
        long dummy = cxParamLongGet((cxParameter*)cxInputDataGet(
                                cxInputPortOpen("Sync Values")));
        return;
    }

    // Check reserved widgets looking for connect/disconnect/re-connect 
    // instruction 
    collab_info.checkWidgets();

    // If connected then can do things
    if (collab_info.isConnected()) {

        // Turn on Sync Values button
        cxInWdgtShow("Sync Values");

        // Check for data to be read from the collaborative session
        // associated with port names that we have registered.
        // Update the control panel if new data is found.
        if (collab_info.newData("Graph Type")) {
            graph_type = cxParamLongGet(collab_info.getParameter("Graph Type"));
            cxInWdgtLongSet("Graph Type",graph_type);
        }
        if (collab_info.newData("Color By What")) {
            color_by_what = cxParamLongGet(
                        collab_info.getParameter("Color By What"));
            cxInWdgtLongSet("Color By What",color_by_what);
        }
        if (collab_info.newData("Treat Negatives As ?")) {
            n_vals = cxParamLongGet(
                            collab_info.getParameter("Treat Negatives As ?"));
            cxInWdgtLongSet("Treat Negatives As ?",n_vals);
        }
        if (collab_info.newData("Height Scale")) {
            h_scale = cxParamDblGet(collab_info.getParameter("Height Scale"));
            cxInWdgtDblSet("Height Scale",h_scale);
        }
        if (collab_info.newData("Surface Scale")) {
            s_scale = cxParamDblGet(collab_info.getParameter("Surface Scale"));
            cxInWdgtDblSet("Surface Scale",s_scale);
        }

    }

    // If no data then finish
    if (lat == NULL) 
        return;

    /* Get the input lattice and determine its data type */
    err = cxLatDescGet(lat, &nDim, &dims, NULL, &nDataVar, &primType,
                NULL, NULL, &coordtype);
    if (err == cx_err_error) {
        cxModAlert("No memory to get lattice description, return control");
        return;
    }

    /* Get the data structure and data */
    cxLatPtrGet(lat, &data_struct, (void **) (&data), &coord, 
                (void **) (&coordvals));
    if (err == cx_err_error) {
        cxModAlert("Invalid lattice pointer used, return control");
        return;
    }

    /* Get the maximum size of the ground area for the blocks
     * This code assumes that a uniform lattice is connected
     */
    if (coordtype != cx_coord_uniform) {
        cxModAlert("Unexpected lattice type connected, return control");
        return;
    }

    delx = (coordvals[1] - coordvals[0])/((float) dims[0] - 1.0);
    dely = (coordvals[3] - coordvals[2])/((float) dims[1] - 1.0);
    dx = 0.5 * delx;
    dy = 0.5 * dely;
    xmin = coordvals[0];
    ymin = coordvals[2];

    /* If a colour map is attached, generate its lookup table */
    if  (cmap != NULL) {
        clut = cxLookupCreate(cmap, cx_lookup_nearest);
        cmap_flag = 1;
    }
    else
        cmap_flag = 0;

    /* Find minimum and maximum values in input lattice */
#ifdef WIN32
    min_val = FLT_MAX;
    max_val = FLT_MIN;
#else
    min_val = MAXFLOAT;
    max_val = MINFLOAT;
#endif
    for (j = 0; j < dims[1]; j++) {
        for (i = 0; i < dims[0]; i++) {
            val = extract_value(data, primType, (j*dims[0]+i));
            if (min_val > val)
                min_val = val;
            if (max_val < val)
                max_val = val;
        }
    }

    /* Scale the height according to the minimum and maximum values
     * allowing for a user-specified multiplication factor h_scale
     */
    if (max_val == min_val)
        scalefactor = 0.08 * (float) h_scale;
    else
        scalefactor = 8.0 * (float) h_scale / (max_val - min_val);

    /* Set external Scale Factor */
    Scale_Param = cxParamDoubleNew((double)scalefactor);
    cxOutputDataSet(cxOutputPortOpen("Scale"),(void *)Scale_Param);

  /* Initialise the geometry structure */
  *geom = cxGeoNew();
  cxGeoBufferSelect(*geom);

  /* New code to use shared memory geometry transcription */
  port = cxOutputPortOpen("Geometry");
  cxGeoBufferPortSet(port);

  /* Create the geometry */
  cxGeoRoot();
  cxGeoDelete();

   /* If a colour map is attached get a colour value
   * else use a fixed value 0f 0.8
   */
  if (!cmap_flag) {
      color_array[0] = 0.8;
      color_array[1] = 0.8;
      color_array[2] = 0.8;
    }

    /* The distance between blocks is determined by the 
      * user-specified scale factor s_scale
      */
     if (s_scale < s_min)
       {
         /* If the size would be too small then it is clamped
           * and the widget must be examined for correct values
          * before setting it to the clamped value
          */
         size = s_min;
         cxInWdgtMinMaxGet("Surface Scale", &smin, &smax);
         flag = 0;
         if (smin > size)
        {
          smin = (double) size;
          flag = 1;
        }
             if (smax < smin)
        {
          smax = smin;
          flag = 1;
        }
          if (flag == 1)
        cxInWdgtDblMinMaxSet("Surface Scale", smin, smax);
          cxInWdgtDblSet("Surface Scale", (double) size);
        }
      else if (s_scale > s_max)
        {
          /* If the size would exceed the available space then it is
           * clamped and the widget must be examined for correct
           * values before setting it to the clamped value
           */
          size = s_max;
          cxInWdgtMinMaxGet("Surface Scale", &smin, &smax);
          flag = 0;
          if (smin > size)
        {
          smin = (double) size;
          flag = 1;
        }
          if (smax < smin)
        {
          smax = smin;
          flag = 1;
        }
          if (flag == 1)
        cxInWdgtDblMinMaxSet("Surface Scale", smin, smax);
          cxInWdgtDblSet("Surface Scale", (double) size);
        }
      else
        size = (float) s_scale;

  /* Build up the geometry by inspecting each node */
  for (j = 0; j < dims[1]; j++)
    {
      for (i = 0; i < dims[0]; i++)
    {
    
        val = extract_value(data, primType,(j*dims[0]+i));

        /* The colour used for the node */
        if (cmap_flag) { 
            switch (color_by_what) 
                {
                    case 0:
                      color_value = (float) i / (float) (dims[0]-1);
                      break;
                    case 1:
                      color_value = (float) j / (float) (dims[1]-1);
                      break;
                case 2:
                color_value = (float) val;
                cxLookupInterp(clut, &color_value, (void *)color_array);
                break;
                }
            cxLookupInterp(clut, &color_value, (void *)color_array);
        }

        /* See if negative */
        if (val < 0.0)
            ngtve = 1;  /*True*/
        else 
            ngtve = 0;  /* False */

        /* Make Positive */
        if ((n_vals == 1) && (ngtve)) {
            val =  -1 * val;
            ngtve = 0;  /* False */
        }

        /* Select the type of graph */
        switch (graph_type) 
        {
        case 0:
          /* Blocks */

            if ((n_vals != 2) || (!ngtve)) {

            /* Create a group */
            SoGroup *Group = new SoGroup ;
            Group->ref();

            /* Add Color */
            SoMaterial *mtl = new SoMaterial;
            Group->addChild(mtl);
            mtl->diffuseColor.setValue(color_array[0],color_array[1],
                        color_array[2]);

            /* Create Transform */
            SoTransform *Trans = new SoTransform;
            Group->addChild(Trans);
            point[0] = (float) xmin + i * delx ;
            point[1] = (float) ymin + j * dely ;
            point[2] = (float) (scalefactor * val)/2 ;
            Trans->translation.setValue(point);
            SoComplexity *complex = new SoComplexity;
            Group->addChild(complex);
            complex->value.setValue(0.2);

            /* Create cube */
            point[0] = (float) (xmin + i * delx + dx * size) - 
                                    (xmin + i * delx - dx * size);
            point[1] = (float) (ymin + j * dely + dy * size) - 
                                    (ymin + j * dely - dy * size);
            if (ngtve)
                point[2] = (float) scalefactor * val * -1;
            else
                point[2] = (float) scalefactor * val ;
            SoCube *cube = new SoCube;
            Group->addChild((SoNode *)cube);
            cube->width.setValue(point[0]);
            cube->height.setValue(point[1]);
            cube->depth.setValue(point[2]);
            /* Add to Explorer Geom */
            cxGeoInventorDefine(Group);
            Group->unref();
            }
            
          break;

        case 1:
          /* Cylinders */
              /* A cylinder is defined by 2 centres and a radius
               * A cone is defined by a centre, a top and a base radius
               */
            if ((n_vals != 2) || (!ngtve)) {
              cyl_point[0] = cyl_point[3] = xmin + i * delx;
              cyl_point[1] = cyl_point[4] = ymin + j * dely;
              cyl_point[2] = 0.0;
              cyl_point[5] = scalefactor * val; 
              if (dx < dy)
                radius = dx * size;
              else
                radius = dy * size;
        
              cxGeoCylindersDefine(1, &cyl_point[0], &cyl_point[3],
                   &radius);
              cxGeoComplexityAdd(0.2);
              cxGeoColorAdd(1, color_array, CX_GEO_PER_OBJECT);
            }
          break;

        case 2:
               /* Cones */
              /* A cylinder is defined by 2 centres and a radius
               * A cone is defined by a centre, a top and a base radius
               */
            if ((n_vals != 2) || (!ngtve)) {
              cyl_point[0] = cyl_point[3] = xmin + i * delx;
              cyl_point[1] = cyl_point[4] = ymin + j * dely;
              cyl_point[2] = 0.0;
              cyl_point[5] = scalefactor * val; 
              if (dx < dy)
                radius = dx * size;
              else
                radius = dy * size;
        
              cxGeoConesDefine(1, &cyl_point[0], &cyl_point[3],
                           &radius);
              cxGeoComplexityAdd(0.2);
              cxGeoColorAdd(1, color_array, CX_GEO_PER_OBJECT);
            }
        break;

        default:
             /* Unexpected type */
              cxModAlert
            ("unexpected graph type requested, return control");
          return;
        }
        
    }
    }
  if (cmap_flag) {
      cxLookupDestroy(clut);
  }
  cxGeoBufferClose(*geom);
}