Calls from External Routines: Call-Ins

Call-In is a framework supported by GT.M that allows a C/C++ program to invoke an M routine within the same process context. GT.M provides a well-defined Call-In interface packaged as a run-time shared library that can be linked into an external C/C++ program.

Relevant files for Call-Ins

To facilitate Call-Ins to M routines, the GT.M distribution directory ($gtm_dist) contains the following files:

  1. libgtmshr.so - A shared library that implements the GT.M run-time system, including the Call-In API. If Call-Ins are used from a standalone C/C++ program, this library needs to be explicitly linked into the program. See Building Standalone Programs”, which describes the necessary linker options on each supported platforms.

    [Note] Note

    .so is the recognized shared library file extension on most UNIX platforms.

  2. mumps - The GT.M startup program that dynamically links with libgtmshr.so.

  3. gtmxc_types.h - A C-header file containing the declarations of Call-In API.

The following sections describe the files relevant to using Call-Ins.

gtmxc_types.h

The header file provides signatures of all Call-In interface functions and definitions of those valid data types that can be passed from C to M. FIS strongly recommends that these types be used instead of native types (int, char, float, and so on), to avoid possible mismatch problems during parameter passing.

gtmxc_types.h defines the following types that can be used in Call-Ins.

Type

Usage

void

Used to express that there is no function return value

gtm_int_t

gtm_int_t has 32-bit length on all platforms.

gtm_uint_t

gtm_uint_t has 32-bit length on all platforms

gtm_long_t

gtm_long_t has 32-bit length on 32-bit platforms and 64-bit length on 64-bit platforms. It is much the same as the C language long type.

gtm_ulong_t

gtm_ulong_t is much the same as the C language unsigned long type.

gtm_float_t

floating point number

gtm_double_t

Same as above but double precision.

gtm_status_t

type int. If it returns zero then the call was successful. If it is non-zero, when control returns to GT.M, it issues a trappable error.

gtm_long_t*

Pointer to gtm_long_t. Good for returning integers.

gtm_ulong_t*

Pointer to gtm_ulong_t. Good for returning unsigned integers.

typedef struct {
    gtm_long_t length;
    gtm_char_t* address;
} gtm_string_t;

The pointer types defined above are 32-bit addresses on all 32-bit platforms. For 64-bit platforms, gtm_string_t* is a 64-bit address.

gtmxc_types.h also provides an input-only parameter type gtm_pointertofunc_t that can be used to obtain call-back function pointers via parameters in the external routine. If a parameter is specified as I:gtm_pointertofunc_t and if a numeric value (between 0-5) is passed for this parameter in M, GT.M interprets this value as the index into the callback table and passes the appropriate callback function pointer to the external routine.

[Note] Note

GT.M represents values that fit in 18 digits as numeric values, and values that require more than 18 digits as strings.

[Note] Note

GT.M MUMPS language runtime supports a maximum of 1MiB strings. Please take care to use 1MiB buffers for Output-only and Input-Output variables. Failure to do so could cause a segmentation violation if the called M routine writes more data into the supplied buffer than the caller allocated.

gtmxc_types.h also includes definitions for the following entry points exported from libgtmshr:

void gtm_hiber_start(gtm_uint_t mssleep);
void gtm_hiber_start_wait_any(gtm_uint_t mssleep)
void gtm_start_timer(gtm_tid_t tid, gtm_int_t time_to_expir, void (*handler)(), gtm_int_t hdata_len, void \
*hdata);
void gtm_cancel_timer(gtm_tid_t tid);

where:

  • mssleep - milliseconds to sleep

  • tid - unique timer id value

  • time_to_expir - milliseconds until timer drives given handler

  • handler - function pointer to handler to be driven

  • hdata_len - 0 or length of data to pass to handler as a parameter

  • hdata - NULL or address of data to pass to handler as a parameter

gtm_hiber_start() always sleeps until the time expires; gtm_hiber_start_wait_any() sleeps until the time expires or an interrupt by any signal (including another timer). gtm_start_timer() starts a timer but returns immediately (no sleeping) and drives the given handler when time expires unless the timer is canceled.

[Important] Important

GT.M continues to support xc_* equivalent types of gtm_* for upward compatibility. gtmxc_types.h explicitly marks the xc_* equivalent types as deprecated.

