Building an External Native Library

External libraries written in compiled languages (typically C or C++) provide a means to interface Rexx programs with other subsystems intended for compiled languages. These libraries are packaged as Dynamic Link Libraries on Windows or shared libraries on Unix-based systems. A named library can be loaded using the ::REQUIRES directive, the loadLibrary() method on the Package class, or by using the EXTERNAL keyword on a ::ROUTINE or ::METHOD directive.

When the library is loaded, the interpreter searches for an entry point in the library named RexxGetPackage(). An external library package is required to provide a RexxGetPackage() function that returns a pointer to the descriptor structure defining the methods and routines contained within the library. The RexxGetPackage() routine takes no arguments and has a RexxPackageEntry *return value. This is normally created using the OOREXX_GET_PACKAGE() macro defined in the oorexxapi.h include file.

// package loading stub.
OOREXX_GET_PACKAGE(package);

Where package is the name of the RexxPackageEntry table for this library. The package entry table is a descriptor contained within the library. Note that on Windows, it is necessary to explicitly export the RexxPackageEntry() function when the library is linked. This is the only name you are required to export. Calls are made to the library routines and methods using addresses stored in the RexxPackageEntry table.

The RexxPackageEntry structure contains information about the package and descriptors of any methods and/or routines defined within the package. The structure looks like this:

typedef struct _RexxPackageEntry
{
    int size;                      // size of the structure...helps compatibility
    int apiVersion;                // version this was compiled with
    int requiredVersion;           // minimum required interpreter version (0 means any)
    const char *packageName;       // package identifier
    const char  *packageVersion;   // package version #
    RexxPackageLoader loader;      // the package loader
    RexxPackageUnloader unloader;  // the package unloader
    struct _RexxRoutineEntry *routines; // routines contained in this package
    struct _RexxMethodEntry *methods;   // methods contained in this package
} RexxPackageEntry;

The fields in the RexxPackageEntry have the following functions:

size and apiVersion

these fields give the size of the received table and identify the interpreter level this library has been compiled against. These indicators will allow additional information to be added to the RexxPackageEntry in the future without causing compatibility issues for older libraries. Normally, these two fields are defined using the STANDARD_PACKAGE_HEADER macro, which sets both values.

requiredVersion

a library can specify the minimum interpreter level it requires. The interpreter will only load libraries that match the minimum compatibility requirement of the library package. A zero value in this field means there's no minimum level requirement. The macro REXX_CURRENT_INTERPRETER_VERSION will set the level of interpreter you are compiling against. If REXX_CURRENT_INTERPRETER_VERSION is specified, then the library package will not load with older releases. The API header files will be updated with a macro for each interpreter version. The version macros are of the form REXX_INTERPRETER_version_level_revision, where version, level, and revision refer to the corresponding values in an interpreter release number. For example, REXX_INTERPRETER_4_0_0 would indicate that the 4.0.0 interpreter level is the minimum this library requires.

packageName

a descriptive name for this library package.

packageVersion

a version string for this package. The version can be in whatever form is appropriate for the package.

packageLoader

a function that will be called when the library package is first loaded by the interpreter. The package loader function is passed a RexxThreadContext pointer, which will give the package access to Rexx interpreter services at initialization time. The package loader is optional and is indicated by a NULL value in the descriptor.

packageUnloader

a function that will be called when the library package is unloaded by the interpreter. The unloading process happens when the last interpreter instance is destroyed during the last cleanup stages. This gives the loaded library an opportunity to clean up any global resources such as cached Rexx object references. The package loader is optional and is indicated by a NULL value in the descriptor.

routines

a pointer to an array of RexxRoutineEntry structures that define the routines provided by this package. If there are no routines, this field should be NULL. See Defining Library Routines for details on creating the exported routine table.

method

a pointer to an array of RexxMethodEntry structures that define the methods provided by this package. If there are no methods, this field should be NULL. See Defining Library Methods for details on creating the exported method table.

Here is an example of a RexxPackageEntry table taken from the rxmath library package:

