Using the Caché Callout Gateway
Creating a Caché Callout Library
[Back] [Next]
   
Server:docs2
Instance:LATEST
User:UnknownUser
 
-
Go to:
Search:    

A Caché Callout library is a shared library that contains your custom Callout functions and the enabling code that allows Caché to use them. This chapter describes how to create a Callout library and access it at runtime.

The following topics are discussed:
Note:
Shared Libraries and Callout Libraries
In this book, the term shared library refers to a dynamically linked file (a DLL file on Windows, an SO file on UNIX® and related operating systems, or a shareable image file on OpenVMS). A Callout library is a shared library that includes hooks to the Callout Gateway, 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 Caché 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:
Invoking function AddInt from Callout library simplecallout.dll
The following ObjectScript code is executed at the Caché 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:
  1. Standard code provided when you include the cdzf.h Callout header file.
  2. One or more functions with correctly specified parameters.
  3. Macro code for a ZFEntry table, which generates the mechanism that Caché 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:
Callout code for simplecallout.dll
#define ZF_DLL  /* Required only for dynamically linked libraries. */
#include <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 0;   /* set the exit status code */
}
ZFBEGIN
   ZFENTRY("AddInt","iiP",AddTwoIntegers)
ZFEND
The ZFEntry table is the mechanism that allows a shared library to be loaded and accessed by the Callout Gateway (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:
Creating a ZFEntry Table
Every Callout library must define a ZFEntry table, which allows Caché 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.
By default, the ZFEntry table will generate code that can only be used for statically linked functions (see Statically Linked Callout Functions for details). To create a Callout library, your code must contain a #define ZF_DLL directive, which is a switch that causes the macro code to generate a different mechanism for locating library functions. Instead of a static pointer table, it generates an internal GetZFTable function. When a Callout library is loaded, Caché calls this function to initialize the library for subsequent lookups of library function names.
Note:
ZFEntry Sequence Numbers
The position of an entry in the ZFEntry table can be significant. The $ZF(-5) and $ZF(-6) interfaces (described in the next chapter, 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.
Note:
OpenVMS only: The ZFPRIV statement
On OpenVMS systems, the ZFENTRY macro can be replaced by ZFPRIV, which takes an extra parameter that specifies privilege level (see Using ZFPRIV to Elevate the Privilege Level).
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
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 prospective of the Caché 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 none (use P) The i linkage type is input only. To return an integer type, use P (int *). Input argument may be a numeric string (see note 1).
int * p P 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 8-bit national strings up to 32 767 characters.
ZWARRAYP 2b or s 2B or S 16-bit Unicode strings up to 32 767 characters.
ZHARRAYP 4b 4B Unicode strings up to 32 767 characters, stored in elements implemented by wchar_t (see note 4)
CACHE_EXSTR 1j or j 1J or J Caché long string of 8-bit national characters
CACHE_EXSTR 2j or n 2J or N Caché long string of 16-bit Unicode characters
CACHE_EXSTR 4j 4J Caché long string of wchar_t characters (see note 4)
  1. i, p, d, f — When numeric arguments are specified, Caché allows the input argument to be a string. See Using Numeric Linkages for details.
  2. #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.
  3. 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.
  4. 4B, 4C, 4J— Although wchar_t is typically 32 bits, Caché uses only 16 bits to store each Unicode character, so these linkages are useful only when interacting with existing code or system interfaces that specify use of wchar_t.
Structure and argument prototype definitions (including InterSystems internal definitions) can be seen in the include file cdzf.h.
Note:
The V Linkage for OpenVMS
In addition to the linkage types discussed in this chapter, the V linkage can be used when calling OpenVMS system services (see Using the V Linkage for System Services).
Using Numeric Linkages
Numeric linkage types are provided for the following datatypes:
C Datatype Input In/Out Notes
int i none (use P) The i linkage type is input only. To return an integer type, use P instead.
int * p P Pointer to int.
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, Caché allows the input argument to be a string. When a string is passed, a leading number will be parsed from 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 Caché ObjectScript.
Note:
Preserving Accuracy in Floating Point Numbers
When the output linkage is specified by F (float *) or D (double *), the number you return will be converted to Caché’s 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, 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.
By default, Caché stores fractional numbers in base 10 form. For example, the number 1.3 is stored as 13*.10. In contrast, most high-level languages store fractional numbers in base 2 form (a binary number times some power of 2). If you describe an argument as f (float *) or d (double *), Caché converts its value from base 10 to base 2 on input and back to base 10 on output. This conversion may introduce slight inaccuracies.
Caché supports the $DOUBLE function for creating a standard IEEE format 64-bit floating point number. These numbers can be passed between external functions and Caché 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 Caché will not send strings containing null ($CHAR(0)) characters. When using this datatype, your C function will truncate a string passed by Caché at the first null character, even if the Caché string is actually longer. For example, the Caché 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:
Using C linkages to pass null-terminated strings
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 <cdzf.h>
#include <stdio.h>
#include <wchar.h>   // Required for 2-byte and 4-byte strings

int get_sample(char* retval) {  // 1-byte, null-terminated
   sprintf(retval,"%d",(rand()%1000000));
   return ZF_SUCCESS;
}

int get_sample_W(unsigned short* retval) {  // 2-byte, null-terminated
   swprintf(retval,6,L"%d",(rand()%1000000));
   return ZF_SUCCESS;
}

int get_sample_H(wchar_t* retval) {  // 4-byte, 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 Counted Strings with B Linkage Types
The cdzf.h Callout header file defines counted string structures ZARRAY, ZWARRAY, and ZHARRAY. These structures contain an array of character elements (8-bit, 16-bit Unicode, or wchar t, respectively) and an integer 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
The B linkages specify pointer types ZARRAYP, ZWARRAYP, and ZHARRAYP, corresponding to the three array structures.
C Datatype Input In/Out Notes
ZARRAYP 1b or b 1B or B National string containing up to 32 767 8-bit characters.
ZWARRAYP 2b or s 2B or S Unicode string containing up to 32 767 16-bit characters.
ZHARRAYP 4b 4B Unicode string containing up to 32 767 wchar_t characters.
The maximum size of the array returned is the maximum string size, which is 32,767 characters. The maximum total length of the arguments depends on the number of bytes per character (see Configuring the $ZF Heap).
Here is a short Callout library that uses all three linkage types to return a numeric string:
Using B linkages to pass counted strings
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 <cdzf.h>
#include <stdio.h>
#include <wchar.h>   // Required for 2-byte and 4-byte strings

int get_sample_Z(ZARRAYP retval) {  // 1-byte, 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) {  // 2-byte, 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) {  // 4-byte, 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
Note:
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 Caché Long Strings with J Linkage Types
The J linkages provide a way to pass Caché long strings. See Support for Long String Operations and Long String Limit in the Caché Programming Orientation Guide. Long strings are enabled by default.
C Datatype Input In/Out Notes
CACHE_EXSTR 1j or j 1J or J Caché long string of 8-bit national characters
CACHE_EXSTR 2j or n 2J or N Caché long string of 16-bit Unicode characters
CACHE_EXSTR 4j 4J Caché long string of wchar_t characters
The callin.h header file (see The callin.h Header File in Using the Caché Callin API) defines counted string structure CACHE_EXSTR. This structure contains an array of character elements (8-bit, 16-bit Unicode, or wchar t) and an integer 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 2-byte string */
      wchar_t        *lch;     /* text of the 4-byte string */
/* OR unsigned short *lch   if 4-byte characters are not enabled */
   } str;
} CACHE_EXSTR, *CACHE_EXSTRP;
Two Caché Callin functions (see the Callin Function Reference in Using the Caché Callin API) are used to create or delete CACHE_EXSTR:
See the following example for a demonstration of how these functions are used.
Here is a short Callout library that uses all three linkage types to return a numeric string:
Using J linkages to pass Caché long strings
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 <cdzf.h>
#include <stdio.h>
#include <wchar.h>   // Required for 2-byte and 4-byte strings
#include <callin.h>   // Required for Caché long strings

int get_sample_L(CACHE_EXSTRP retval) {  // 1-byte, longstring
   Callin_char_t numstr[6];
   size_t len = 0;
   sprintf(numstr,"%d",(rand()%1000000));
   len = strlen(numstr);
   CACHEEXSTRKILL(retval);
   if (!CACHEEXSTRNEW(retval,len)) {return ZF_FAILURE;}
   memcpy(retval->str.ch,numstr,len);   // copy to retval->str.ch
   return ZF_SUCCESS;
}

int get_sample_LW(CACHE_EXSTRP retval) {  // 2-byte, longstring
   unsigned short numstr[6];
   size_t len = 0;
   swprintf(numstr,6,L"%d",(rand()%1000000));
   len = wcslen(numstr);
   CACHEEXSTRKILL(retval);
   if (!CACHEEXSTRNEW(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(CACHE_EXSTRP retval) {  // 4-byte, longstring
   wchar_t numstr[6];
   size_t len = 0;
   swprintf(numstr,6,L"%d",(rand()%1000000));
   len = wcslen(numstr);
   CACHEEXSTRKILL(retval);
   if (!CACHEEXSTRNEW(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
Note:
Always kill CACHE_EXSTRP input arguments
In the previous example, CACHEEXSTRKILL(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
The $ZF heap is the virtual memory space allocated for all $ZF input and output parameters. It is controlled by the following Caché system settings:
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:
These settings can be changed in either of the following places:
Callout Library Runup and Rundown Functions
A Caché 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:
When building the Callout library, you may need to explicitly export the symbols ZFInit and ZFUnload during the link procedure.
Troubleshooting
Just because you can call almost any routine with the Callout Gateway doesn’t mean you should. It is best used for math functions, and can be used effectively for interface to external devices not well handled with Caché I/O, or for some system services where a Caché interface does not otherwise exist.
The following actions can cause serious problems: