CREXX

REXX Language implementation

View the Project on GitHub adesutherland/CREXX

\crexx{}/PA - Plugin Architecture

This facility allows for the compilation, linking, and execution of additional functionality (developed in C) alongside REXX code. It enables the decoupling of native modules (plugins), which can be developed and packaged either as separate entities or linked statically to the main \crexx{} core solution.

The architecture is designed to completely decouple the plugins from the rest of \crexx{}, and the plugin client library only consists of a single header file (rexxpa.h)

Plugin developers have the flexibility to structure their code in a way that allows for both dynamic or static linking. While the source code can remain the same, it needs to be built differently based on the desired linking approach, as it relies on macro expansion.

During the REXX compilation process, the REXX compiler inspects the plugin to determine the type and argument of any provided functions. This inspection helps ensure type safety. It is recommended for plugin developers to load or initialise dependencies only when an explicitly exposed initialisation REXX function is called or when the first function is invoked (lazy initialisation) to avoid overhead during build (rather than run). In addition, for the static builds, there is macro definition DECL_ONLY which allows definition / implementation code to be excluded from the static library designed to be linked to the compiler only.

For dynamically packaged plugins (with the extension *.rxplugin), the search process for these plugins mirrors how \crexx{} locates REXX modules (extensions such as *.rexx, *.rxas, or *.rxbin). In contrast, for static builds, the provided functions are loaded before the execution of the main() function in the core crexx solution, however these functions are placed at the end of the search order, meaning users can override static function definitions with local native or crexx modules.

The Plugin Architecture offers a comprehensive set of resources for developers, including a header file, macros, and a cmake configuration. These tools aim to create a convenient and efficient development environment for plugin creation.

User-provided plugins are recommended to be provided as dynamic .rxplugin files. Dynamic plugins offer several advantages: easy site-wide distribution by placing them in the same directory as \crexx{} binaries, project-specific customization by locating them in the project REXX files directory, and flexible placement in any desired location using “rxc” and “rxvm” options.

In contrast, static plugin packaging is more complex in terms of linking and is intended for core \crexx{} components that are part of every \crexx{} release. Static plugins are shipped within the \crexx{} binaries, ensuring their availability and consistency across distributions.

The choice between dynamic and static plugin packaging depends on the specific requirements and use cases: dynamic plugins provide flexibility and customization for users, while static plugins are designed to provide a robust solution for core \crexx{} components.

Example Plugin

The following code demonstrates a decryption plugin.

/*------------------------------------------------------------------*/
/*                                                                  */
/* Name: rxdes.C                                                    */
/*                                                                  */
/* Copyright René Jansen june 1st 1993                              */
/*                                                                  */
/* Function: crexx external functions RxDesEncrypt and RxDesDecrypt */
/*                                                                  */
/* dependencies/calls: desbase.c CREXX/rxpa                         */
/*                                                                  */
/* Ported to CREXX by Adrian Sutherland 2024                        */
/*------------------------------------------------------------------*/

#include <stdio.h>
#include <string.h>
#include "crexxpa.h"    // crexx/pa - Plugin Architecture header file
#include "desbase.h"    // DES encryption/decryption functions

// Encrypt a string using DES - the first argument is the key, the second the data
PROCEDURE(RxDesEncrypt)
{
    char   des_out[8];
    char   des_in[8];
    char   key[8];
    char   result[17];

    if( NUM_ARGS != 2)
        RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "2 arguments expected")

    if (strlen(GETSTRING(ARG(0))) != 16 || strlen(GETSTRING(ARG(1))) != 16)
        RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Both arguments must be 16 hex digits")

    if( hex2bin(GETSTRING(ARG(0)), key) < 0)
        RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Key is not valid hex")

    if( hex2bin(GETSTRING(ARG(1)), des_in) < 0)
        RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Data is not valid hex")

    // Encrypt
    desinit(key);
    endes(des_in, des_out);

    // Set return and make sure the signal is reset/ok
    bin2hex(des_out, result);
    SETSTRING(RETURN, result);
    RESETSIGNAL
}

// Decrypt a string using DES - the first argument is the key, the second the data
PROCEDURE(RxDesDecrypt)
{
    char   des_out[8];
    char   des_in[8];
    char   key[8];
    char   result[17];

    if( NUM_ARGS != 2)
        RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "2 arguments expected")

    if (strlen(GETSTRING(ARG(0))) != 16 || strlen(GETSTRING(ARG(1))) != 16)
        RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Both arguments must be 16 hex digits")

    if( hex2bin(GETSTRING(ARG(0)), key) < 0)
        RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Key is not valid hex")

    if( hex2bin(GETSTRING(ARG(1)), des_in) < 0)
        RETURNSIGNAL(SIGNAL_INVALID_ARGUMENTS, "Data is not valid hex")

    // Decrypt
    desinit(key);
    dedes(des_in, des_out);

    // Set return and make sure the signal is reset/ok
    bin2hex(des_out, result);
    SETSTRING(RETURN, result);
    RESETSIGNAL
}

