User functions in KCML
The UFN interface
The interface between KCML and the UFN's converts parameters from KCML internal form for example BCD number or fixed size string to a standard C form for example int, double, or null terminated string as specified by the UFN. It then puts the parameters into a table and calls the UFN with a pointer to the table. The UFN returns, possibly with an error status, and the interface converts the receiver parameters back to KCML internal format.
Support for UFN's is through two files, uf_samp.c, a C program external to KCML and uf_pub.h, a header file for uf_samp.c. The makefile uf_samp.mak can be used to build this sample.
The interface to UFN's is by a single call to uf_ext() during the initialization of KCML and direct calls to the UFN's when the CALL statement is executed.
Initialization
The programmer must provide the a function in his library with the name and type of
PUFN_Spec UFN_EXPORT uf_ext(UFN_Subs *p) { /* return pointer to table */ return UFN_Table; }
KCML will call this during its initialisation to get a pointer to the table of UFNs specified in this library. The programmer can also perform any required one off initialization at this time. The argument passed into this reoutine is now obsolete but is preserved for compatibility.
The layout of the table whose address is returned by uf_ext() is defined in the next section.
Specification of UFN
UFN's are defined in uf_samp.c which declares an array of UFN specifications for all the possible UFN calls. Each element of the array is of type UFN_Spec and it specifies the name by which the UFN is known in KCML, the entry point into the external C routine and a list of the parameters to be passed. The layout of this table is the responsibility of the programmer. It must be in sorted order of UFN name. When KCML starts up it calls the routine uf_ext() in uf_samp.c which returns a pointer to this table back to KCML.
The typedef for UFN_Spec may be found in uf_pub.h.
typedef struct { const char *name; // name CALLed in KCML UFN_RET (UFN_API *function)(UFN_ARGS); // the C function to call UfnArgType Param[UFN_MAX_PARAMS+1]; // type info for the args } UFN_Spec;
The values in the elements of the parameter list specify the type of storage expected for that parameter by the C routine. High order bits can optionally be set by macro to indicate that the parameter value must be returned to KCML after the call. This RCVR() macro must be used for parameters that follow the TO in the CALL statement. Other bits are set for arrays and optional fields.
An example specification table follows:
static const UFN_Spec UFN_Table[] = { { "DOUBLEIT", doubleit, UFN_INT, RCVR(UFN_INT), }, { "REVERSEIT", reverseit, UFN_CSTR, RCVR(UFN_CSTR), }, { 0 }, /* end marker */ };
where, in uf_pub.h:
MAXPARAMS | 12 - the maximum number of parameters allowed. Do not change this. |
RCVR(x) | Marks x as a receiver parameter that must follow the TO. In the original UFN implementation the parameters before the TO were passed down to the function and only those after the TO had their values returned. In the current implementation any parameter changed in the function will be returned but you should still stick with this convention. |
CSTR | Indicates the parameter is a C string (NULL terminated. May NOT contain NULLs). KCML will strip trailing blanks before sending down and will retore them if returned. |
STR | Indicates the parameter is a KCML string (not NULL terminated. May contain NULLs). This can be used for structures. |
INT | Indicates the parameter is a 4 byte signed integer. |
DOUBLE | Indicates the parameter is a 8 byte double. |
The uf_samp.c sample defines two routines
They can be called by the uf_samp.src program:
00010 REM Simple test of UFN sample : 'main() : $END : DEFSUB 'main() : LOCAL DIM from$32 : LOCAL DIM to$32 : LOCAL DIM x : LOCAL DIM y : : x = 27 : from$ = "quick brown fox" : CALL DOUBLEIT x TO y : CALL REVERSEIT from$ TO to$ : PRINT "x = ";x;", y = ";y : PRINT "to = ";to$ : END SUB
Which would produce the following output:-
$ kcml -x uf_samp -p uf_samp.src x = 27 , y = 54 to = xof nworb kciuq
Note:
static UFN_RET UFN_API my_ufn(UFN_ARGS) { ... ... } static const UFN_Spec UFN_Table[] = { { "MY_UFN", my_ufn, UFN_INT, RCVR(UFN_INT), }, ... ... }; PUFN_Spec UFN_EXPORT uf_ext(UFN_Subs *p) { /* return pointer to table */ return UFN_Table; }
CALLing a User Defined Function.
A UFN is called with a pointer to an array of parameters held within KCML's data segment. The parameter block is of type UFN_value[]. where the UFN_Value is defined as:
typedef union UFN_Value { KCMLDOUBLE nval; /* numeric value */ KCMLINT ival; /* integer value */ UFN_Pstr s; /* string */ UFN_Pna na; /* numeric array */ UFN_Psa sa; /* string array */ KCMLINT fd; /* file descriptor */ UFN_Sym sym; /* symbol */ } UFN_Value;
This is a union of all the possible representations of a parameter. The programmer should use the element appropriate to the type specified. To help with this a number of useful macros have been defined
#define NVAL(x) (pb[(x)].nval) #define IVAL(x) (pb[(x)].ival) #define NINT(x) (IVAL(x)) #define SVAL(x) (pb[(x)].s.sptr) #define SLEN(x) (pb[(x)].s.slen) #define SATTR(x) (pb[(x)].s.attr) #define SYPTR(x) (pb[(x)].sym.sym_ptr)
KCML strings require information about the length of the string to be passed so a structure is required.
typedef struct UFN_Pstr { unsigned char *sptr; /* pointer to slen bytes */ unsigned int slen; /* length of string */ }
The structures UFN_sa and UFN_na which define the dimensions of string and numeric arrays are defined in uf_pub.h.
The length of a string parameter can be found using the SLEN(nParam) macro. For example, the reverseit() routine checks the destination buffer is big enough to store the reversed string
static UFN_RET UFN_API reverseit(UFN_ARGS) { char *p; char *q; if (SLEN(0) >= SLEN(1)) { /* won't fit in rcvr */ return UFN_BADARGS; } p = SVAL(0); ... ... }
The address of a string can be found using the SVAL(nParam) as illustrated in the above code fragment.
Return value
The return value of the UFN routine can be used to produce KCML error messages. These are
Return value | Error message |
---|---|
0 | success - no error |
1 | S24 - Wrong parameters supplied to user function |
2 | P46 - Error from user function. |
3 | S22 - Unrecoverable error in user function |
>3 | X75 - Illegal number |
Miscellaneous
There are two statements in KCML which can be used with UFNs
LIST U lists all built-in CALL routines. When a UFN library has been loaded then LIST U will also include all user defined functions and their usage, e.g.
... ... XML_PARSE_BUFFER sym TO KDB_ERROR_CODE, handle XML_TEXT handle, sym, sym, int, sym TO KDB_ERROR_CODE, int Library uf_samp DOUBLEIT int TO int REVERSEIT C-str TO C-str
LIST CALL This prints a table of CALL commands against the lines on which they occur, e.g.
LIST CALL REVERSEIT - 00010 DOUBLEIT - 00030
Linking the UFN library
To build the sample shared library you will need uf_samp.c, uf_pub.h and uf_samp.mak. These are all installed in the normal /usr/local/kcml directory for Unix systems. Inspect the makefile uf_samp.mak and uncomment the CFLAGS compiler definitions appropraite to your platform then issue
make -f uf_samp.mak
to build the library which will usually be called uf_samp.so. It can then be used by changing the way KCML is invoked in .profile to add a -x switch e.g.
kcml -x uf_samp StartProg.src
The library name parameter to the -x flag does not need the libraries filename extension, eg .so, as KCML will automatically add the operating system's default extension.