// now build the actual entry list
RexxRoutineEntry rxmath_functions[] =
{
    REXX_TYPED_ROUTINE(MathLoadFuncs, MathLoadFuncs),
    REXX_TYPED_ROUTINE(MathDropFuncs, MathDropFuncs),
    REXX_TYPED_ROUTINE(RxCalcPi,      RxCalcPi),
    REXX_TYPED_ROUTINE(RxCalcSqrt,    RxCalcSqrt),
    REXX_TYPED_ROUTINE(RxCalcExp,     RxCalcExp),
    REXX_TYPED_ROUTINE(RxCalcLog,     RxCalcLog),
    REXX_TYPED_ROUTINE(RxCalcLog10,   RxCalcLog10),
    REXX_TYPED_ROUTINE(RxCalcSinH,    RxCalcSinH),
    REXX_TYPED_ROUTINE(RxCalcCosH,    RxCalcCosH),
    REXX_TYPED_ROUTINE(RxCalcTanH,    RxCalcTanH),
    REXX_TYPED_ROUTINE(RxCalcPower,   RxCalcPower),
    REXX_TYPED_ROUTINE(RxCalcSin,     RxCalcSin),
    REXX_TYPED_ROUTINE(RxCalcCos,     RxCalcCos),
    REXX_TYPED_ROUTINE(RxCalcTan,     RxCalcTan),
    REXX_TYPED_ROUTINE(RxCalcCotan,   RxCalcCotan),
    REXX_TYPED_ROUTINE(RxCalcArcSin,  RxCalcArcSin),
    REXX_TYPED_ROUTINE(RxCalcArcCos,  RxCalcArcCos),
    REXX_TYPED_ROUTINE(RxCalcArcTan,  RxCalcArcTan),
    REXX_LAST_ROUTINE()
};

RexxPackageEntry rxmath_package_entry =
{
    STANDARD_PACKAGE_HEADER
    REXX_INTERPRETER_4_0_0,              // anything after 4.0.0 will work
    "RXMATH",                            // name of the package
    "4.0",                               // package information
    NULL,                                // no load/unload functions
    NULL,
    rxmath_functions,                    // the exported functions
    NULL                                 // no methods in rxmath.
};

// package loading stub.
OOREXX_GET_PACKAGE(rxmath);

Defining Library Routines

The RexxRoutineEntry table defines routines that are exported by a library package. This table is an array of RexxRoutineEntry structures, terminated by an entry that contains nothing but zero values in the fields. The REXX_LAST_ROUTINE() macro will generate a suitable table terminator entry.

The remainder of the table will be entries generated via either the REXX_CLASSIC_ROUTINE() or REXX_TYPED_ROUTINE() macros. REXX_CLASSIC_ROUTINE() entries are for routines created using the older string-oriented function style. The classic routines allow packages to be migrated to the new package loading system without requiring a rewrite of all of the contained functions. See External Function Interface for details on creating the functions in the classic style.

Routine table entries defined using REXX_TYPED_ROUTINE() use the new object-oriented interfaces for creating routines. These routines can use the interpreter runtime to convert call arguments from Rexx objects into primitive types and return values converted from primitive types back into Rexx objects. These routines are also given access to a rich set of services through the RexxCallContext interface vector.

The REXX_CLASSIC_ROUTINE() and REXX_TYPED_ROUTINE() macros take two arguments. The first entry is the package table name for this routine. The second argument is the entry point name of the real native code routine that implements the function. These names are frequently the same, but need not be. The package table name is the name this routine will be called with from Rexx code.

Smaller function packages frequently place all of the contained functions and the package definition tables in the same file, with the package tables placed near the end of the source file so all of the functions are visible. For larger packages, it may be desirable to place the functions in more than one source file. For functions packaged as multiple source files, it is necessary to create prototype declarations so the routine entry table can be generated. The oorexxapi.h header file includes REXX_CLASSIC_ROUTINE_PROTOTYPE() and REXX_TYPED_ROUTINE_PROTOTYPE() macros to generate the appropriate declarations. For example,

// create function declarations for the linker
REXX_TYPED_ROUTINE_PROTOTYPE(RxCalcPi);
REXX_TYPED_ROUTINE_PROTOTYPE(RxCalcSqrt);