Call-In Table

The Call-In table file is a text file that contains the signatures of all M label references that get called from C. In order to pass the typed C arguments to the type-less M formallist, the enviroment variable GTMCI must be defined to point to the Call-In table file path. Each signature must be specified separately in a single line. GT.M reads this file and interprets each line according to the following convention (specifications withint box brackets "[]", are optional):

<c-call-name> : <ret-type> <label-ref> ([<direction>:<param-type>,...])

where,

<label-ref>: is the entry point (that is a valid label reference) at which GT.M starts executing the M routine being called-in

<c-call-name>: is a unique C identifier that is actually used within C to refer to <label-ref>

<direction>: is either I (input-only), O (output-only), or IO (input-output)

<ret-type>: is the return type of <label-ref>

[Note] Note

Since the return type is considered as an output-only (O) parameter, the only types allowed are pointer types and void. Void cannot be specified as parameter.

<param-type>: is a valid parameter type. Empty parentheses must be specified if no argument is passed to <label-ref>

The <direction> indicates the type of operation that GT.M performs on the parameter read-only (I), write-only (O), or read-write (IO). All O and IO parameters must be passed by reference, that is as pointers since GT.M writes to these locations. All pointers that are being passed to GT.M must be pre-allocated. The following table details valid type specifications for each direction.

Directions

Allowed Parameter types

I

gtm_long_t, gtm_ulong_t, gtm_float_t, gtm_double_t,_gtm_long_t*, gtm_ulong_t*, gtm_float_t*, gtm_double_t*,_gtm_char_t*, gtm_string_t*

O/IO

gtm_long_t*, gtm_ulong_t*, gtm_float_t*, gtm_double_t*,_gtm_char_t*, gtm_string_t*

Here is an example of Call-In table (calltab.ci) for piece.m (see “Example: Calling GT.M from a C Program”):

print     :void            display^piece()
getpiece  :gtm_char_t*     get^piece(I:gtm_char_t*, I:gtm_char_t*, I:gtm_long_t)
setpiece  :void            set^piece(IO:gtm_char_t*, I:gtm_char_t*, I:gtm_long_t, I:gtm_char_t*)
pow       :gtm_double_t*   pow^piece(I:gtm_double_t, I:gtm_long_t)
powequal  :void            powequal^piece(IO:gtm_double_t*, I:gtm_long_t)
piece     :gtm_double_t*   pow^piece(I:gtm_double_t, I:gtm_long_t)
[Note] Note

The same entryref can be called by different C call names (for example, pow, and piece). However, if there are multiple lines with the same call name, only the first entry will be used by GT.M. GT.M ignores all subsequent entries using a call name. Also, note that the second and third entries, although shown here as wrapped across lines, must be specified as a single line in the file.

Call-In Interface

This section is further broken down into 6 subsections for an easy understanding of the Call-In interface. The section is concluded with an elaborate example.

Initialize GT.M

gtm_status_t gtm_init(void);

If the base program is not an M routine but a standalone C program, gtm_init() must be called (before calling any GT.M functions), to initialize the GT.M run-time system.

gtm_init() returns zero (0) on success. On failure, it returns the GT.M error status code whose message can be read into a buffer by immediately calling gtm_zstatus() (see Print Error Messages”). Duplicate invocations of gtm_init() are ignored by GT.M.

If Call-Ins are used from an external call function (that is, a C function that has itself been called from M code), gtm_init() is not needed, because GT.M is initialized before the External Call. All gtm_init() calls from External Calls functions are ignored by GT.M.

Call an M Routine from C

GT.M provides 2 interfaces for calling a M routine from C. These are:

  • gtm_cip

  • gtm_ci

gtm_cip offers better performance on calls after the first one.

gtm_cip
gtm_status_t gtm_cip(ci_name_descriptor *ci_info, ...);

The variable argument function gtm_cip() is the interface that invokes the specified M routine and returns the results via parameters.

ci_name_descriptor has the following structure:

typedef struct
{
  gtm_string_t rtn_name;
  void* handle;
} ci_name_descriptor;

rtn_name is a C character string indicating the corresponding <lab-ref> entry in the Call-In table.

