The following sample makefile templates for creating outboard modules can be found in /usr/local/dx/samples/user:
Replace makex.o, add.o, and hello.o with the names of your .o files; replace m_Hello, etc. with the names of your modules; and replace hello, etc. with the names you want for your executables. The .mdf file for the outboard modules is user_outboard.mdf.
Linking Outboard Modules |
---|
Typically outboard modules are linked to the library dxlite, which contains the Data Explorer data model routines (see Appendix B. "Data Explorer Data Model Library: DXlite Routines"). This library does not contain all of the Data Explorer routines (see Appendix C. "Data Explorer Library Routines"), and an outboard module requiring access to such "additional" routines must be linked to the library dxcallm. However, the resulting outboard executable will be significantly larger than it would be otherwise. |
Starting Data Explorer requires specifying the .mdf file to the user interface:
dx -mdf my.mdf
Notes:
Executive("mdf file", "module_name.mdf"); $sync
To debug a module you must first modify the CFLAGS line of the makefile to compile your source code as debuggable (-g) rather than optimized (-O).
Note: Data Explorer library routines are available only as optimized object code.
To debug a module, start Data Explorer with the additional flag -outboarddebug. Instead of automatically starting the module, Data Explorer will prompt you to start the executable. You can then run the module from a debugger, using the flags specified to you by Data Explorer when it prompts you to start the module.
The simplest type of outboard module does not need to save information, does not communicate with any other process, and does not cause asynchronous executions. It takes inputs, computes something based on them, and returns outputs. The executable program that makes up the outboard module is run each time the module is called, and it exits after returning the output values.
To prevent the executable program of the outboard module from exiting after each execution, set the PERSISTENT flag on the FLAGS line in its .mdf file. This setting may be necessary so that the module can save information from one execution to another, or because repeated exits and restarts take too much time.
A persistent outboard module is started from the user interface the first time the module is called and does not exit until its icon is deleted from the network (program), the entire network is deleted, or the Reset Server option is selected from the Connection pull-down menu.
In script mode, persistent outboard modules are loaded the first time they are called and they do not exit until the executive exits or the command
Executive ("flush dictionary");is run.
Global variables can be safely used to save information between executions of an outboard module. If the same outboard module occurs in a network more than once, a different process is started for each occurrence.
Note that the module may not be called at every execution: If the inputs are changed from their original values and then back again, Data Explorer saves the previous results and uses them without recalling the module. The "cache none" option prevents previous results from being saved, but you also need to set "cache none" for all downstream modules that process the outputs. Otherwise, caching at the lower levels will still prevent the module from being called each time. The SIDE_EFFECT flag specifies that the module is to be called each time, the performance penalty being that the module continues to execute even if the inputs remain unchanged.
An asynchronous module can request that it be rerun. See 11.2 , "Asynchronous Modules".
If an outboard module should be run on one particular machine (perhaps because it is compute intensive and needs to run on a fast machine, or because it needs to access a peripheral that is connected to only one machine), the OUTBOARD line can specify a host name as well as an executable name. The Data Explorer libraries will take care of establishing a connection between where the main Data Explorer executive is running and the outboard host machine. The DXMODULES environment variable or the -modules flag can be used to specify a search path for outboard module executables, or the OUTBOARD line can specify a fully qualified path name.
A valid .rhosts file must be present to allow Data Explorer to use the "rsh" command to start a process on another machine. (See the UNIX manual page for "rsh" or "remsh" for more information.)
DXReadyToRun cannot be called from the time a module receives its inputs until after it returns its outputs. To trigger another execution immediately, it can do so after the call to DXCallOutboard() in the outboard.c file.
Outboard modules cannot be written in coroutine style. They cannot produce outputs without being called by Data Explorer and thereby receiving new inputs (which can be ignored), and they must return something - the main Data Explorer executive will wait for the module to return before continuing.
An asynchronous module cannot be run in distributed mode, but it can be executed on another machine by setting the host name on the OUTBOARD line.
The function of this example module is to monitor the status of a given file. Whenever the file is modified, its data are reimported. For example, this program could be used to monitor the output of a simulation program. The data can be plotted as they are created.
This sample program is /usr/local/dx/samples/outboard/watchfile.c. The same directory also holds the associated .mdf file (watchfile.mdf) and an example (watchsocket.c) that listens for input over a socket. See /usr/local/dx/samples/Outboard/Readme for more information abut sample modules.
/* * sample asynchronous outboard module. * * uses a signal to ask to be woken after a certain delay. * if a given file has been changed, re-import the data. * * see watchfile.mdf, which must be loaded before this can be run. * also see Makefile_architecture.name for how to compile this. */ #include <dx/dx.h> #include <unistd.h> #include <signal.h> #include <stdio.h> #include <sys/stat.h> static Pointer id = NULL; static time_t lastchanged; static int seconds; static char filename[1024];
/* * this routine is called each time the alarm signal is * issued. */ void signalcatch() { struct stat Buffer; time_t changed; /* stat the file to find out its last modification time */ if (stat(filename, &Buffer) == 0) { /* the last time the file was changed */ changed = Buffer.st_mtime; /* compare to the last time the file was checked */ if (lastchanged != changed) { /* the file has changed. Rerun the main program. */ DXReadyToRun(id); }
/* else the file hasn't changed since last time we checked */ else { /* go back to sleep for some seconds, but first reset the * alarm */ signal(SIGALRM, signalcatch); alarm(seconds); } } }
Error m_WatchFile(Object *in, Object *out) { struct stat Buffer; char *file; /* the first input is the filename to check */ if (!in[0]) { DXSetError(ERROR_MISSING_DATA,"missing filename"); return ERROR; } if (!DXExtractString(in[0], &file)) { DXSetError(ERROR_BAD_PARAMETER,"filename must be a string"); return ERROR; }
/* put the filename into a static global variable */ strcpy(filename,file); /* the second input is the number of seconds to wait between checks */ /* the default is 10 seconds */ if (!in[1]) seconds = 10; else { if (!DXExtractInteger(in[1], &seconds)) { DXSetError(ERROR_BAD_PARAMETER,"seconds must be an integer"); return ERROR; } }
/* the first time through, get the module id for the DXReadyToRun call */ if (!id) { id = DXGetModuleId(); if (!id) { out[0] = NULL; return ERROR; } } /* get the last modification time of the file */ if (stat(filename, &Buffer) != 0) { DXSetError(ERROR_BAD_PARAMETER,"file %s not found"); return ERROR; } lastchanged = Buffer.st_mtime;
/* import the data from the file */ out[0] = DXImportDX(filename, NULL, NULL, NULL, NULL); /* set the alarm for the next wakeup */ signal(SIGALRM, signalcatch); alarm(seconds); return OK; }
Note: If this program were compiled and linked as an inboard module, the global variables would have to be stored in the cache and associated with the module ID. Otherwise, the global variables would be shared among all calls to the module.