// now build the actual entry list
RexxRoutineEntry rxmath_functions[] =
{
    REXX_TYPED_ROUTINE(RxCalcPi,      RxCalcPi),
    REXX_TYPED_ROUTINE(RxCalcSqrt,    RxCalcSqrt),
    REXX_LAST_ROUTINE()
};

Routine Declarations

Library routines are created using a series of macros that create the body of the function. These macros define the routine arguments and return value in a form that allows the Rexx runtime to perform argument checking and conversions before calling the target routine. These macros are named "RexxRoutinen, where n is the number of arguments you wish to be passed to your routine. For example,

RexxRoutine2(int, beep, wholenumber_t, frequency, wholenumber_t, duration)
{
    return Beep(frequency, duration);  /* sound beep                 */
}

defines a beep routine that will be passed two wholenumber_t arguments (frequency and duration). The return value is an int value.

An argument can be made optional by prefixing the type with "OPTIONAL_". For example,

RexxRoutine2(int, beep, wholenumber_t, frequency, OPTIONAL_wholenumber_t, duration)
{
    return Beep(frequency, duration);  /* sound beep                 */
}

would define a routine that takes two arguments. The first argument is required, but the second is optional. Any optional arguments, when omitted on a call, will be passed using a zero value appropriate to the type. The macros argumentExists(n) or argumentOmitted(n) can reliably test if an argument was passed. For example, argumentExists(2) tests if the duration argument was specified when beep() was called. The n value is origin 1.

In addition to the arguments passed by the caller, there are some special argument types that provide your routine with additional information. These special types will add additional arguments to your native routine implementation. The argument value specified with argumentExists() or argumentOmitted() maps to the arguments passed to your C++ routine rather than the arguments in the originating Rexx call. See Routine ArgumentTypes for details on the special argument types.

All routine declarations have an undeclared special argument passed to the routine. This special argument is named context. The context is a pointer to a RexxCallContext value and provides access to all APIs that are valid from a routine context.

Note: void is not a valid return type for a routine. There must be a real return type specified on the routine declaration. If you wish to have a routine without a return value, declare the routine with a return type of RexxObjectPtr and return the value NULLOBJECT. Routines that do not return a real value may not be invoked as functions. Only the CALL instruction allows a return without a value.

Routine Argument Types

A routine argument or return value may be a numeric type or an object type. For numeric types, the call arguments must be convertible from a Rexx object equivalent into the primitive value or an error will be raised. For optional numeric arguments, a zero value is passed for omitted values. When used as a return type, the numeric values are translated into an appropriate Rexx object value.

If an argument is an object type, some additional validation is performed on the arguments being passed. If an argument does not meet the requirements for a given object type, an error will be raised. If an object-type argument is optional and a value is not specified on the call, the value NULLOBJECT is passed to your routine. The supported object types and the special processing rules are as follows:

RexxObjectPtr

a reference to any Rexx object instance. Any arbitrary object type may be passed for a RexxObjectPtr argument.

RexxStringObject

an instance of the Rexx String class. The argument value must be a Rexx String value or convertible to a Rexx String value using the request('String') mechanism.

RexxArrayObject

An instance of a Rexx single-dimension array.

RexxClassObject

An instance of Rexx Class class.

RexxStemObject

An instance of Rexx Stem class. For routine calls, a stem argument may be specified either using the stem variable name directly or giving the stem variable name as a quoted string. For example, for a routine defined using

RexxRoutine1(int, MyRoutine, RexxStemObject, stem)

the following calls are equivalent:

x = MyRoutine(a.)
x = MyRoutine('a.')

This special processing allows routines that currently access stem variables using the RexxVariablePool API to be more easily converted to the more capable API set.

In addition to the numeric and object types, there are additional special types that provide additional information to the calling routine or perform common special conversions on argument values. The special types available to routines are:

CSTRING

The argument is passed as an ASCII-Z string. The source argument must be one that is valid as a RexxStringObject value. The RexxStringObject is converted into a pointer to an ASCII-Z string. This is equivalent to the value returned from the StringValue() API from a RexxStringObject value. For an optional CSTRING argument, a NULL pointer is provided when the argument is omitted.