The handle is GT.M private information initialized by GT.M on the first call-in and to be provided unmodified to GT.M on subsequent calls. If application code modifies it, it will corrupt the address space of the process, and potentially cause just about any bad behavior that it is possible for the process to cause, including but not limited to process death, database damage and security violations.

The gtm_cip() call must follow the following format:

status = gtm_cip(<ci_name_descriptor> [, ret_val] [, arg1] ...);

First argument: ci_name_descriptor, a null-terminated C character string indicating the alias name for the corresponding <lab-ref> entry in the Call-In table.

Optional second argument: ret_val, a pre-allocated pointer through which GT.M returns the value of QUIT argument from the (extrinsic) M routine. ret_val must be the same type as specified for <ret-type> in the Call-In table entry. The ret_val argument is needed if and only if <ret-type> is not void.

Optional list of arguments to be passed to the M routine's formallist: the number of arguments and the type of each argument must match the number of parameters, and parameter types specified in the corresponding Call-In table entry. All pointer arguments must be pre-allocated. GT.M assumes that any pointer, which is passed for O/IO-parameter points to valid write-able memory.

The status value returned by gtm_cip() indicates the GT.M status code; zero (0), if successful, or a non-zero; $ZSTATUS error code on failure. The $ZSTATUS message of the failure can be read into a buffer by immediately calling gtm_zstatus() (for details, see Print Error Messages”).

gtm_ci
gtm_status_t gtm_ci(const gtm_char_t* c_call_name, ...);

The variable argument function gtm_ci() is the interface that actually invokes the specified M routine and returns the results via parameters. The gtm_ci() call must be in the following format:

status = gtm_ci(<c_call_name> [, ret_val] [, arg1] ...);

First argument: c_call_name, a null-terminated C character string indicating the alias name for the corresponding <lab-ref> entry in the Call-In table.

Optional second argument: ret_val, a pre-allocated pointer through which GT.M returns the value of QUIT argument from the (extrinsic) M routine. ret_val must be the same type as specified for <ret-type> in the Call-In table entry. The ret_val argument is needed if and only if <ret-type> is not void.

Optional list of arguments to be passed to the M routine's formallist: the number of arguments and the type of each argument must match the number of parameters, and parameter types specified in the corresponding Call-In table entry. All pointer arguments must be pre-allocated. GT.M assumes that any pointer, which is passed for O/IO-parameter points to valid write-able memory.

The status value returned by gtm_ci() indicates the GT.M status code; zero (0), if successful, or a non-zero; $ZSTATUS error code on failure. The $ZSTATUS message of the failure can be read into a buffer by immediately calling gtm_zstatus(). For more details, see Print Error Messages”.

Example: Calling GT.M from a C Program

Here are some working examples of C programs that use call-ins to invoke GT.M. Each example is packaged in a zip file which contains a C program, a call-in table, and a GT.M API. To run an example, click Download icon and follow the compiling and linking instructions in the comments of the C program.

Example

Download information

gtmaccess.c (gtm_ci interface)

Click Download gtmci_gtmaccess.zip to download or open directly from http://tinco.pair.com/bhaskar/gtm/doc/books/pg/UNIX_manual/gtmci_gtmaccess.zip

gtmaccess.c (gtm_cip interface)

Click Download gtmcip_gtmaccess.zip to download or open directly from http://tinco.pair.com/bhaskar/gtm/doc/books/pg/UNIX_manual/gtmcip_gtmaccess.zip

cpiece.c (gtm_ci interface)

Click Download gtmci_cpiece.zip to download or open directly from http://tinco.pair.com/bhaskar/gtm/doc/books/pg/UNIX_manual/gtmci_cpiece.zip

Print Error Messages

void gtm_zstatus (gtm_char_t* msg_buffer, gtm_long_t buf_len);

This function returns the null-terminated $ZSTATUS message of the last failure via the buffer pointed by msg_buffer of size buf_len. The message is truncated to size buf_len if it does not fit into the buffer. gtm_zstatus() is useful if the external application needs the text message corresponding to the last GT.M failure. A buffer of 2048 is sufficient to fit in any GT.M message.

Exit from GT.M

gtm_status_t  gtm_exit (void);

gtm_exit() can be used to shut down all databases and exit from the GT.M environment that was created by a previous gtm_init().

