User-compiled Functions
STEPforward allows the end user to encapsulate a piece of logic into a box called "User-defined Function" and reuse it in various Procedures, Reports and Processes. STEPforward also provides an inventory of the most commonly used functions that are ready for use the moment STEPforward is installed.
Although user-defined functions can be assembled from a rich assortment of pre-fabricated objects that address most of the database communication, text and numbers manipulation, decision-making etc. problems, there may come a time when the user will wish to construct his/her own piece of code not limited to the functionality available to the user-defined functions from the inventory of graphical tools.
STEPforward provides for this by allowing the end users to compile their own Objective-C code into a bundle and incorporate it into STEPforward by dynamically loading the bundle at the launch time. The loading is done automatically, once the bundle is copied into Resources folder of StepForward.app bundle. This pre-compiled bundle appears in STEPforward as a new function that can be used like any other user-defined or system-provided function.
In order to work properly, the user-compiled function must conform to the rules described in the following section.
1. | General structure of functions |
Once brought into the Workspace from the Palette window, the functions expand into the following generic graphical form:
In its most general form, the function can take a number of input parameters, and return one or more values back to the graphical Procedure (or Report, or Process) where it is located. Variations are possible, when a function does not return any value, or does not take any parameters. Sometimes functions may return a single value through the slot provided at the bottom of the graphic, and sometimes they can return multiple values through the parameter slots (in which case they become input/output, rather than just input, parameters).
Ultimately, the function may not have any parameters and not return any value; such function may nevertheless serve a useful purpose that is pre-programmed in its body and simply does not need to communicate with the rest of the world in order to do its job.
Whatever shape and form the functions have, they all must be subclasses of one abstract superclass, SFSystemFunction, which itself is a subclass of NSObject. SFSystemFunction provides a basic framework, initializing variables and objects (among them the SFDataBase object that provides a communications channel with the supporting relational database), and implementing a number of methods that can be overridden by the subclasses.
2. | Makeup of function's parameters and return value |
Every function that takes one or more parameters or returns a value must implement initFor: method where it creates a NSMutableArray of SFParameterData objects (one for each input parameter that the function takes) and instantiate a SFVariable object that will carry the return data back to the caller. The following is a sample code taken from the SystemStateFunction (the system function that returns an integer number of system state as recorded in one of the STEPforward database tables):
- initFor: (id) sender
{
SFParameterData *parameter;
if (instanceInitialized)
return (instance);
[super initFor:sender];
returnVariable = [[SFVariable alloc] initName:@"" type:DATAFIELD];
[returnVariable setDataType:INTEGER];
/*
** Create NSArray for parameter data and fill it in
*/
parameterList = [[NSMutableArray alloc] initWithCapacity:0];
parameter = [[SFParameterData alloc]
initWithName:@"Date"
dataType:DATE
displayedDataType:DATE
ioFlag:INPUT
runTime:YES
isSwitchDisplayed:YES];
[parameterList addObjectWithoutRetaining:parameter];
parameter = [[SFParameterData alloc]
initWithName:@"Time"
dataType:TIME
displayedDataType:TIME
ioFlag:INPUT
runTime:YES
isSwitchDisplayed:YES];
[parameterList addObjectWithoutRetaining:parameter];
/*
** Initialize rest of variables
*/
instanceInitialized = YES;
return self;
}
Every object of parameterList NSMutableArray carries information about one parameter taken by the function. The following is a brief description of the SFParameterData class initialization method that takes this information:
- initWithName: (NSString *) name
dataType: (int) dataType
displayedDataType: (int) displayedDataType
ioFlag: (int) ioFlag
runTime: (BOOL) runTime
isSwitchDisplayed: (BOOL) isSwitchDisplayed
name | name that appears in the parameter slot of the graphic when the function is brought into the Workspace. | |
dataType | type of data carried by the parameter (see DefinedConstants.h header file for the complete list of all available data types). | |
displayedDataType | type of data used in formatting the legend of the function (usually the same as dataType). | |
ioFlag | flag to tell the system whether or not the function can return data through the parameter slot, in addition to taking an input value from it (see DefinedConstants.h header file for the list of valid I/O flags). | |
runTime | determines how the parameter is presented in the function's graphic. If the run time flag is set to YES, the parameter slot in the function's graphic is set to receive the name of the variable that carries data. If the flag is set to NO, the parameter slot is set to be an editable field in which the actual data is typed. This flag only determines the initial state of the parameter slot; it can be changed by pressing the run-time switch (see isSwitchDisplayed flag in the next entry). | |
isSwitchDisplayed | controls the use of the run-time switch. If this flag is set to NO, the switch is not displayed and the parameter slot is set to the rigidly fixed state, determined by the runTime flag. |
SFParameterData class implements one more method to set array of NSString objects:
- (void) setStringTable: (NSArray *) stringArray
stringArray | NSArray object that can be used to carry items for pop-up list to be displayed in the parameter slot. In this case, when the parameter is set to an actual data contents (i.e. runTime flag is NO), it will display a pop-up list of fixed values instead of an editable text field. Use of this method is optional. |
If the function does not take any parameters, it does not have to instantiate the parameterList.
To pass the results of its work to the caller, the function can instantiate returnVariable object (of SFVariable class) that must be set to the appropriate data type (see DefinedConstants.h header file for the complete list of all available data types). Functions that do not return values, or return them through parameters, do not need to instantiate the returnVariable object.
3. | Methods implemented by functions |
In addition to initFor: method described in the previous section, subclasses of SFSystemFunction must implement the following methods:
- (NSString *) functionName
This method returns the NSString that is shown in the list of available functions in the Palette window, when the user brings in the new function into the Workspace. This is the name by which the function is recognized in the flowchart where it is located.
- (NSString *) descriptionForFunction: (NSString *) name
This methods formats and returns the NSString that is shown in Description column of the function list displayed by the Palette. name is the name of the function as set by the functionName method and may be ignored.
- (int) returnType
Returns the data type of returnVariable object as set by the initFor: method; needs to be implemented only if the function returns a value.
- execute: (NSArray *) parmList
This is the central method of every function. It is in this method that the function does its work (calculation, formatting of text, retrievals from and updates to the database tables etc.) parmList is a NSArray of SFVariable objects that contain the parameter data upon which the function operates (functions that do not use parameters receive nil as parmList).
In order to extract data from its parameter list, the function can use the following instance methods implemented by the SFVariable class:
- (NSString *) asciiValue
Returns textual representation of data carried by the SFVariable. Values of LOGICAL type are returned as @"YES" or @"NO" NSStrings. Values of IMAGE type are not returned; instead the method returns @"<Image>" NSString.
- (void *) value
Returns the pointer to the memory buffer where the actual data is stored. To be extracted properly, the pointer should be cast according to the type of data carried by the Variable.
- setValue: (void *) data
Sets SFVariable's contents to data which must be a pointer to a program variable that carries data to be copied into the SFVariable object.
- setDataType: (int) type
Sets the data type of the SFVariable. Valid data types are listed in DefinedConstants.h header file.
- (int) dataType
Returns the type of data carried by the SFVariable. It is the same data type that is set by setDataType: method; can be used in casting the pointer returned by value method.
Every implementation of execute: method must begin with sending this message to its superclass:
[super execute:nil];
Functions returning a value must return their returnVariable object in the return statement as follows:
return (returnVariable);
When execute: method is called the first time, its [super execute:nil] call obtains an initialized instance of the SFDataBase object from STEPforward in the database instance variable. This object can be used to retrieve, update, insert and delete rows in the database tables. The SFDataBase object cannot be instantiated by the function; rather, it is created by the server and vended out (by copy or by proxy) to the client part of STEPforward. There is exactly one instance of the SFDataBase object in every running copy of STEPforward. The user should never release the database instance.
Documentation on SFDataBase class is available in the DataBase.rtf file.
4. | Singly instantiable functions |
Sometimes it is desirable to create a single instance of the function, so that all graphics showing the same function in the given Procedure, Report or Process actually use the same object. In such cases the user should implement the following methods and static variables:
static id instance = nil; /* Id of the instance of the Class (one only) */
static BOOL instanceInitialized = NO; /* Flag to track initialization of the instance */
+ alloc
{
if (instance == nil)
instance = NSAllocateObject([self class],0,NULL);
return (instance);
}
- initFor: (id) sender
{
if (instanceInitialized)
return (instance);
[super initFor:sender];
.
.
.
instanceInitialized = YES;
return self;
}
- free
{
instance = nil;
instanceInitialized = NO;
return [super free];
}
See examples of functions for more information.
5. | Building the function bundle |
In order to be dynamically loaded into STEPforward at the run time, functions must be compiled and packaged into bundles. The bundles are stored in the designated folder in the StepForward.app bundle (Resources) and are picked up from there by NSBundle mechanism when STEPforward is launched.
The Linker Flags option of the Project Builder's Inspector should be set to -undefined suppress to avoid linker errors.
6. | Troubleshooting |
Once loaded into STEPforward, the function becomes part and parcel of the executable code. Hence, fatal run-time errors caused by the execution of the function will result in bringing down all of STEPforward.
These errors may be especially difficult to track if the function is used in a Report or Process that runs silently in the background. In this case, the background job will just quit without much of diagnostics available. One way to handle such problems is to run a suspect job in the foreground, watching for the fatal error messages in the Console window. The user can also run STEPforward through gdb, putting breaks in the source file of the function and stepping through its execution.
Dynamic loading of bundles in OpenStep has some restrictions when it comes to loading objects that may contain references to other objects -- Objective-C classes or conventional functions. The external objects that are not defined by the source code of the user-compiled function must be present in the body of the recipient application (i.e. STEPforward) so that the loader can find referenced symbols and resolve the references from the bundle dynamically.
In reality this means that if some function is not present in STEPforward's body because STEPforward never made use of it, and this function is referenced in the newly compiled bundle, STEPforward will not be able to load the bundle -- it will be aborted by the loader and the whole application will crash.
When such a situation occurs, the user may have to avoid using the offending function, provide a substitute or contact us; in critically important cases we may be able to force-link the missing function into the main body of STEPforward in order to resolve the impasse.