When CSTRING is used as a return value, the ASCII-Z string value will be converted into a Rexx String object. CSTRING return values are best confined to returning C literal values. The Rexx runtime does not free any memory associated with a CSTRING return value, so care must be taken to avoid memory leaks. Also, locally declared character buffers cannot be returned as the storage associated with buffer is no longer valid once your routine returns to the Rexx interpreter. For example, the following is not valid:

RexxRoutine0(CSTRING, MyRoutine)
{
   ....
   char buffer[32];
   sprintf(buffer, "%d:%d", major, minor);
   return buffer;     // buffer is not valid once return executes
}

A RexxStringObject return value and the String() API is more appropriate in this situation.

RexxRoutine0(RexxStringObject, MyRoutine)
{
   ....
   char buffer[32];
   sprintf(buffer, "%d:%d", major, minor);
   return context->String(buffer);     // creates a string object and returns it.
}
POINTER

an "unwrapped" Pointer or Buffer string object. If the argument is a Pointer object, the wrapped pointer value is returned as a void * value.. If the argument is a Buffer object, then a pointer to the buffer's data area is returned. A NULL pointer is returned for an omitted OPTIONAL_POINTER argument.

When POINTER is used as a routine return value, any pointer value can be returned. The Rexx runtime will wrap the pointer value in a Rexx Pointer object.

POINTERSTRING

a pointer value that has been encoded in string form. The string value must be in the format "0xnnnnnnnn", where the digits are valid hexadecimal digits. On 64-bit platforms, the pointer value must be 16 digits long. The string value is converted into a void * value. A NULL pointer is returned for an omitted optional POINTERSTRING argument.

When POINTERSTRING is used as a routine return value, any pointer value can be returned. The Rexx runtime will convert the pointer value back into an encoded string value.

NAME

The name of the invoked routine, passed as a CSTRING. NAME is not valid as a return value.

ARGLIST

A RexxArrayObject containing all arguments passed to the routine. This is equivalent to using Arg(1, 'A') from Rexx code. The returned array contains all of the routine arguments that were specified in the original call. Omitted arguments are empty slots in the returned array. In addition, if a routine has an ARGLIST argument specified, the normal check for the maximum number of arguments is bypassed. This makes possible routines with an open-ended number of arguments. ARGLIST is not valid as a return value.

Defining Library Methods

The RexxMethodEntry table defines method that are exported by a library package. This table is an array of RexxMethodEntry structures, terminated by an entry that contains nothing but zero values in the fields. The REXX_LAST_METHOD() macro will generate a suitable table terminator entry.

The remainder of the table will be entries generated via the REXX_TYPED_METHOD() macro. Routine table entries defined using REXX_TYPED_METHOD() use the object-oriented interfaces for creating methods that can be defined on Rexx classes. These methods can use the interpreter runtime to convert call arguments from Rexx objects into primitive types and return values from primitive types back into Rexx objects. Native methods are also given access to a rich set of services via the RexxMethodContext interface vector.

The REXX_TYPED_METHOD() macro take two arguments. The first entry is the package table name for this method. The second argument is the entry point name of the real native code method that implements the function. These names are frequently the same, but need not be.

Smaller function packages frequently place all of the contained functions and the package definition tables in the same file, with the package tables placed near the end of the source file so all of the methods are visible. For larger packages, it may be desirable to place the methods in more than one source file. For libraries packaged as multiple source files, it is necessary to create a prototype declarations so the method entry table can be generated. The oorexxapi.h header file includes a REXX_TYPED_METHOD_PROTOTYPE() macro to generate the appropriate declarations. For example,

// create function declarations for the linker
REXX_TYPED_METHOD_PROTOTYPE(point_init);
REXX_TYPED_METHOD_PROTOTYPE(point_add);

// now build the actual entry list
RexxMethodEntry point_methods[] =
{
    REXX_TYPED_METHOD(point_init, point_init),
    REXX_TYPED_METHOD(point_add,  point_add),
    REXX_LAST_METHOD()
};

Method Declarations