Note that gtm_init() creates various GT.M resources and keeps them open across multiple invocations of gtm_ci() until gtm_exit() is called to close all such resources. On successful exit, gtm_exit() returns zero (0), else it returns the $ZSTATUS error code.

gtm_exit() cannot be called from an external call function. GT.M reports the INVGTMEXIT error if an external call function invokes gtm_exit(). Since the GT.M run-time system must be operational even after the external call function returns, gtm_exit() is meant to be called only once during a process lifetime, and only from the base C/C++ program when GT.M functions are no longer required by the program.

Building Standalone Programs

All external C functions that use call-ins should include the header file gtmxc_types.h that defines various types and provides signatures of call-in functions. To avoid potential size mismatches with the parameter types, FIS strongly recommends that gtm *t types defined in gtmxc_types.h be used instead of the native types (int, float, char, etc).

To use call-ins from a standalone C program, it is necessary that the GT.M runtime library (libgtmshr.so) is explicitly linked into the program. If call-ins are used from an External Call function (which in turn was called from GT.M through the existing external call mechanism), the External Call library does not need to be linked explicitly with libgtmshr.so since GT.M would have already loaded it.

The following sections describe compiler and linker options that must be used on each platform for call-ins to work from a standalone C/C++ program.

IBM pSeries (RS/6000) AIX

  • Compiler: -I$gtm_dist

  • Linker: -L$gtm_dist -lgtmshr

X86 GNU/Linux

  • Compiler: -I$gtm_dist

  • Linker: -L$gtm_dist -lgtmshr -rpath $gtm_dist

  • FIS advises that the C/C++ compiler front-end be used as the Linker to avoid specifying the system startup routines on the ld command. The compile can pass linker options to ld using -W option (for example, cc -W1, -R, $gtm_dist). For more details on these options, refer to the appropriate system's manual on the respective platforms.

Nested Call-Ins

Call-ins can be nested by making an external call function in-turn call back into GT.M. Each gtm_ci() called from an External Call library creates a call-in base frame at $ZLEVEL 1 and executes the M routine at $ZLEVEL 2. The nested call-in stack unwinds automatically when the External Call function returns to GT.M.

GT.M currently allows up to 10 levels of nesting, if TP is not used, and less than 10 if GT.M supports call-ins from a transaction (see “Rules to Follow in Call-Ins”). GT.M reports the CIMAXLEVELS error when the nesting reaches its limit.

Following are the GT.M commands, Intrinsic Special Variables, and functions whose behavior changes in the context of every new nested call-in environment.

ZGOTO 0 (zero) returns to the processing of the invoking non-M routine as does ZGOTO 1 (one) with no entryref, while ZGOTO 1:entryref replaces the originally invoked M routine and continues M execution.

$ZTRAP/$ETRAP NEW'd at level 1 (in GTM$CI frame).

$ZLEVEL initializes to one (1) in GTM$CI frame, and increments for every new stack level.

$STACK initializes to zero (0) in GTM$CI frame, and increments for every new stack level.

$ESTACK NEW'd at level one (1) in GTM$CI frame.

$ECODE/$STACK() initialized to null at level one (1) in GTM$CI frame.

[Note] Note

After a nested call-in environment exits and the external call C function returns to M, the above ISVs and Functions restore their old values.

Rules to Follow in Call-Ins

  1. External calls must not be fenced with TSTART/TCOMMIT if the external routine calls back into mumps using call-in mechanism. GT.M reports the CITPNESTED error if nested call-ins are invoked within a TP fence since GT.M currently does not handle TP support across multiple call-in invocations.

  2. The external application should never call exit() unless it has called gtm_exit() previously. GT.M internally installs an exit handler that should never be bypassed.

  3. The external application should never use any signals when GT.M is active since GT.M reserves them for its internal use. GT.M provides the ability to handle SIGUSR1 within M (see “$ZINTerrupt” for more information). An interface is provided by GT.M for timers.

  4. FIS recommends the use of gtm_malloc() and gtm_free() for memory management by C code that executes in a GT.M process space for enhanced performance and improved debugging. Always use gtm_malloc to allocate returns for pointer types to prevent memory leaks.

  5. GT.M performs device input using the read() system service. UNIX documentation recommends against mixing this type of input with buffered input services in the fgets() family and ignoring this recommendation is likely to cause loss of input that is difficult to diagnose and understand.