31 December 2015

In my previous tutorial I went in-depth on what has to be done to expose a foreign function to Rexx. The process was a bit long-winded, but by no means a difficult one. An astute reader, however, will have noted one small problem:

if RxFuncAdd('ExternalMultiply', 'external', 'ExternalMultiply') <> 0 then
  do
    say RxFuncErrMsg()
    exit E_ERROR
  end

That is an awful lot of boilerplate overhead that you have to use for each and every foreign function you wish to use. If only there were some way to add multiple foreign functions with a single call!

Well, there isn’t. Quite. You’ll need two calls, you see.

Declaration and implementation (I)

Let’s say I wanted to add a further external function to Rexx: ExternalAdd, say. It does exactly the same thing as ExternalMultiply, but it adds its arguments instead of multiplying them. Of course you’ll have to declare the new function:

RexxFunctionHandler ExternalAdd;

And you’ll need to implement it:

APIRET APIENTRY ExternalAdd(PCSZ name, ULONG argc, PRXSTRING argv,
                            PCSZ queuename, PRXSTRING returnstring)
{
  long long sum = 0;
  long i;

  for (i = 0; i < argc; i++)
  {
    sum += rexx_to_long(argv[i]);
  }
  long_long_to_rexx(sum, returnstring);

  return RX_OK;
}
Note Again I’ve done the brain-damaged implementation that doesn’t error-check and is unsafe. And again I do this because I’m concentrating on concepts, not on the fiddly details that would obfuscate them when learning.

Declaration and implementation (II)

Now, however, I am going to declare and implement one more function:

RexxFunctionHandler LoadFunctions;
APIRET APIENTRY LoadFunctions(PCSZ name, ULONG argc, PRXSTRING argv,
                              PCSZ queuename, PRXSTRING returnstring)
{
  Registry *freg;

  for (freg = FunctionRegistry; freg->ExternalName; freg++)
  {
    if (RexxRegisterFunctionDll(freg->ExternalName, LIBNAME,
                                freg->InternalName) != RXFUNC_OK)
    {
      return RX_ERROR;
    }
  }
  return RX_OK;
}

Usage

This function is exposed specifically to register the rest of the functions in the API on behalf of the caller. This means that using the library and bringing in all of the exposed APIs looks like this:

if RxFuncAdd('LoadFunctions', 'externals', 'LoadFunctions') <> 0 then
  do
    say RxFuncErrMsg()
    exit E_ERROR
  end
call LoadFunctions

Supporting data structures

Of course if you look inside the LoadFunctions() implementation, there’s data structures in use that we’ve not looked at. These are simple enough:

#define LIBNAME "externals"
typedef struct {
   PCSZ ExternalName;
   PCSZ InternalName;
} Registry;
static Registry FunctionRegistry[] = {
  { "ExternalMultiply", "ExternalMultiply" },
  { "ExternalAdd",      "ExternalAdd"      },
  { NULL,               NULL               }
};

Registry (arbitrarily named) is just a structure containing two (C-style) strings: one for the "External name" (exactly as per RxFuncAdd() in Rexx code) and one for the "Internal name" (same). After that is an array of these that has one member for each additional exported function. A pair of NULL pointers ends the array.

Note A simple NULL-terminated array of strings would have done the job for us, except for the fact that there’s no guarantee that the external and internal names will match for all possible platforms this may get ported to. A bit of "unnecessary" duplication now makes fixing porting problems without code surgery much easier later.

With this information we can now understand the working of LoadFunctions(). All it really does is use RexxRegisterFunctionDLL() on behalf of the user, effectively calling RxFuncAdd() in native code for the caller to spare the endless boilerplate Rexx-side. There’s nothing preventing the user from calling RxFuncAdd() manually (outside of common sense, I mean).

From this point it’s easy to figure out the rest. Complete code is appended.

Complete source

The following blocks contain the full source code for the multiple external function implementation, the test driver, as well as a simple build script usable in a Linux environment.

Simple example
/* externals.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define INCL_RXFUNC
#include <rexxsaa.h>

/* helper function declarations */
static long rexx_to_long(RXSTRING);
static void long_long_to_rexx(long long, PRXSTRING);