Library methods are created using a series of macros that create the body of the method. These macros define the method arguments and return value in a form that allows the Rexx runtime to perform argument checking and conversions before calling the target method. These macros are named "RexxMethodn, where n is the number of arguments you wish to be passed to your method. For example,

RexxMethod2(int, beep, wholenumber_t, frequency, wholenumber_t, duration)
{
    return Beep(frequency, duration);  /* sound beep                 */
}

defines a beep method that will be passed two wholenumber_t arguments (frequency and duration). The return value is an int value.

An argument can be made optional by prefixing the type with "OPTIONAL_". For example,

RexxMethod2(int, beep, wholenumber_t, frequency, OPTIONAL_wholenumber_t, duration)
{
    return Beep(frequency, duration);  /* sound beep                 */
}

would define a method that takes two arguments. The first argument is required, but the second is optional. Any omitted optional arguments will be passed using a zero value appropriate for the type. The macros argumentExists(n) or argumentOmitted(n) can reliably test if an argument was passed. For example, argumentExists(2) tests if the duration argument was specified when calling the beep() method. The n value is origin 1.

In addition to the arguments passed by the caller, there are some special argument types that provide your routine with additional information. These special types will add additional arguments to your native routine implementation. The argument position specified with argumentExists() or argumentOmitted() maps to the arguments passed to your C++ routine rather than the arguments in the originating Rexx call. See below for details on the special argument types.

All method declarations have an undeclared special argument passed to the routine. This special argument is named context. The context is a pointer to a RexxMethodContext value and provides access to all APIs valid from a method context.

Note: void is not a valid return type for a method. There must be a real return type specified on the method declaration. If you wish to have a method without a return value, declare the method with a return type of RexxObjectPtr and return the value NULLOBJECT. Methods that do not return a real value may not be invoked within expression, but must be used as standalone message instructions.

Method Argument Types

A method argument or return value may be a numeric type or an object type. For numeric types, the arguments must be convertible from a Rexx object equivalent into the primitive value or an error will be raised. For optional numeric arguments, a zero value is passed for omitted values. When used as a return type, the numeric values are translated into an appropriate Rexx object value.

If an argument is an object type, some additional validation is performed on the arguments being passed. If an argument does not meet the requirements for a given object type, an error will be raised. If an object-type argument is optional and a value is not specified on the call, the value NULLOBJECT is passed to your routine. The supported object types and the special processing rules are as follows:

RexxObjectPtr

a reference to any Rexx object instance. Any arbitrary object type may be passed for a RexxObjectPtr argument.

RexxStringObject

an instance of the Rexx String class. The argument value must be a Rexx String value or convertible to a Rexx String value using the request('String') mechanism.

RexxArrayObject

An instance of a Rexx single-dimension array.

RexxClassObject

An instance of Rexx Class class.

RexxStemObject

An instance of Rexx Stem class. To pass a Stem to a method, a stem argument must be specified using a stem variable name directly. For example, for a method defined using

RexxMethod1(int, MyMethod, RexxStemObject, stem)

the following call passes a stem object associated with a stem variable to the method:

x = o~myMethod(a.)

In addition to the numeric and object types, there are additional special types that provide additional information to the calling routine or perform common special conversions on argument values. The special types available to routines are:

CSTRING

The argument is passed as an ASCII-Z string. The source argument must be one that is valid as a RexxStringObject value. The RexxStringObject is converted into a pointer to an ASCII-Z string. This is equivalent to the value returned from the StringValue() API from a RexxStringObject value. For an optional CSTRING argument, a NULL pointer is provided when the argument is omitted.

When CSTRING is used as a return value, the ASCII-Z string value will be converted into a Rexx String object. CSTRING return values are best confined to returning C literal values. The Rexx runtime does not free any memory associated with a CSTRING return value, so care must be taken to avoid memory leaks. Also, locally declared character buffers cannot be returned as the storage associated with buffer is no longer valid once your method returns to the Rexx interpreter. For example, the following is not valid:

RexxMethod0(CSTRING, MyMethod)
{
   ....
   char buffer[32];
   sprintf(buffer, "%d:%d", major, minor);
   return buffer;     // buffer is not valid once return executes
}

A RexxStringObject return value and the String() API is more appropriate in this situation.

