Feedback Search Top Backward Forward
EDM/2

Building a REXX DLL in C

Written by Jorge Martins

Linkbar

 

Introduction

The purpose of this article is to show how to build a DLL in the C programming language containing functions that can be called from REXX.

A working knowledge of the C programming language is assumed as well as familiarity with a suitable development environment.

All the examples were implemented and tested using the Visual Age C++ for OS/2 version 3.0, but should be compatible with other C compilers, as long as the OS/2 Toolkit is properly installed.

Why?

Q: Why would anyone want to build a REXX DLL?
A: Because it is a powerful way to extend REXX with functions not present in the REXX library.

Q: Why can't these functions be written in REXX?
A: Because REXX is interpreted, thus slower than compiled C code and more importantly, some things can't be done in REXX like accessing the OS/2 API.

Q: What is special about a REXX DLL, in comparison with a "normal DLL"?
A: The functions have a specified return code, a specified parameter list and use a specified linking convention.

Q: How does one exchange values with the only REXX data type, the REXX string?
A: REXX strings map directly to a "typedefed" structure (RXSTRING).

Q: Is it easy to write a REXX DLL?
A: Yes.

REXX Strings

In order to exchange values between the REXX interpreter and C code, there's a structure (RXSTRING) which maps directly to REXX strings:


  typedef struct
  {
      ULONG           strlength;
      PCH             strptr;
  } RXSTRING;
The strlength member specifies the length of the string. This is necessary because REXX strings aren't NULL-terminated. The strptr is a pointer to the first character of the string.

A null string ("") has a strlength of 0 and a non-NULL strptr. An empty string has a NULL strptr. REXX routines return empty strings, REXX functions return non-empty strings.

Writing a REXX DLL

There's no better way to demonstrate how to create a REXX DLL than with real code. This is a REXX DLL, comprising one function used to calculate the CRC value of a file. Actually, the DLL has more than one function, but those are helper functions and will not be made available to the REXX interpreter directly.

The header file (rxcrcdll.h)

This is such a small project that a separate include file doesn't make much sense, but ... rxcrcdll.h.

The RETSTR_OK symbolic constant is 0 and it is used to return success. The RETSTR_INVALID symbolic constant must be different from 0 and represents failure.

The function we are going to call from REXX is declared as RexxFunctionHandler, which is "typedefed" in the rexxsaa.h header file as:


  typedef ULONG APIENTRY RexxFunctionHandler(PUCHAR,
                                             ULONG,
                                             PRXSTRING,
                                             PSZ,
                                             PRXSTRING);
This is what differentiates functions available to the REXX interpreter from regular functions. All the functions available to REXX must return an unsigned long int, must use system linkage and the exact argument list specified above.

The first argument points to the name of the calling function. The second argument contains the number of arguments passed to the REXX function. The third argument points to the first of a series of RXSTRINGs (the arguments passed from the REXX interpreter). The fourth argument points to the current queue name. Finally, the fifth argument specifies the RXSTRING returned to REXX.

Note that you can have as many functions you like in the DLL but the functions you wish to export to the REXX interpreter must be declared as above.

The main source (rxcrcdll.c)

The main source contains the definition of three functions. The one we'll export to the REXX interpreter and two helper functions. Notice that, since this is a DLL, there is no main function.

rxcrcdll.c


  #define     INCL_REXXSAA
  #include    <rexxsaa.h>
The rexxsaa.h header file is needed to build REXX DLLs since it contains the declaration of the RXSTRING, for example.

  if (numargs!=1)
    return RETSTR_INVALID;
The RxGetCrc function checks the numargs variable and returns an error if it is not one (the name of the file).

  CrcGenTable(crcTable);
This line generates a table necessary to compute the CRC value of the file.

  if ((fp=fopen(args[0].strptr,"rb"))==NULL)
  {
    strcpy(string,"ERROR");
  }
  else
  {
    crc=CrcGetCrc(fp,crcTable);
    sprintf(string,"%08lx",crc);
  }
This piece of code opens the file and assigns "ERROR" to an auxiliary string if we can't open the file. Otherwise it computes the CRC value of the file with the CrcGetCrc function, converts the numeric result to hexadecimal and then to a string.

At this point we have a NULL-terminated, C type string. We only have to assign its contents to the retstr, which is the RXSTRING returned to the caller REXX procedure.


  strcpy(retstr->strptr,string);
  retstr->strlength = strlen(string);
We set the length of the REXX string equal to the length of the temporary and then copy the actual contents of the string. The strptr member points to a pre-allocated memory area, so unless you are dealing with large strings, you don't need to allocate memory yourself.

  return RETSTR_OK;
If we survived this long, then everything must be OK and we return success.

Note that function returns error status with an unsigned long, and the actual REXX string through the last argument.

The module definition file (rxcrcdll.def)

Since we're building a DLL, we need to write a module definition file. This is nothing more than a series of instructions the linker needs to properly create the DLL, such as the names of the functions we wish to be called from other modules and so on.

rxcrcdll.def


  library rxcrcdll
This line instructs the linker to build a DLL named rxcrcdll.

  exports
    RXGETCRC = RxGetCrc
The exports statement is followed by the names of all the functions we wish to export. In this case, even if we exported the other functions (besides RxGetCrc) we wouldn't be able to call them from REXX since they do not follow the conventions. Note that we must uppercase the name of the function, otherwise REXX won't be able to call it.

This module definition file has nothing special because we're writing a REXX DLL. The only requirement is to uppercase the function names. In fact, most module definition files are a lot more complex that this one.

Compiling and linking

You could write a small makefile to build this DLL, but as it is composed by a very small number of files we'll compile and link directly in the command line:


  icc /Ge- rxcrcdll.c rxcrcdll.def
The /Ge- switch informs the linker we're building a DLL.

Creating a REXX procedure to test the DLL (rxcrc.cmd)

Okay, so were done with the DLL, let's try it!!!

rxcrc.cmd


  call rxfuncadd "RxGetCrc", "rxcrcdll", "RxGetCrc"
The rxfuncadd function registers our function, which, from this point on, will be available to the REXX procedure.

  crc=rxgetcrc(filename)
This line calls our function and stores its return value in the crc variable.

Notes

When you run the REXX procedure, the REXX interpreter loads the DLL and keeps it open until all command line sessions are close. So, if you need to rebuild the DLL it will fail because the DLL is open and can't be rewritten. This means, you'll have to close all command line sessions (so that the DLL is unloaded) to rebuild it.

Conclusion

REXX DLLs provide a powerful way to add functionality to REXX procedures, virtually any kind of functionality can be provided to a REXX procedure. They are also very easy to create. There's only one important (but simple) data type, the RXSTRING, and a set of rules to remember when declaring REXX functions.

 

Linkbar