External Functions

External Functions

Top  Previous  Next

 

Sometimes applications need to be able to call external functions such as those in standard operating system libraries. QM provides an External Call Interface by which this can be done from within the security of the managed application environment of QMBasic.

 

 

Function Prototypes

 

Before an external function can be used by an application, it must be defined using the QMBasic DEFFUN statement with the EXTERNAL keyword

DEFFUN name(arg1, arg2, ...) EXTERNAL {CALLING "libname"}

where

nameis the name of the function.
arg1 etcare placeholders for the function arguments.
libnameis a quoted string defining the name of the executable program providing these library functions. If omitted, it defaults to qmextcall. The .exe suffix on Windows systems is added automtically. The name is case sensitive on Linux systems.

 

To preserve the managed application environment provided by QM, external function calls are handled by a child process that is created when the first external function is called from a QM session. Use of a child process ensures that errors in this program cannot corrupt memory within the parent QM process. This isolation is essential as corruptions to QM's shared memory area could cause problems in other processes that would be hard to diagnose and might damage files.

 

Although the overheads of using a separate process are low, the external function call mechanism is intended for execution of user written code that performs a substantial task or is not used intensively by the application. Developers who have a need for intensive use of short library functions should contact QM support as it may be possible to integrate these into the standard product.

 

To use the external call interface, the application developer must download and modify the skeleton external call server program that is shown below. This sample program shows how to execute the C library stat() function, returning the size of a named file. This operation is available directly from QMBasic and the program is not intended as a realistic use of the external call interface but serves to show how to implement external function calls.

 

Note that the function names passed by the parent QM process are always in uppercase unless the CASE.SENSITIVE setting of the $MODE compiler directive is enabled. A real external call server program may need to support multiple functions and would do so using conditional coding similar to that in the skeleton program.

 

Up to 31 arguments can be passed into an external function. To optimise the interface between the child and parent processes, an argument placeholder name can be prefixed with IN: to indicate that the argument is passed into the function but no updated value is to be returned, or OUT: to indicate that the argument is used only to return data and the current content of the argument variable should not be passed to the child process. For example,

DEFFUN MYFUN(IN:A, IN:B, OUT:C) EXTERNAL

Any arguments for which neither qualifier is present are bidirectional though automatic optimisation is applied so as not to pass back arguments that have not changed.

 

The server process is started when the parent process first calls an external function and will normally stay running until the user exits from QM or uses LOGTO to move to a different account. The program can maintain internal state data, file variables, etc that persist from one call to the next.

 

Arguments passed into the function are numbered from one. The user written code that will replace the test code in the skeleton program can access the arguments passed from the QMBasic function call by use of GetInteger(), GetFloat() or GetString(), each of which takes the argument number to be retrieved as its only parameter. The number of arguments passed from the QMBasic program can be determined using the GetNumArgs() function and the data type for an argument can be retrieved using the GetArgType() function which returns a single character, I for integer, F for floating point and S for string. Data type conversion will be performed where the actual argument type does not match the form in which is requested, for example, using GetInteger() to retrieve and argument that was passed into the QMBasic function call as a string. Attempting to access an argument position that was not passed in the function call or an argument that was unassigned will return either numeric zero or a null string.

 

The GetString() function returns a pointer to a null terminated 8 bit character string form of the argument. This string should not be overwritten. Although the string is null terminated, the interface between the parent QM process and the external call server process permits char(0) within strings. In this case, use of the C strlen() function will not return the correct string length. The StringLength() function can be used to determine the length of the actual argument string.

 

Using GetString() with an ECS mode version of QM will return only the low order 8 bits of each character and hence is likely to lead to incorrect results if the QMBasic data passed as the argument contains characters outside the 8-bit set. The GetStringW() function returns data as a null terminated wchar_t data type string and can be used regardless of whether the QM system is in ECS mode. Note that Windows systems define wchar_t as 16 bits whereas the gcc compiler used on most Linux systems defines wchar_t as 32 bits.

 

Arguments may be updated for return values using the ReturnInteger(), ReturnFloat() or ReturnString() functions. Each of these takes the argument number and new value as its parameters. The ReturnString()function has a third argument through which the caller should pass the string length. Passing a negative value for the string length indicates that the function should treat the data as a null terminated string and determine its length internally.

 

The ReturnStringW() function returns a wchar_t form string for which the length should be specified in characters, not bytes. Use of ReturnStringW() on a non-ECS mode system to return data that includes characters outside the 8-bit set will return only the low order 8 bits of each character.

 

The return functions allow reference to argument number zero. This corresponds to the value that will be returned as the result of the QMBasic function call that triggered the child process action.

 

The new value set by any of the return functions will be ignored for arguments declared as input only in the QMBasic function definition.

 

The SetOSError() function can be used to set a value for OS.ERROR() in the QMBasic program on return from the external function. SetOSError() takes a single int argument. If this function is not used, the value of OS.ERROR() is unchanged.

 

The CompleteCall() function must be called as the final stage in processing a request and takes a single argument which is the value to be returned by the QMBasic STATUS() function after the external function call has completed. This allows errors to be reported to the calling process. It is recommended that error code values should correspond to the standard QM error numbers as defined in the SYSCOM ERR.H include record. Passing a negative status value will cause the QMBasic program to abort, reporting the absolute status value in the error message. Any return argument values will not be updated in this situation.

 

 

Compiling the Program

 

Windows users should define the WINDOWS token either from the compiler or by including a line

#define WINDOWS

before the #include directives.

 

The appropriate version of the qmextcall library from the bin subdirectory of the QMSYS account must be linked with this program. The versions supplied are:

 

QM Version

Name

Usage

Windows 32 bit

qmextcallbl.lib

Borland C

qmextcallms.lib

Microsoft C

Windows 64 bit

qmextcall64.lib

Microsoft C

Linux / Mac / Unix

libqmextcall.a

Static library

qmextcall.so

Shared object library (Linux)

qmextcall.dylib

Dynamic library (Mac)

 

The default name for the compiled program is qmextcall (qmextcall.exe on Windows). This program must be placed in the bin subdirectory of the QMSYS account. Alternative names can be used with the CALLING clause to the DEFFUN statement.

 

 

Skeleton External Call Server

 

#include <stdio.h>

#include <string.h>

#include <errno.h>

#include <sys/stat.h>

 

#ifdef WINDOWS

  #include <windows.h>

#else

  #include <wchar.h>

#endif

 

#include <qmextcall.h>

 

int main(int argc, char * argv[])

{

char function_name[MAX_FUNCTION_NAME_LEN+1];

struct stat statbuf;

int err;

 

/* Process command line and initialise */

 

Initialise(argc, argv);

 

/* Process requests */

 

while(GetCallRequest(function_name))

 {

  err = 0;    /* Default returned status value */

 

//----- Insert code to handle your external functions here

 

  if (!strcmp(function_name, "STAT"))

   {

    if (!stat(GetString(1), &statbuf))

     {

      ReturnInteger(0, statbuf.st_size);

     }

    else

     {

      ReturnInteger(0, -1);

      err = errno;

     }

   }

 

//----- End of code to handle your external functions

 

  else      /* Function name not recognised */

   {

    err = -ER_FUNCNAME;

   }

 

  CallCompleted(err);   /* Send updated arguments and result */

 }

 

return 0;

}