RexxMethod0(RexxStringObject, MyMethod)
{
   ....
   char buffer[32];
   sprintf(buffer, "%d:%d", major, minor);
   return context->String(buffer);     // creates a string object and returns it.
}
POINTER

an "unwrapped" Pointer or Buffer string object. If the argument is a Pointer object, the wrapped pointer value is returned as a void * value.. If the argument is a Buffer object, then a pointer to buffer's storage area is returned. A NULL pointer is returned for an omitted optional POINTER argument.

When POINTER is used as a method return value, any pointer value can be returned. The Rexx runtime will wrap the pointer value in a Rexx Pointer object.

POINTERSTRING

a pointer value that has been encoded in string form. The string value must be in the format "0xnnnnnnnn", where the digits are valid hexadecimal digits. On 64-bit platforms, the pointer value must be 16 digits long. The string value is converted into a void * value. A NULL pointer is returned for an omitted optional POINTERSTRING argument.

When POINTERSTRING is used as a method return value, any pointer value can be returned. The Rexx runtime will convert the pointer value back into an encoded string value.

NAME

The name of the invoked method, passed as a CSTRING. This is the message name that was used to invoke the method. NAME is not valid as a return value.

ARGLIST

A RexxArrayObject containing all arguments passed to the method. This is equivalent to using Arg(1, 'A') from Rexx code. The returned array contains all of the method arguments that were specified in the original call. Omitted arguments are empty slots in the returned array. In addition, if a method has an ARGLIST argument specified, the normal check for the maximum number of arguments is bypassed. This makes possible methods with an open-ended number of arguments. ARGLIST is not valid as a return value.

OSELF

A RexxObjectPtr containing a reference to the object that was the message target for the current method. This is equivalent to the SELF variable that is available in Rexx method code. OSELF is not valid as a return value.

SUPER

A RexxClassObject containing a reference to the super scope object for the current method. This is equivalent to the SUPER variable that is set in Rexx method code. SUPER is not valid as a return value.

SCOPE

A RexxObjectPtr containing a reference to the current method's owning scope. This is normally the class that defined the method currently being executed. SCOPE is not valid as a return value.

CSELF

CSELF is a special argument type used for classes to store native pointers or structures inside an object instance. When a CSELF type is encountered, the runtime will search all of the object's variable scopes searching for a variable named CSELF. If a CSELF variable is located and the value is an instance of either the Pointer or Buffer class, the POINTER value will be passed to the method as a void * value. Objects that rely on CSELF values typically set the variable CSELF inside an init method for the object. For example:


RexxMethod2(RexxObjectPtr, stream_init, OSELF, self, CSTRING, name)
{
    // create a new stream info member
    StreamInfo *stream_info = new StreamInfo(self, name);
    RexxPointerObject streamPtr = context->NewPointer(stream_info);
    context->SetObjectVariable("CSELF", streamPtr);

    return NULLOBJECT;
}

Then, within a method for the object, the CSELF variable is used as an argument to the method, the void * is retrieved and cast to the correct type:

RexxMethod3(size_t, stream_charout, CSELF, streamPtr, OPTIONAL_RexxStringObject, data, OPTIONAL_int64_t, position)
{
    StreamInfo *stream_info = (StreamInfo *)streamPtr;
    stream_info->setContext(context, context->False());

    ...
}

CSELF is not valid as a return value.

Pointer, Buffer, and CSELF

Methods written in C++ frequently need to acquire access to data that is associated with an object instance. ooRexx provides two classes, Buffer and Pointer, that allow these associations to be made. Both classes are real Rexx classes that can be passed as arguments, returned as method results, and assigned to object instance variables. For the Rexx programmer who might encounter one of these instances, these are opaque objects that don't appear to be of much use. To the native library writer, the usefulness derives from what's stored inside these objects.

The Buffer class