/* external API function declarations */
RexxFunctionHandler ExternalMultiply;
RexxFunctionHandler ExternalAdd;
RexxFunctionHandler LoadFunctions;

/* symbolic return values */
#define RX_OK     0
#define RX_ERROR  1

/* function registry */
#define LIBNAME "externals"
typedef struct {
   PCSZ ExternalName;
   PCSZ InternalName;
} Registry;
static Registry FunctionRegistry[] = {
  { "ExternalMultiply", "ExternalMultiply" },
  { "ExternalAdd",      "ExternalAdd"      },
  { NULL,               NULL               }
};

/* external API functions */
APIRET APIENTRY ExternalMultiply(PCSZ name, ULONG argc, PRXSTRING argv,
                                 PCSZ queuename, PRXSTRING returnstring)
{
  long long product = 1;
  long i;

  for (i = 0; i < argc; i++)
  {
    product *= rexx_to_long(argv[i]);
  }
  long_long_to_rexx(product, returnstring);

  return RX_OK;
}

APIRET APIENTRY ExternalAdd(PCSZ name, ULONG argc, PRXSTRING argv,
                            PCSZ queuename, PRXSTRING returnstring)
{
  long long sum = 0;
  long i;

  for (i = 0; i < argc; i++)
  {
    sum += rexx_to_long(argv[i]);
  }
  long_long_to_rexx(sum, returnstring);

  return RX_OK;
}

APIRET APIENTRY LoadFunctions(PCSZ name, ULONG argc, PRXSTRING argv,
                              PCSZ queuename, PRXSTRING returnstring)
{
  Registry *freg;

  for (freg = FunctionRegistry; freg->ExternalName; freg++)
  {
    if (RexxRegisterFunctionDll(freg->ExternalName, LIBNAME,
                                freg->InternalName) != RXFUNC_OK)
    {
      return RX_ERROR;
    }
  }
  return RX_OK;
}

/* helper functions */

static long rexx_to_long(RXSTRING rexxval)
{
  return strtol(RXSTRPTR(rexxval), NULL, 10);
}

static void long_long_to_rexx(long long val, PRXSTRING rexxval)
{
  sprintf(RXSTRPTR(*rexxval), "%lld", val);
  rexxval->strlength = strlen(RXSTRPTR(*rexxval));
}
Test driver
/* test-externals.rx */
E_OK         = 0
E_SYNTAX     = 1
E_ERROR      = 2
E_FAILURE    = 3
E_HALT       = 4
E_NOTREADY   = 5
E_NOVALUE    = 6
E_LOSTDIGITS = 7
E_UNKNOWN    = 255

signal on syntax      name  error
signal on error       name  error
signal on failure     name  error
signal on halt        name  error
signal on notready    name  error
signal on novalue     name  error
signal on lostdigits  name  error

if RxFuncAdd('LoadFunctions', 'externals', 'LoadFunctions') <> 0 then
  do
    say RxFuncErrMsg()
    exit E_ERROR
  end
call LoadFunctions

say 'ExternalMultiply(2, 3, 4, 5, 6) returned' ExternalMultiply(2, 3, 4, 5, 6)
say 'ExternalAdd(2, 3, 4, 5, 6) returned' ExternalAdd(2, 3, 4, 5, 6)

exit E_OK

error:
  type = condition('C')
  if condition('I') = 'SIGNAL' then
    say 'Error' type || '(' || rc || ') signalled on line' sigl || '.'
  else
    say 'Error' type || '(' || rc || ') called on line' sigl || '.'
  say 'Description:' condition('D')

  select
    when type = 'SYNTAX' then
      code = E_SYNTAX
    when type = 'ERROR' then
      code = E_ERROR
    when type = 'FAILURE' then
      code = E_FAILURE
    when type = 'HALT' then
      code = E_HALT
    when type = 'NOTREADY' then
      code = E_NOTREADY
    when type = 'NOVALUE' then
      code = E_NOVALUE
    when type = 'LOSTDIGITS' then
      code = E_LOSTDIGITS
    otherwise
      code = E_UNKNOWN
  end

  exit code
Build script
/* build-externals.rx */
'gcc -shared -fpic -o libexternals.so externals.c'