// Functions to be provided to rexx - these are loaded either when the plugin is loaded (dynamic) or
// before main() is called (static)
LOADFUNCS
//      C Function__, REXX namespace & name, Option_, Return Type_, Arguments
ADDPROC(RxDesDecrypt, "rxdes.decrypt",       "b",     ".string",    "key=.string,data=.string");
ADDPROC(RxDesEncrypt, "rxdes.encrypt",       "b",     ".string",    "key=.string,data=.string");
ENDLOADFUNCS

// End of file

The following shows how the defined functions are published to \crexx{}.

// Functions to be provided to rexx \- these are loaded either when the*  
// plugin is loaded (dynamic) or before main() is called (static)*  
LOADFUNCS  
//      C Function\_\_, REXX namespace & name, Option\_, Return Type\_,*   
// Arguments*  
ADDPROC (RxDesDecrypt, "rxdes.decrypt",       "b",     ".string",     
   "key=.string,data=.string");  
ADDPROC (RxDesEncrypt, "rxdes.encrypt",       "b",     ".string",      
   "key=.string,data=.string");  
ENDLOADFUNCS**

The ADDPROC Macro published the function to \crexx{}. This has the namespace and name, options/level (should be b), the function return type (in level b format) and the arguments (again in level b format). This allows the Compiler and VM to search and “fix-up” procedure calls using the same search order and syntax as for procedures developed in REXX.

Macros

The following MACROs are provided for plugin developers (defined in rexxpa.h)

Macro Purpose
LOADFUNCS Starts and finishes the block of ADDFUNC()’s
ADDFUNC() Published a function to \crexx{}
NUM_ARGS Is the number of arguments passed the the function
ARG() Returns the nth argument (which is an opaque pointer to the \crexx{} register)
RETURN Returns the register used to pass the function’s returned value.
GETSTRING() Gets the String value of a register
SETSTRING() Sets the String value of a register
GETINT() Gets the Integer value of a register
SETINT() Sets the Integer value of a register
GETFLOAT() Gets the float (double) value of a register
SETFLOAT() Sets the float (double) value of a register
SIGNAL Returns the registers used to pass and Signal Information.
RESETSIGNAL Ensures that the signal register is set no “no signal”
RETURNSIGNAL() Sets the signal register and returns from the function. Used for error conditions.
ENDLOADFUNCS Starts and finishes the block of ADDFUNC()’s

The Signal values are:

Signal Purpose    
SIGNAL_NONE      
SIGNAL_ERROR Syntax error SIGNAL_OVERFLOW_UNDERFLOW numeric overflow or underflow
SIGNAL_CONVERSION_ERROR conversion error between types    
SIGNAL_UNKNOWN_INSTRUCTION unknown RSAS instruction    
SIGNAL_FUNCTION_NOT_FOUND attempt to execute an unknown function    
SIGNAL_OUT_OF_RANGE attempt to access an array element that is out of range    
SIGNAL_FAILURE error in an external function or subroutine    
SIGNAL_HALT an external request to halt execution    
SIGNAL_NOTREADY IO error, such as a file not being ready    
SIGNAL_INVALID_ARGUMENTS invalid arguments are passed to a function    
SIGNAL_OTHER Other (or unknown) error condition    

In addition

Build Script

The following example shows a CMake script (CMakeLists.txt) to build a plugin. It uses a provided CMake function add_plugin_target from RXPluginFunction.cmake.

cmake_minimum_required(VERSION 3.5)
project(rxdes C)

set(CMAKE_C_STANDARD 90)

# Including RXPlugin Build System
include(${CMAKE_SOURCE_DIR}/rxpa/RXPluginFunction.cmake)

# Create module
add_dynamic_plugin_target(des rxdes.c desbase.c desbase.h)

This script builds a dynamic version of the plugin, and supports Windows (mingw and VS), OSX and Linux.

Static Builds

The \crexx{} Cmake build configuration should be referenced for static build support. Building the static library is simple, but proper linking is crucial to guarantee that the linker recognizes the need to link in the plugin and that the plugin’s initialization function is called at program load across various platforms.

Execution from REXX

The following is a REXX Level B example using the plugin.

Note that there is no manual loading of the dynamic or static plugin, instead \crexx{} loads the plugins using the same search rules as it uses for other REXX modules. This means that the REXX program (or programmer) does not need to be concerned about how the external function is provided - REXX, Native, Dynamic, Static - it all has the same calling syntax. This is designed to meet the objective to simplify programming. ```rexx options levelb /* This is a rexx level b program */

import rxfnsb /* Import the crexx level B functions */
import rxdes /* Import the rxdes plugin functions */

/* Note that the input and output to the des functions are in hex strings */

Plaintext = “0000000000000000”
key = “08192A3B4C5D6E7F”

Ciphertext = Encrypt(key,Plaintext) ``` In line 4, the import statement loads the namespace meaning that any REXX modules or native plugins in the rxdes namespace will be loaded as needed; the function call, encrypt(), follows REXX syntax, and the compiler can check parameters and return types as normal.

Future Changes