The Buffer class allows the library writer to allocate blocks of memory from the Rexx object space. The memory is a part of the Buffer object instance, and will be reclaimed automatically when the Buffer object is garbage collected. This means the programmer does not need to explicitly release a Buffer object, this is handled automatically. It does, however, require that steps be taken to protect the Buffer object from garbage collection while it is still needed. The usual protection mechanism it to store the buffer object in an object instance variable using SetObjectVariable(). Once assigned to a variable, the Buffer is protected from garbage collection until its associated object instance is also reclaimed. The buffer is part of the persistent state of the the object.

Buffer objects are allocated using the NewBuffer() that's part of the RexxThreadContext interface. Once created, you access the Buffer's data area using BufferData(), which returns a pointer to the beginning of the data buffer. The data buffer area is writeable storage, into which any data may be placed. This is frequently used to allocate a C++ struct or class instance that is the native embodiment of the class implementation. For example

RexxMethod0(RexxObjectPtr, myclass_init)
{
    // create a buffer for my internal data.
    RexxBufferObject data = context->NewBuffer(sizeof(MyDataClass));
    // store this someplace safe
    context->SetObjectVariable("MYDATA", data);
    // get access to the data area
    void *dataPtr = context->BufferData(data);
    // construct a C++ object to place in the buffer
    MyDataClass *myData = new (dataPtr) MyDataClass();
    // initialize the data below
    ...

    return NULLOBJECT;
}

This example allocates a Buffer object instance, creates a C++ class in its data area, and stores a reference to the Buffer in the MYDATA object variable. Other C++ methods can access this instance by using the C++ equivalent to the Rexx EXPOSE instruction.

RexxMethod0(RexxObjectPtr, myclass_dosomething)
{
    // retrieve my instance buffer
    RexxBufferObject data = (RexxBufferObject)context->GetObjectVariable("MYDATA");
    // Get the data pointer and cast it back to my class type
    MyDataClass *myData = (MyDataClass *)context->BufferData(data);
    // perform the operation below
    ...
}

Since Buffer object instances are reclaimed automatically when the object is gabage collected, no additional steps are required to cleanup that memory. However, if there are additional dymanically allocated resources associated with the Buffer, such as pointers to system allocated resources or dynamically allocated memory, it may be necessary to add an UNINIT method to your class to ensure the resources are not leaked.

RexxMethod0(RexxObjectPtr, myclass_uninit)
{
    // retrieve my instance buffer
    RexxBufferObject data = context->GetObjectVariable("MYDATA");
    // Get the data pointer and cast it back to my class type
    MyDataClass *myData = (MyDataClass *)context->BufferData(data);
    // delete any resources I've obtained (but not the MyDataClass
    // instance itself
    delete ((void *)myData) myData;
}

The Pointer class

The Pointer class has similar uses as the Buffer class, but Pointer instances only hold a single pointer value to native C/C++ resources. A Pointer instance is effectively a Buffer object where the buffer data area is a single void * pointer. Like Buffer objects, Pointers can be stored in Rexx variables and retrieved in native methods. Pointer object instances are garbage collected just like Buffer objects, but when a Pointer is reclaimed, whatever values refererenced by the Pointer instance are not cleaned up. If additional cleanup is required, then it will be necessary to implement an UNINIT method to handle the cleanup. Here are the Buffer examples above reworked for the Pointer class:

RexxMethod0(RexxObjectPtr, myclass_init)
{
    // construct a C++ object to associate with the object
    MyDataClass *myData = new MyDataClass();
    // create a Pointer to store this in the object
    RexxPointerObject data = context->NewPointer(myData);
    // store this someplace safe
    context->SetObjectVariable("MYDATA", data);
    // initialize the data below
    ...

    return NULLOBJECT;
}


RexxMethod0(RexxObjectPtr, myclass_dosomething)
{
    // retrieve my instance data
    RexxPointerObject data = (RexxPointerObject)context->GetObjectVariable("MYDATA");
    // Get the data pointer and cast it back to my class type
    MyDataClass *myData = (MyDataClass *)context->PointerValue(data);
    // perform the operation below
    ...
}


RexxMethod0(RexxObjectPtr, myclass_uninit)
{
    // retrieve my instance data
    RexxPointerObject data = (RexxPointerObject)context->GetObjectVariable("MYDATA");
    // Get the data pointer and cast it back to my class type
    MyDataClass *myData = (MyDataClass *)context->PointerValue(data);
    // delete the backing instance
    delete myData;
}

