Creating an InterSystems Callout Library
An InterSystems Callout library is a shared library that contains your custom Callout functions and the enabling code that allows InterSystems IRIS to use them. This chapter describes how to create a Callout library and access it at runtime.
The following topics are discussed:
-
Introduction to Callout Libraries — describes how a Callout library is created and accessed.
-
ZFEntry Linkage Options — provides a detailed description of the linkage options that determine how function arguments are passed.
-
Compatible Languages and Compilers — describes how you may be able to create Callout libraries in languages other then C.
-
Callout Library Runup and Rundown Functions — describes two optional functions that can be set to run automatically when a Callout library is loaded or unloaded.
-
Troubleshooting and Error Processing — lists some coding practices that should be avoided, and describes special functions for handling UNIX® signal processing errors.
In this book, the term shared library refers to a dynamically linked file (a DLL file on Windows or an SO file on UNIX® and related operating systems). A Callout library is a shared library that includes hooks to the $ZF Callout Interface, allowing it to be loaded and accessed at runtime by various $ZF functions.
Introduction to Callout Libraries
There are several different ways to access a Callout library from ObjectScript code, but the general principal is to specify the library name, the function name, and any required arguments (see “Invoking Callout Library Functions”). For example, the following code invokes a simple Callout library function:
The following ObjectScript code is executed at the Terminal. It loads a Callout library named simplecallout.dll and invokes a library function named AddInt, which adds two integer arguments and returns the sum.
USER> set sum = $ZF(-3,"simplecallout.dll","AddInt",2,2)
USER> write "The sum is ",sum,!
The sum is 4
This example uses $ZF(-3), which is the simplest way to invoke a single Callout library function. See “Invoking Callout Library Functions” for other options.
The simplecallout.dll Callout library is not much more complex than the code that calls it. It contains three elements required by all Callout libraries:
-
Standard code provided when you include the iris-cdzf.h Callout header file.
-
One or more functions with correctly specified parameters.
-
Macro code for a ZFEntry table, which generates the mechanism that InterSystems IRIS will use to locate your Callout functions when the library is loaded (see “Creating a ZFEntry Table” for details).
Here is the code that was compiled to produce the simplecallout.dll Callout library:
#define ZF_DLL /* Required for all Callout code. */
#include <iris-cdzf.h> /* Required for all Callout code. */
int AddTwoIntegers(int a, int b, int *outsum) {
*outsum = a+b; /* set value to be returned by the $ZF function call */
return IRIS_SUCCESS; /* set the exit status code */
}
ZFBEGIN
ZFENTRY("AddInt","iiP",AddTwoIntegers)
ZFEND
-
The first two lines must define ZF_DLL and include the iris-cdzf.h file. These two lines are always required.
-
The AddTwoIntegers() function is defined next. It has the following features:
-
Two input parameters, integers a and b, and one output parameter, integer pointer *outsum.
-
A statement assigning a value to output parameter *outsum. This will be the value returned by the call to $ZF(-3).
-
The return statement does not return the function output value. Instead, it specifies the exit status code that will be received by InterSystems IRIS if the $ZF call is successful. If the function fails, InterSystems IRIS will receive an exit status code generated by the system.
-
-
The last three lines are macro calls that generate the ZFEntry table used by InterSystems IRIS to locate your Callout library functions. This example has only a single entry, where:
-
"AddInt" is the string used to identify the function in a $ZF call.
-
"iiP" is a string that specifies datatypes for the two input values and the output value.
-
AddTwoIntegers is the entry point name of the C function.
-
The ZFEntry table is the mechanism that allows a shared library to be loaded and accessed by the $ZF Callout Interface (see “Creating a ZFEntry Table”). A ZFENTRY declaration specifies the interface between the C function and the ObjectScript $ZF call. Here is how the interface works in this example:
-
The C function declaration specifies three parameters:
int AddTwoIntegers(int a, int b, int *outsum)
Parameters a and b are the inputs, and outsum will receive the output value. The return value of AddTwoIntegers is an exit status code, not the output value.
-
The ZFENTRY macro defines how the function will be identified in InterSystems IRIS, and how the parameters will be passed:
ZFENTRY("AddInt","iiP",AddTwoIntegers)
"AddInt" is the library function identifier used to specify C function AddTwoIntegers in a $ZF call. The linkage declaration ("iiP") declares parameters a and b as linkage type i (input-only integers), and outsum as linkage type P (an integer pointer that can be used for both input and output).
-
The $ZF(-3) function call specifies the library name, the library function identifier, and the input parameters, and returns the value of the output parameter:
set sum = $ZF(-3,"simplecallout.dll","AddInt",2,2)
Parameters a and b are specified by the last two arguments. No argument is required for the third parameter, outsum, because it is used only for output. The value of outsum is assigned to sum when the $ZF(-3) call returns .
Creating a ZFEntry Table
Every Callout library must define a ZFEntry table, which allows InterSystems IRIS to load and access your Callout functions (see the “Introduction to Callout Libraries” for a simple example). The ZFEntry table is generated by a block of macro code beginning with ZFBEGIN and ending with ZFEND. Between these two macros, a ZFENTRY macro must be called once for each function to be exposed.
Each ZFENTRY call takes three arguments:
ZFENTRY(zfname,linkage,entrypoint)
where zfname is the string used to specify the function in a $ZF call, linkage is a string that specifies how the arguments are to be passed, and entrypoint is the entry point name of the C function.
To create a Callout library, your code must contain a #define ZF_DLL directive, which is a switch that generates an internal GetZFTable function for locating library functions. When a Callout library is loaded, InterSystems IRIS calls this function to initialize the library for subsequent lookups of library function names.
The position of an entry in the ZFEntry table can be significant. The $ZF(-5) and $ZF(-6) interfaces (described in “Invoking Callout Library Functions”) both invoke a library function by specifying its sequence number (starting with 1) in the table. For example, $ZF(-6) would invoke the third function in a ZFEntry table with the following call:
x = $ZF(-6,libID,3)
where libID is the library identifier and 3 is the sequence number of the third entry in the table.
Some compilers (such as Microsoft Visual Studio) support precompiled headers. If you use precompiled headers, the #define ZF_DLL statement must be in effect for the precompilation. If it is not, the resulting dll will cause a <DYNAMIC LIBRARY LOAD> error when it is used. It is strongly recommended that precompiled headers not be used for a Callout library.
ZFEntry Linkage Options
Each ZFENTRY statement (see “Creating a ZFEntry Table”) requires a string that determines how function arguments are passed. This section provides a detailed description of available linkage options.
-
Introduction to Linkages — provides an overview of the various linkage types and lists all of the linkage options discussed in this chapter.
-
Using Numeric Linkages — describes linkage options for numeric parameters.
-
Passing Null Terminated Strings with C Linkage Types — describes linkage options for null terminated strings.
-
Passing Short Counted Strings with B Linkage Types — describes linkages that use the ZARRAY structure for counted character arrays.
-
Passing Standard Counted Strings with J Linkage Types — describes linkages that use the InterSystems IRIS IRIS_EXSTR structure for counted character arrays.
-
Configuring the $ZF Heap for Legacy Short Strings — describes InterSystems IRIS system settings that control memory allocation for legacy short string parameter passing.
Introduction to Linkages
Each ZFENTRY statement (see “Creating a ZFEntry Table”) requires a string that describes how the arguments are passed. For example, "iP" specifies two parameters: an integer, and a pointer to an integer. The second letter is capitalized to specify that the second argument may used for both input and output. Your code can have up to 32 actual and formal parameters.
If you specify an uppercase linkage type (permitted for all linkage types except i), the argument can be used for both input and output. If only one output argument is specified, its final value will be used as the return value of the function. If more than one output argument is specified, all output arguments will be returned as a comma-delimited string.
Output arguments do not have to be used as input arguments. If you specify output-only arguments after all input arguments, the function can be called without specifying any of the output arguments (see “Introduction to Callout Libraries” for an example).
From the perspective of the ObjectScript programmer, parameters are input only. The values of the actual parameters are evaluated by the $ZF call and linked to the formal parameters in the C routine declaration. Any changes to the C formal parameters are either lost or are available to be copied to the $ZF return value.
If the ZFENTRY macro does not specify a formal parameter to be used as the return value, the $ZF call will return an empty string (""). The linkage declaration can contain more than one output parameter. In this case, all the return values will be converted to a single comma-delimited string. There is no way to distinguish between the comma inserted between multiple return parameters, and a comma present in any one return value, so only the final return value should contain commas.
The following table describes the available options:
C Datatype | Input | In/Out | Notes |
---|---|---|---|
int | i or 4i | none (use P) | Specifies a 32-bit integer. The i linkage type is input only. To return an integer type, use P or 4P (int *). Input argument may be a numeric string (see note 1). |
int * | p or 4p | P or 4P | Pointer to a 32-bit integer. Input argument may be a numeric string (see note 1). |
_int64 | 8i | none (use 8P) | Specifies a 64-bit integer. To return a 64-bit integer type, use 8P. Input argument may be a numeric string (see note 1). |
_int64 * | 8p | 8P | Pointer to a 64-bit integer. Input argument may be a numeric string (see note 1). |
double * | d | D | Input argument may be a numeric string (see note 1). Use #D to preserve a double * in radix 2 format (see note 2). |
float * | f | F | Input argument may be a numeric string (see note 1). Use #F to preserve a float * in radix 2 format (see note 2). |
char * | 1c or c | 1C or C | This is the common C NULL-terminated string (see note 3). |
unsigned short * | 2c or w | 2C or W | This is a C style NULL-terminated UTF-16 string (see note 3). |
wchar t * | 4c | 4C | This is a C style NULL-terminated string stored as a vector of wchar_t elements (see notes 3 and 4). |
ZARRAYP | 1b or b | 1B or B | short 8-bit national strings (up to 32,767 characters). |
ZWARRAYP | 2b or s | 2B or S | short 16-bit Unicode strings (up to 32,767 characters). |
ZHARRAYP | 4b | 4B | short Unicode strings (up to 32,767 characters) stored in elements implemented by wchar_t (see note 4) |
IRIS_EXSTR | 1j or j | 1J or J | Standard string (up to the string length limit) of 8-bit national characters |
IRIS_EXSTR | 2j or n | 2J or N | Standard string (up to the string length limit) of 16-bit Unicode characters |
IRIS_EXSTR | 4j | 4J | Standard string (up to the string length limit) of wchar_t characters (see note 4) |
-
i, p, d, f — When numeric arguments are specified, InterSystems IRIS allows the input argument to be a string. See “Using Numeric Linkages” for details.
-
#F, #D— To preserve a number in radix 2 floating point format, use #F for float * or #D for double *. See “Using Numeric Linkages” for details.
-
1C, 2C, 4C — All strings passed with this linkage will be truncated at the first null character. See “Passing Null Terminated Strings with C Linkage Types” for details.
-
4B, 4C, 4J— Although wchar_t is typically 32 bits, InterSystems IRIS uses only 16 bits to store each Unicode character. An output argument containing large wchar_t values will be converted to UTF-16 for assignment to the $ZF return value. A string containing UTF-16 (surrogate pairs) will be expanded to wchar_t for $ZF input arguments. The true wchar_t values can be accessed using ObjectScript functions $WASCII() and $WCHAR().
Structure and argument prototype definitions (including InterSystems internal definitions) can be seen in the include file iris-cdzf.h.
Using Numeric Linkages
Numeric linkage types are provided for the following datatypes:
C Datatype | Input | In/Out | Notes |
---|---|---|---|
int | i | none (use P) | Specifies a 32-bit integer. The i linkage type is input only. To return an integer type, use P or 4P (int *). |
int * | p | P | Pointer to a 32-bit integer. |
_int64 | 8i | none (use 8P) | Specifies a 64-bit integer. To return a 64-bit integer type, use 8P. |
_int64 * | 8p | 8P | Pointer to a 64-bit integer. |
double * | d | D | Use #D (output only) to return a double * in radix 2 format. |
float * | f | F | Use #F (output only) to return a float * in radix 2 format. |
When numeric arguments are specified, InterSystems IRIS allows the input argument to be a string. When a string is passed, a leading number will be parsed from the string to derive a numeric value. If there is no leading number, the value 0 will be received. Thus "2DOGS" is received as 2.0, while "DOG" is received as 0.0. Integer arguments are truncated. For example, "2.1DOGS" is received as 2. For a detailed discussion of this subject, see “String-to-Number Conversion” in Using ObjectScript.
When the output linkage is specified by F (float *) or D (double *), the number you return will be converted to an internal radix 10 number format. To preserve the number in radix 2 format, use #F for float * or #D for double *.
The # prefix is not permitted for input arguments. In order to avoid conversion (which may cause a slight loss in precision), input values must be created with $DOUBLE in the ObjectScript code that calls the function, and the corresponding input linkages must be specified as lower case f or d.
InterSystems IRIS supports the $DOUBLE function for creating a standard IEEE format 64-bit floating point number. These numbers can be passed between external functions and InterSystems IRIS without any loss of precision (unless the external function uses the 32-bit float instead of the 64-bit double). For output, the use of the IEEE format is specified by adding the prefix character # to the F or D argument type. For example, "i#D" specifies an argument list with one integer input argument and one 64-bit floating point output argument.
Passing Null Terminated Strings with C Linkage Types
This linkage type should be used only when you know InterSystems IRIS will not send strings containing null ($CHAR(0)) characters. When using this datatype, your C function will truncate a string passed by InterSystems IRIS at the first null character, even if the string is actually longer. For example, the string "ABC"_$CHAR(0)_"DEF" would be truncated to "ABC".
C Datatype | Input | In/Out | Notes |
---|---|---|---|
char * | 1c or c | 1C or C | This is the common C NULL-terminated string. |
unsigned short * | 2c or w | 2C or W | This is a C style NULL-terminated UTF-16 string. |
wchar t * | 4c | 4C | This is a C style NULL-terminated string stored as a vector of wchar_t elements. |
Here is a short Callout library that uses all three linkage types to return a numeric string:
Each of the following three functions generates a random integer, transforms it into a numeric string containing up to 6 digits, and uses a C linkage to return the string .
#define ZF_DLL // Required when creating a Callout library.
#include <iris-cdzf.h>
#include <stdio.h>
#include <wchar.h> // Required for 16-bit and 32-bit strings
int get_sample(char* retval) { // 8-bit, null-terminated
sprintf(retval,"%d",(rand()%1000000));
return ZF_SUCCESS;
}
int get_sample_W(unsigned short* retval) { // 16-bit, null-terminated
swprintf(retval,6,L"%d",(rand()%1000000));
return ZF_SUCCESS;
}
int get_sample_H(wchar_t* retval) { // 32-bit, null-terminated
swprintf(retval,6,L"%d",(rand()%1000000));
return ZF_SUCCESS;
}
ZFBEGIN
ZFENTRY("GetSample","1C",get_sample)
ZFENTRY("GetSampleW","2C",get_sample_W)
ZFENTRY("GetSampleH","4C",get_sample_H)
ZFEND
Passing Short Counted Strings with B Linkage Types
The iris-cdzf.h Callout header file defines counted string structures ZARRAY, ZWARRAY, and ZHARRAY, representing a short string (an InterSystems legacy string type). These structures contain an array of character elements (8-bit, 16-bit Unicode, or 32-bit wchar t, respectively) and a short integer (maximum value 32,768) specifying the number of elements in the array. For example:
typedef struct zarray {
unsigned short len;
unsigned char data[1]; /* 1 is a dummy value */
} *ZARRAYP;
where
-
len — contains the length of the array
-
data — is an array that contains the character data. Element types are unsigned char for ZARRAY, unsigned short for ZWARRAY, and wchar_t for ZHARRAY.
The B linkages specify pointer types ZARRAYP, ZWARRAYP, and ZHARRAYP, corresponding to the three array structures. The maximum size of the array returned is 32,767 characters.
C Datatype | Input | In/Out | Notes |
---|---|---|---|
ZARRAYP | 1b or b | 1B or B | short national string containing up to 32,767 8-bit characters. |
ZWARRAYP | 2b or s | 2B or S | short Unicode string containing up to 32,767 16-bit characters. |
ZHARRAYP | 4b | 4B | short Unicode string containing up to 32,767 wchar_t characters. |
The maximum total length of the arguments depends on the number of bytes per character (see “Configuring the $ZF Heap”).
Here is a Callout library that uses all three linkage types to return a numeric string:
Each of the following three functions generates a random integer, transforms it into a numeric string containing up to 6 digits, and uses a B linkage to return the string .
#define ZF_DLL // Required when creating a Callout library.
#include <iris-cdzf.h>
#include <stdio.h>
#include <wchar.h> // Required for 16-bit and 16-bit characters
int get_sample_Z(ZARRAYP retval) { // 8-bit, counted
unsigned char numstr[6];
sprintf(numstr,"%d",(rand()%1000000));
retval->len = strlen(numstr);
memcpy(retval->data,numstr,retval->len);
return ZF_SUCCESS;
}
int get_sample_ZW(ZWARRAYP retval) { // 16-bit, counted
unsigned short numstr[6];
swprintf(numstr,6,L"%d",(rand()%1000000));
retval->len = wcslen(numstr);
memcpy(retval->data,numstr,(retval->len*sizeof(unsigned short)));
return ZF_SUCCESS;
}
int get_sample_ZH(ZHARRAYP retval) { // 32-bit, counted
wchar_t numstr[6];
swprintf(numstr,6,L"%d",(rand()%1000000));
retval->len = wcslen(numstr);
memcpy(retval->data,numstr,(retval->len*sizeof(wchar_t)));
return ZF_SUCCESS;
}
ZFBEGIN
ZFENTRY("GetSampleZ","1B",get_sample_Z)
ZFENTRY("GetSampleZW","2B",get_sample_ZW)
ZFENTRY("GetSampleZH","4B",get_sample_ZH)
ZFEND
Commas are used as separators in an output argument string that contains multiple values. Because commas can also be a part of counted string arrays, declare these arrays at the end of the argument list and use one array per call.
Passing Standard Counted Strings with J Linkage Types
The iris-callin.h header file defines counted string structure IRIS_EXSTR, representing a standard InterSystems IRIS string. This structure contains an array of character elements (8-bit, 16-bit Unicode, or 32–bit wchar t) and an int value (up to the string length limit) specifying the number of elements in the array:
typedef struct {
unsigned int len; /* length of string */
union {
Callin_char_t *ch; /* text of the 8-bit string */
unsigned short *wch; /* text of the 16-bit string */
wchar_t *lch; /* text of the 32-bit string */
/* OR unsigned short *lch if 32-bit characters are not enabled */
} str;
} IRIS_EXSTR, *IRIS_EXSTRP;
C Datatype | Input | In/Out | Notes |
---|---|---|---|
IRIS_EXSTR | 1j or j | 1J or J | Standard string of 8-bit national characters |
IRIS_EXSTR | 2j or n | 2J or N | Standard string of 16-bit Unicode characters |
IRIS_EXSTR | 4j | 4J | Standard string of 32-bit characters wchar_t characters |
The IRIS_EXSTR data structure is manipulated by functions from the Callin API (a library of low-level InterSystems function calls. See the “Callin Function Reference” in Using the Callin API for details. Despite the similar names, the Callin API and the $ZF Callout Interface are completely separate products).
The following functions are used to create and destroy instances of IRIS_EXSTR:
-
IrisExStrNew[W][H] — Allocates the requested amount of storage for a string, and fills in the IRIS_EXSTR structure with the length and a pointer to the value field of the structure.
-
IrisExStrKill — Releases the storage associated with a IRIS_EXSTR string.
Here is a Callout library that uses all three linkage types to return a numeric string:
Each of the following three functions generates a random integer, transforms it into a numeric string containing up to 6 digits, and uses a J linkage to return the string .
#define ZF_DLL // Required when creating a Callout library.
#include <iris-cdzf.h>
#include <stdio.h>
#include <wchar.h>
#include <iris-callin.h>
int get_sample_L(IRIS_EXSTRP retval) { // 8-bit characters
Callin_char_t numstr[6];
size_t len = 0;
sprintf(numstr,"%d",(rand()%1000000));
len = strlen(numstr);
IRISEXSTRKILL(retval);
if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;}
memcpy(retval->str.ch,numstr,len); // copy to retval->str.ch
return ZF_SUCCESS;
}
int get_sample_LW(IRIS_EXSTRP retval) { // 16-bit characters
unsigned short numstr[6];
size_t len = 0;
swprintf(numstr,6,L"%d",(rand()%1000000));
len = wcslen(numstr);
IRISEXSTRKILL(retval);
if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;}
memcpy(retval->str.wch,numstr,(len*sizeof(unsigned short))); // copy to retval->str.wch
return ZF_SUCCESS;
}
int get_sample_LH(IRIS_EXSTRP retval) { // 32-bit characters
wchar_t numstr[6];
size_t len = 0;
swprintf(numstr,6,L"%d",(rand()%1000000));
len = wcslen(numstr);
IRISEXSTRKILL(retval);
if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;}
memcpy(retval->str.lch,numstr,(len*sizeof(wchar_t))); // copy to retval->str.lch
return ZF_SUCCESS;
}
ZFBEGIN
ZFENTRY("GetSampleL","1J",get_sample_L)
ZFENTRY("GetSampleLW","2J",get_sample_LW)
ZFENTRY("GetSampleLH","4J",get_sample_LH)
ZFEND
In the previous example, IRISEXSTRKILL(retval) is always called to remove the input argument from memory. This should always be done, even if the argument is not used for output. Failure to do so may result in memory leaks.
Configuring the $ZF Heap for Legacy Short Strings
This section applies only to legacy short strings (see “Passing Short Counted Strings with B Linkage Types”). Standard InterSystems IRIS strings (see “Passing Standard Counted Strings with J Linkage Types”) use their own stack.
The $ZF heap is the virtual memory space allocated for all $ZF short string input and output parameters. It is controlled by the following InterSystems IRIS system settings:
-
ZFString is the number of characters permitted for a single string parameter. The number of bytes this actually requires will vary depending on whether you are using 8-bit characters, 16-bit Unicode characters, or 32-bit characters on UNIX®. The permitted range for this setting is 0 to 32767 characters. The default is 0, indicating that the maximum value should be used.
-
ZFSize is the total number of bytes InterSystems IRIS allocates for all $ZF input and output parameters. The permitted range for this setting is 0 to 270336 bytes, where 0 (the default setting) indicates that InterSystems IRIS should calculate an appropriate value based on the value of ZFString.
Calculate ZFSize (total number of bytes) based on ZFString (maximum number of characters per string) as follows:
ZFSize = (<bytes per character> * ZFString) + 2050
For example, suppose ZFString has the default value of 32767 characters:
-
Using Unicode 16-bit characters, an appropriate value for ZFSize is (2 * 32767 + 2050) = 67584 bytes.
-
Using UNIX® 32-bit characters, an appropriate value for ZFSize is (4 * 32767 + 2050) = 133118 bytes.
These settings can be changed in either of the following places:
-
The configuration parameter file (see “zfheap” in the “[config]” section of the Configuration Parameter File Reference)
-
The Management Portal (see the ZFSize and ZFString entries under “Advanced Memory Settings” in the Additional Configuration Settings Reference).
Compatible Languages and Compilers
Using the $ZF Callout Interface you can write functions in an external language and call them from ObjectScript. Callout libraries are usually written in C, but could potentially be written in any other compiled language that uses a calling convention understood by your C compiler. Two compatibility issues arise. First, the compiler must use an Application Binary Interface (ABI) that is compatible with C. Second, the compiler must generate code that does not rely on any runtime library features that are not compatible with InterSystems IRIS.
InterSystems supports using the same C compiler that we use to generate InterSystems IRIS on all platforms:
Platform | Compiler |
---|---|
IBM AIX | IBM XL C for AIX |
Mac OS X (Darwin) | Xcode |
Microsoft Windows | Microsoft Visual Studio |
Linux (all variants) | GNU Project GCC C |
Most platforms have a standardized Application Binary Interface (ABI), making most compilers compatible. The Intel x86-32 and x86-64 platforms are major exceptions, Multiple calling conventions exist for these platforms. See (https://en.wikipedia.org/wiki/X86_calling_conventionsOpens in a new tab) for a discussion of calling conventions on these platforms.
Many C compilers allow a different calling convention to be declared for an external routine. It may be possible to call a routine written in another language by writing a C wrapper routine that declares the appropriate calling convention.
Callout Library Runup and Rundown Functions
An InterSystems Callout library can include custom internal functions that will be called when the shared object is loaded (runup) or unloaded (rundown). No arguments are passed in either case. The functions are used as follows:
-
ZFInit — is invoked when a Callout library is first loaded by $ZF(-3), $ZF(-4,1), or $ZF(-6). The return code from this function should be zero to indicate absence of error, or non-zero to indicate some problem. If the call was successful, the address of your ZFUnload rundown function is saved.
-
ZFUnload — is invoked when a Callout library is unloaded or replaced by a call to $ZF(-3), or is unloaded by $ZF(-4,2) or $ZF(-4,4). It is not invoked at process halt. If some error occurs during the rundown function, further calls to it will be disabled to allow unloading of the Callout library. The return value from ZFUnload is currently ignored.
When building the Callout library, you may need to explicitly export the symbols ZFInit and ZFUnload during the link procedure.
Troubleshooting and Error Processing
This section discusses the following topics:
-
Worst Practices — lists some practices that could cause serious problems.
-
Handling UNIX® Signal Processing Errors — describes some functions to help recover from failed system calls that may happen when the process receives a signal.
Worst Practices
Although you can call almost any routine with the $ZF Callout Interface, it is best used for math functions. It can also be used effectively as an interface to external devices not well handled with InterSystems IRIS I/O, or for some system services where an InterSystems IRIS interface does not otherwise exist.
The following actions can cause serious problems:
-
Accessing any memory that doesn’t belong to you
Memory access violations will be handled by InterSystems IRIS, and will be treated as bugs in InterSystems IRIS.
-
Encountering any other errors handled by traps
Errors handled by traps (such as divide by zero errors on most platforms) will also be treated as bugs in InterSystems IRIS.
-
Changing your processes priority
InterSystems IRIS needs to interact with other processes running InterSystems IRIS. Lowering your priority can be just as bad as raising it. For example, imagine that your process acquires a spin-lock protected resource just before relinquishing the CPU. If your priority is too low, other processes with higher priority can fight for the resource, effectively preventing your process running so it can release the spin-lock.
-
Masking interrupts
You might mask interrupts very briefly to implement your own interlock, but you should be very careful to not leave interrupts masked for any period of time.
-
Creating or opening any resource that you can’t clean-up
It is fine to open files, and allocate memory with malloc, because those resources will be closed or freed upon termination of your process. If you create a second thread, you can’t guarantee the second thread will exit gracefully before the InterSystems IRIS process exits, so don’t create a second thread.
-
Returning non-opaque objects from your non-ObjectScript code
Don’t malloc a block of memory in your code and expect to be able to use $VIEW(address,−3,size) to read it. Also, you should not pass a malloc block back to your non-ObjectScript code. Your code should return an opaque handle, and later when it receives an opaque handle it should verify that it is valid before using it.
-
Exiting your process
You should never just call exit. Always return with either ZF_SUCCESS or ZF_FAILURE (and remember that the implementation of these values differs among InterSystems IRIS platforms).
-
Exiting by calling any variant of exec
You can fork and then call exec in the child process, but be very sure that the parent will always return to InterSystems IRIS and the child process will never return to InterSystems IRIS.
-
Changing the error handling behavior for the process
Unlike Windows, UNIX® systems only allow local error handling to be established for the current and inner frames.
Handling UNIX® Signal Processing Errors
When running under UNIX® and related operating systems, some system calls may fail if the process receives a signal, the most common being open, read, write, close, ioctl, and pause. If the function uses any of these system calls, your code must be able to distinguish among real errors, a Ctrl-C, and a call that should be restarted.
The following functions allow you to check for asynchronous events and to set a new alarm handler in $ZF. The function declarations are included in iris-cdzf.h:
int sigrtclr(); — Clears retry flag. Should be called once before using sigrtchk().
int dzfalarm(); — Establishes new SIGALRM handler.
On entry to $ZF, the previous handler is automatically saved. On exit, it is restored automatically. A user program should not alter the handling of any other signal.
int sigrtchk(); — Checks for asynchronous events. Should be called whenever one of the following system calls fails: open, close, read, write, ioctl, pause, or any call that fails when the process receives a signal. It returns a code indicating the action that the user should take:
-
-1 — Not a signal. Check for I/O error. See contents of errno variable.
-
0 — Other signal. Restart operation from point at which it was interrupted.
-
1 — SIGINT/SIGTERM. Exit from $ZF with a SIGTERM "return 0". These signals are trapped appropriately.
A typical $ZF function used to control some device would use logic similar to the following pseudo-code:
if ((fd = open(DEV_NAME, DEV_MODE)) < 0) {
Set some flags
Call zferror
return 0;
}
The open system call may fail if the process receives a signal. Usually this situation is not an error and the operation should be restarted. Depending on the signal, however, you might take other actions. So, in order to take account of all the possibilities, consider using the following code:
sigrtclr();
while (TRUE) {
if (sigrtchk() == 1) return 1 or 0;
if ((fd = open(DEV_NAME, DEV_MODE)) < 0) {
switch (sigrtchk()) {
case -1:
/* This is probably a real device error */
Set some flags
Call zferror
return 0;
case 0:
/* A innocuous signal was received. Restart. */
continue;
case 1:
/* Someone is trying to terminate the job. */
Do cleanup work
return 1 or 0;
}
}
else break;
}
/*
Code to handle the normal situation:
open() system call succeeded
*/
Remember: your error processing code must never alter the handling of a signal except by calling dzfalarm() to establish a new SIGALRM handler.