The POINTER method type

The Rexx runtime has some special support for Pointer and Buffer objects when they are passed as method arguments and also when used as return values. The RexxMethod macros used to define method instances support the POINTER special argument type. When an argument is defined as a POINTER, then the argument value must be either a Buffer object or a Pointer object. The Rexx runtime will automatically pass this argument to the native method as the Buffer BufferData() value or the Pointer PointerValue() value, thus removing the need to unwrapper these in the method code. The POINTER type is generally used for private methods of a class where the Rexx versions of the methods pass Pointer or Buffer references to the private native code. For example, the Rexx code might look like this:

::method setTitle
  expose title prefix handle
  use arg title
  // set the title to the title concatenated to the prefix
  self~privateSetTitle(handle, prefix title)

::method privateSetTitle PRIVATE EXTERNAL "LIBRARY mygui setTitle"

The corresponding C++ method would look like this:

RexxMethod2(RexxObjectPtr, setTitle, POINTER, handle, CSTRING, title)
{
    // the pointer object was unwrapped for me
    MyWindowHandle *myHandle = (MyWindowHandle *)handle;

    // other stuff here
}

When POINTER is used as a method return type, the runtime will automatically create a Pointer object instance that wrappers the returned void *value. The created Pointer instance is the result returned to the Rexx code.

The CSELF method type

There's one additional concept using Pointer and Buffer objects supported by the C++ APIs. When a method definition specifies the special type CSELF, the runtime will look for an object variable named CSELF. If the variable is found, and if the variable is assigned to an instance of Pointer or Buffer, then the corresponding data pointer is returned as the argument. The CSELF is most useful when just a single anchor to native C++ data is backing an object instance and the backing data is created in the object INIT method. Here's the Pointer example above reworked to use CSELF:

RexxMethod0(RexxObjectPtr, myclass_init)
{
    // construct a C++ object to associate with the object
    MyDataClass *myData = new MyDataClass();
    // create a Pointer to store this in the object
    RexxPointerObject data = context->NewPointer(myData);
    // assign this to the special CSELF variable
    context->SetObjectVariable("CSELF", data);
    // initialize the data below
    ...

    return NULLOBJECT;
}


RexxMethod1(RexxObjectPtr, myclass_dosomething, CSELF, cself)
{
    // We can just cast this to our data value
    MyDataClass *myData = (MyDataClass *)cself;
    // perform the operation below
    ...
}


RexxMethod1(RexxObjectPtr, myclass_uninit, CSELF, cself)
{
    // We can just cast this to our data value
    MyDataClass *myData = (MyDataClass *)cself;
    // delete the backing instance
    delete myData;
}

Using the CSELF argument type eliminates the need to directly access the Rexx variable used to anchor the value in every method except the INIT method. This produces generally smaller code and more reliable too since the runtime is managing the retrieval.

There are other advantages to using the CSELF convention. The example above is equivalent to the examples using Pointer and Buffer objects. If, however, you were to create a subclass of the Buffer example and try to access the value stored in MYDATA from a subclass method, you'll find that GetObjectVariable("MYDATA") will return NULLOBJECT. The GetObjectVariable() method retrieves variables from the current method's variable scope. Since the INIT method that set MYDATA originally and the subclass method that wishes to access the data are defined at different class scopes, GetObjectVariable() will access different variable pools and MYDATA will not be found. One solution would be to create a private attributed method in the base class:

::attribute mydata get private

The subclass method can then access the method using SendMessage0() to access the value.

RexxObjectPtr self = context->GetSelf()
RexxPointerObject = context->SendMessage0(self, "MYDATA");

The CSELF type handles this detail automatically. When used as an argument, all variable scopes of the object's class hiearchy are searched for a variable named CSELF. if one is located, it will be used for the value passed to the method. This allows all subclasses of a class using the CSELF convention to access the backing native data.

Frequently, one class instance might need access to the native information associated with another object instance. The other object instance might be of the same class or another class that is designed to interoperate with the current class. The ObjectToCSelf() allows the CSELF information for an object other than the current active object to be retieved.