Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Extending REXX with C++


SEP94: Extending REXX with C++

Extending REXX with C++

Combining REXX's ease of use with the power and flexibility of C++

Art Sulger

Art specializes in database administration, analysis, and programming for the State of New York. He can be contacted on CompuServe at 71020,435.


Windows programmers are used to using C or C++ to extend visual development tools. OS/2 developers haven't always had it so easy, however. It's only recently that a number of OS/2-targeted visual development tools have come onto the scene, most of which use OS/2's built-in REXX interpreter (see "A Multicolumn List-Box Container for OS/2," DDJ, May 1994). With tools such as Gpf's GpfRexx, HockWare's VisPro/REXX, and Watcom's VX-REXX, REXX can call routines written in other languages--as long as those languages can create DLLs.

I recently wrote a number of database routines in C++ for OS/2 Presentation Manager applications. I wanted to use visual development tools to build the user interface without recoding the database routines. Consequently, I extended my C++ classes into REXX-callable external functions. To illustrate this process, I'll develop, test, and debug a C++ class that displays text files. I'll then write an application using one of the visual Presentation Manager tools linked to the C++ class.

External REXX Functions

REXX allows programs to be written in a clear and structured way. With REXX, you must follow a rigid structure to write external functions that REXX can call; see Example 1. You do not directly pass these parameters; they are built by the REXX interpreter. The only parameters you can even indirectly control are argc and argv, which are built from the function arguments in your REXX program--just as main(int argc, char ** argv) in C is built by the command-line arguments.

REXX has no built-in data typing. The type is inferred from the context, so variables are passed as character strings. Unlike C, these strings are not null terminated so the structure includes the length as well as a pointer to the value. Example 2 is the structure from the REXXSAA.H file (supplied with the OS/2 Developer's Toolkit; you must include this file in your C module). For instance, suppose you included the commands in Example 3(a) in a REXX program. The C function that receives the command would look like Example 3(b). Notice that the fname argument is the argv[0].strptr parameter of the x->Open() call.

RXSTRING is also the structure you use to build the character string to return to the REXX command file. Interestingly, the C module is really returning values to two different processes--your REXX command file and the system's REXX interpreter. You notify the interpreter with the return statement, and it expects either 0 (VALID_ROUTINE) or 40 (INVALID_ROUTINE). An INVALID_RETURN halts program execution.

You supply the other return value at the address of the last *retstr argument passed into your function by the REXX interpreter. The value you pass back from your function is assigned to the special REXX variable RESULT; you can also assign it to a variable in your REXX program. In Example 4, say emits the value you have put into the retstr parameter in your FileDBNext function. Similarly, the string you build in FileDBError is interpreted as a numeric return code in the do while phrase. With the interpreter passing memory addresses from your REXX program to your C functions, you may be wondering, who owns this memory?

The return value (retstr) is allocated by REXX. The REXX interpreter allocates 256 bytes for return values when the process starts. This memory is owned by the process and is accessible by both your REXX program and C functions. In addition, any memory you allocate remains available to the process. The usual scoping rules apply, with the presumption that the REXX program and C functions are treated as a single module.

The return value that REXX allocates is slightly different than the memory allocations that you make. You don't have to worry about freeing it, but you do have to worry about not having enough. For instance, what happens if the value you want to return is greater than 256 bytes? After all, REXX imposes no limit on the length of the returned string. The function CopyResult (Listing One) checks to see if there is enough storage, and, if not, allocates more. CopyResult() uses DosAllocMem(), which handles memory in 4K chunks, so chances are you won't have to do too many allocations. This memory stays around until the process ends, or until the REXX program encounters a return statement that has no expression attached to it, in which case REXX drops (uninitializes) it.

Using C++

Except for the way C++ mangles names, it is relatively straightforward to extend these concepts to C++. The REXXSAA.H file should wrap the declarations in extern "C" directives (although some C++ implementations don't do this). If you get compile warnings about undefined pragmas or link errors listing unresolved externals, chances are the header file is obsolete. You can download the correct file (REXXSAA.ZIP) from CompuServe (GO IBMDF1).

Listing Two is a C++ class for reading text files. It opens files, reads lines sequentially, and performs some simple error checking. The class is a scaled-down version of the database class that I designed to use with the new REXX tools.

REXX development tools need only interface to the public methods of your C++ class. In the FileDB class these consist of: two constructors, of which one immediately opens a file and the other instantiates the class without a particular filename; a destructor, which simply calls the Close() method; Next(), which returns character strings from the file; and Error(), which returns the class status.

Constructors should go in one function and destructors in another. Your REXX procedures will then follow the scenario in Example 5. The constructor function will initialize the class (clss*x=new clss), and the destructor function will invoke the class destructor (delete x).

There are two methods of mapping the external function(s) to the public class members: You can write a separate function for each public method, and the REXX calls will look like Example 6(a); or, you can put all of the interface code in a single function, and the same REXX program will look like Example 6(b). Listing One uses both methods.

Telling REXX About New Functions

You have to notify the REXX environment about any new external functions written in a language other than REXX. You can use a built-in REXX function which you must use for at least one of your external functions; see Example 7(a). This loads the external routine Func1 located in RXFILEDB.DLL and tells REXX that it will be known to REXX by the name Func1. Another approach is for the C program to register external functions. Example 7(b) from RXFILEDB.CPP registers all the functions in a previously defined data structure.

The usual procedure is to build a LoadFunctions procedure in your DLL that will handle the bulk of the registrations. The REXX program need only register this single function, then call it. Once registered, the external functions are visible to all processes until OS/2 shuts down or the functions are deregistered. Usually you won't deregister functions, and often you may want to write a procedure that will automatically register your functions at startup.

The code does the registration in the body of the pseudoconstructor. This is acceptable to OS/2; there is no penalty for duplicate registrations.

Compiling, Linking, and Testing

Compiling and linking are straightforward if you know how to code an OS/2 DLL. You will need to link to the OS/2 Developer's Toolkit with the appropriate headers and the REXX.LIB file. These are supplied by the compiler vendor as part of the OS/2 support files. If you are using fopen() and similar functions, you should link to the multithreaded C run-time support libraries. Listing Three shows the compile and link commands for IBM C Set++ and Watcom.

During development, you may get an error while the DLL is being linked because the DLL you are testing may still be owned by the operating system. Make sure you end the process in which you are testing the DLL by exiting the window in which the testing is being done.

If you want to use your library from more than one application concurrently, direct the linker to provide a data segment for every process that invokes it. If your application uses any C run-time functions, tell the linker to initialize the library at each invocation. This is shown in the linker definition files in Listing Three for both IBM C Set++ and Watcom.

Testing DLLs can be a problem. You will first want to make sure your class works correctly. I've included test.cpp (see Listing Four) for just this purpose.

Once you are sure the C++ class works, you should test the REXX callable parts by writing a command file--a text file containing REXX commands. Command files must start with a C-style comment, and clauses are delimited by semicolons or line ends, unless the last character in a line is a comma (the continuation character). Your test program will make calls to the external functions which you have built. A call is made using the CALL keyword, or simply by writing the function. In the latter, the interpreter will execute the function, then attempt to execute the result.

The RXSTRING that you build in the RexxFunctionHandler to return a value is placed in a special variable, RESULT, which is set automatically during execution of a REXX program. The clause in Example 8(a) is equivalent to Example 8(b), which in turn is functionally identical to Example 8(c).

The sample program T.CMD (Listing Five) prompts for a filename, then lists it. The program starts with a menu to test concurrent invocations of the DLL. The menu allows you to select a file in more than one window, then start the listings almost simultaneously.

If the command file works correctly, then you are ready to use the DLL. If, on the other hand, you run into a bug, you can source-level debug DLLs called by REXX by debugging the REXX interpreter (cmd.exe). To do so with the C Set++ PM debugger, choose the Breakpoints/Load Occurrence option from the Disassembly window and type in the name of your DLL. The command for loading the debugger initially is IPMD cmd /K x:\dir\MyREXX.cmd.

A VX-REXX Application

Figure 1 shows two VX-REXX applications and one REXX command file (T.CMD), all using the RXFileDB DLL. The code generated is based on the screen design. The three applications are displaying three different listings. Listing Six provides only the additional code needed for the VX-REXX application. The FileDBStart pseudoconstructor call is in the Initialization routine of the REXX source, and the destructor is in the Quit routine. The File dialog in the Menu routine calls the remaining functions.

The ability to extend the new REXX development tools can provide a major boost in productivity. As you can see here, you can extend these tools even further using your existing library of C++ classes.

Figure 1 Two VX-REXX applications and one REXX command file (T.CMD).

Example 1: Prototype for C function callable by REXX.

ULONG _System    // The _System directive inhibits C++ name mangling
FileDBLoadFuncs(
CHAR *name,      // The name of this Function name.
ULONG argc,      // Number of arguments, as in main(int argc,...
RXSTRING argv[], // Arguments, as in main(int argc,char *argv[])
CHAR *q_name,    // Current Rexx queue name
RXSTRING *retstr)// The value that this function will return

Example 2: REXXSAA.H structure.

typedef struct _RXSTRING {     /* rxstr */
        ULONG  strlength;      /* length of string   */
        PCH    strptr;         /* pointer to string  */
}  RXSTRING;

Example 3: (a) Sample REXX program; (b) the C function called by the REXX program in (a).

(a) pull fname
    say FileDBOpen(fname)

(b) ULONG _System
    FileDBOpen(CHAR *name, ULONG argc, RXSTRING argv[],
           CHAR *q_name, RXSTRING *retstr)
      {

      char tmp[5] ;
      if( argc != 1 )
        return(INVALID_ROUTINE);
      if (x)
        {
        x->Open(argv[0].strptr) ; // argv[0].strptr == REXX fname
        strcpy(retstr->strptr, itoa(x->Error(), tmp, 10)) ;
        retstr->strlength = strlen(retstr->strptr) ;
        }
      else
        . . .
      return(VALID_ROUTINE);
      }

Example 4: Assigning a value to a variable in your REXX program.

do while FileDBError() = 0
say FileDBNext()

  . . .

Example 5: Constructors should go in one function and destructors in another.

/* typical REXX call to C++ */
call FileDBStart  /* the constructor */
do until FileDBError() = 0
  say FileDBNext()
end
call FileDBFinish /* the destructor */

Example 6: (a)Writing a separate function for each public method to map the external function to the public class members; (b) putting interface code in a single function to map the external function.

(a) call FileDBOpen("filename")
    if FileDBError() = 0 then do
      say FileDBNext()
      end

(b) call MyREXXDLL("Open", "filename")
    if MyREXXDLL("Error") = 0 then do
      say MyREXXDLL("Next")
      end

Example 7: (a) Using a built-in function to notify the REXX environment about a new external function; (b) using a C program to register external functions.

(a) call RXFuncAdd 'Func1',
          'MYDLL', 'Func1'

(b) for( j = 0; j < entries; ++j )
      RexxRegisterFunctionDll(
      RxFncTable[ j ].rxName,
                         DLLNAME.
      RxFncTable[ j ].cName );

Example 8: Returning a value. The clause in (a) is equivalent to (b) which, in turn, is functionally identical to (c).

(a) say FileDBNext()

(b) call FileDBNext
    say Result

(c) result = FileDBNext()
    say result

Listing One


/* RXFileDB.cpp */
#include "FileDB.hpp"
#define INCL_DOS
#define INCL_NOMAPI
#include <os2.h>
#define  INCL_RXFUNC
#include <rexxsaa.h>
#include <stdlib.h>
#include <stdio.h>
// Change this if you're changing the DLL name...
#define DLLNAME "RXFILEDB"
#define  INVALID_ROUTINE 40            // return Rexx error
#define  VALID_ROUTINE    0
// Some useful macros:
#define  SetNullRXString(dest) {*dest->strptr = '\0' ; \
                                 dest->strlength = 0 ;}
#define BUILDRXSTRING(t, s) {strcpy((t)->strptr,(s));\
                            (t)->strlength = strlen((s));}
// The C++ interface is done by declaring a global class instance and then 
// using members of this global class in our RexxFunctionHandler functions.
// The class can be declared, or a pointer can be declared and then allocated
// within a RexxFunctionHandler function.  We will use the latter approach.
// Either of these declarations will work, with the appropriate adjustments
// in the code that references them:
//FileDB x ;    // Don't new and delete these.
//                 Reference members with dot(.) operator.
FileDB * x ;    // Must new and delete.
//                 Refer to members with arrow(->) operator.
// Then declare the functions that access the public members of
// this class.  Remember to export these!
//========================================================================
RexxFunctionHandler FileDBLoadFuncs ; // Load the other functions.
RexxFunctionHandler FileDBDropFuncs ; // Drop all these functions; why bother?
RexxFunctionHandler FileDBStart ;     // Our class constructor.
RexxFunctionHandler FileDBFinish ;    // Our class destructor.
RexxFunctionHandler FileDBClose ;     // "call FileDBClose()"
RexxFunctionHandler FileDBError ;     // "do while FileDBError() = 0"
RexxFunctionHandler FileDBNext ;      // "say FileDBNext()"
RexxFunctionHandler FileDBOpen ;      // "say FileDBOpen('fname')"
RexxFunctionHandler FileDBAPI ;       // A single function API
// Define the table that lists REXX function names and the corresponding
// DLL entry point.  You must change this table whenever you add/remove
// a function or entry point.
typedef struct {
    PSZ     rxName;
    PSZ     cName;
} fncEntry, *fncEntryPtr;
static fncEntry RxFncTable[] =
   {
        /* function */     /* entry point (may not match function name*/
      { "FileDBLoadFuncs", "FileDBLoadFuncs" },
      { "FileDBDropFuncs", "FileDBDropFuncs" },
      { "FileDBStart",     "FileDBStart"     },
      { "FileDBFinish",    "FileDBFinish"    },
      { "FileDBClose",     "FileDBClose"     },
      { "FileDBError",     "FileDBError"     },
      { "FileDBNext",      "FileDBNext"      },
      { "FileDBOpen",      "FileDBOpen"      },
      { "FileDBAPI",       "FileDBAPI"       },
   };
// This function builds strings to return to REXX :
void CopyResult(PRXSTRING dest, const char * src) ;
//------------------------------------------------------------------------
// FileDBLoadFuncs -- Register all the functions with REXX.
ULONG _System
FileDBLoadFuncs(CHAR *name, ULONG argc, RXSTRING argv[],
                CHAR *q_name, RXSTRING *retstr)
  {
  int entries;
  int j;
  if (argc != 0)
    return(INVALID_ROUTINE);
  entries = sizeof( RxFncTable ) / sizeof( fncEntry );
  for( j = 0; j < entries; ++j )
    RexxRegisterFunctionDll( RxFncTable[ j ].rxName, DLLNAME,
    RxFncTable[ j ].cName );
  SetNullRXString(retstr)
  return (VALID_ROUTINE);
  }
//------------------------------------------------------------------------
ULONG _System
FileDBDropFuncs( CHAR *name, ULONG argc, RXSTRING argv[],
                 CHAR *q_name, RXSTRING *retstr )
  {
  int entries;
  int j;
  if( argc != 0 )
    return( INVALID_ROUTINE );
  entries = sizeof( RxFncTable ) / sizeof( fncEntry );
  for( j = 0; j < entries; ++j )
    RexxDeregisterFunction(RxFncTable[ j ].rxName);
  SetNullRXString(retstr)
  return(VALID_ROUTINE);
  }
//---------------------------------------------------------------------------
ULONG _System
FileDBStart( CHAR *name, ULONG argc, RXSTRING argv[],
             CHAR *q_name, RXSTRING *retstr )
  {
  if(argc == 1)
   {
   x = new FileDB(argv[0].strptr) ;
   CopyResult(retstr, argv[0].strptr) ;
   }
  else
  if(argc == 0)
    {
    x = new FileDB() ;
    BUILDRXSTRING(retstr, "Okay")
    }
  else
    return (INVALID_ROUTINE) ;
  return(VALID_ROUTINE);
  }
//------------------------------------------------------------------------
ULONG  _System
FileDBFinish( CHAR *name, ULONG argc, RXSTRING argv[],
              CHAR *q_name, RXSTRING *retstr )
  {
  if( argc != 0 )
    return( INVALID_ROUTINE );
  if (x)
    delete x ;
  SetNullRXString(retstr)
  return(VALID_ROUTINE);
  }
//----------------------------------------------------------------------
ULONG _System
FileDBClose( CHAR *name, ULONG argc, RXSTRING argv[],
             CHAR *q_name, RXSTRING *retstr )
  {
  char tmp[5] ;
  if( argc != 0)
    return(INVALID_ROUTINE);
  if (x)
    {
    x->Close() ;
    BUILDRXSTRING(retstr, itoa(x->Error(), tmp, 5))
    }
  else
    BUILDRXSTRING(retstr, "No Object")
  return(VALID_ROUTINE);
  }
//----------------------------------------------------------------------
ULONG _System
FileDBError( CHAR *name, ULONG argc, RXSTRING argv[],
             CHAR *q_name, RXSTRING *retstr )
  {
  char tmp[5] ;
  if( argc != 0 )
    return( INVALID_ROUTINE );
  if (x)
    BUILDRXSTRING(retstr, itoa(x->Error(), tmp, 10))
  else
    BUILDRXSTRING(retstr, "No Object")
  return(VALID_ROUTINE);
  }
//----------------------------------------------------------------------
ULONG _System
FileDBNext( CHAR *name, ULONG argc, RXSTRING argv[],
            CHAR *q_name, RXSTRING *retstr )
  {
  if (argc != 0)
    return( INVALID_ROUTINE );
  if (x)
    {
    CopyResult(retstr, x->Next()) ;
    if (x->Error() != 0)
      BUILDRXSTRING(retstr, " ")
    }
  else
    BUILDRXSTRING(retstr, "No Object")
  return(VALID_ROUTINE);
  }
//----------------------------------------------------------------------
// Open returns a number which we convert to character for Rexx->
ULONG _System
FileDBOpen( CHAR *name, ULONG argc, RXSTRING argv[],
            CHAR *q_name, RXSTRING *retstr )
  {
  char tmp[5] ;
  if( argc != 1 )
    return(INVALID_ROUTINE);
  if (x)
    {
    x->Open(argv[0].strptr) ;
    BUILDRXSTRING(retstr, itoa(x->Error(), tmp, 10))
    }
  else
    BUILDRXSTRING(retstr, "No Object")
  return(VALID_ROUTINE);
  }
//----------------------------------------------------------------------
/* This is the 'all-in-one' function.  REXX programs will typically
   call this function as follows: 
   call FileDBAPI("Open", "FileName")
   if FileDBAPI("Error") = 0 then do
     say FileDBAPI("Next") ....  */
ULONG  _System
FileDBAPI( CHAR *name, ULONG argc, RXSTRING argv[],
           CHAR *q_name, RXSTRING *retstr )
  {
  char tmp[5] ;
  if (argc == 0) // There must be at least one arg, the name of the call.
    return( INVALID_ROUTINE );
  if (!x)
    {
    BUILDRXSTRING(retstr, "No Object")
    return(VALID_ROUTINE);
    }
  if (stricmp("Close", argv[0].strptr) == 0)
    {
    if (argc != 1)
      return( INVALID_ROUTINE );
    x->Close() ;
    CopyResult(retstr, itoa(x->Error(), tmp, 10)) ;
    }
  else
  if (stricmp("Error", argv[0].strptr) == 0)
    {
    if (argc != 1)
      return( INVALID_ROUTINE );
    CopyResult(retstr, itoa(x->Error(), tmp, 10)) ;
    }
  else
  if (stricmp("Next", argv[0].strptr) == 0)
    {
    if (argc != 1)
      return( INVALID_ROUTINE );
    CopyResult(retstr, x->Next()) ;
    }
  else
  if (stricmp("Open", argv[0].strptr) == 0)
    {
    if (argc != 2)
      return( INVALID_ROUTINE );
    x->Open(argv[1].strptr) ;
    CopyResult(retstr, itoa(x->Error(), tmp, 10)) ;
    }
  else
    CopyResult(retstr, "Invalid First Argument") ;
  return (VALID_ROUTINE) ;
  }
//------------------------------------------------------------------------
/* CopyResult -- Copies a string into a result, allocating space for it if
     necessary. If you pass it an RXSTRING with a non-null buffer and a 
     non-zero length, it will try to copy the data into that buffer. Otherwise
     is uses DosAllocMem to allocate a new one.  */
void CopyResult(PRXSTRING dest, const char *src)
  {
  int len = strlen(src) ;
  static void  *mem = NULL;
  if( !dest )
    return ;
  if( (!src) && dest->strptr != NULL)
     SetNullRXString(dest)
  else
  if( dest->strptr != NULL && len <= dest->strlength )
    {
    memset(dest->strptr, 0, (size_t)dest->strlength);
    memcpy(dest->strptr, src, len);
    dest->strlength = len;
    }
  else
    {
    // The buffer is too small, so allocate a new one
    SetNullRXString(dest)
    if (DosAllocMem(&mem, len + 1, PAG_COMMIT | PAG_WRITE | PAG_READ))
        return ;
    dest->strptr    = (char *)mem;
    dest->strlength = len;
    //memset(dest->strptr, 0, len + 1);
    *(dest->strptr + len) = '\0' ;
    memcpy(dest->strptr, src, len);
    }
  }


Listing Two


/* This C++ class can be dynamically allocated and called from REXX. It reads
    text files. There are public members that return integers and others that
    return character pointers. The class does file manipulation using 
    ordinary C runtime functions. */
#ifndef FILEDB_HPP
#define FILEDB_HPP
#include <stdio.h>
#include <string.h>
#define MAXROWSIZE  200
class FileDB
  {
  private:
    FILE * fp ;
    int error ;
    char * bf ;
    long pos ;
    const char * Get()
      {
      if (fgets(bf, MAXROWSIZE, fp) != NULL)
        {
        error = 0 ;
        if (strchr(bf, 0x0d))
          memset(strchr(bf, 0x0d), '\0', 1) ;
        else
        if (strchr(bf, 0x0a))
          memset(strchr(bf, 0x0a), '\0', 1) ;
        if (!strchr(bf, 0x1a))
          return bf ;
        }
      //else
      fseek(fp, 0L, SEEK_SET) ;
      error = -2 ;
      *bf = '\0' ;
      return bf ;
      }
  public:
    FileDB(){fp=NULL;bf=NULL;error=0;}
    FileDB(char * fname){fp=NULL;bf=NULL;Open(fname);}
    ~FileDB(){Close();}
    void Close()
      {
      if(fp)
        {
        fclose(fp);
        fp=(FILE *)NULL;
        }
      if (bf)
        delete [] bf ;
      }
    int     Error(){return error;}
    const char * Next(){return Get();}
    void Open(char *fname)
      {
      Close();
      bf = new char [MAXROWSIZE] ;
      if ((fp = fopen(fname, "r")) == NULL)
        error = -1 ;
      else
        error = 0 ;
      fseek(fp, 0L, SEEK_SET) ;
      }
  } ;
#endif // FILEDB_HPP


Listing Three


/* commands for compiling and linking for IBM C-Set++ */
ICC.EXE   /Ge- /Gm+ /C   .\$*.cpp
 /B" /de /noe /m"
 /Fe"RXFILEDB.DLL" REXX.LIB RXFILEDB.DEF

;RXFileDB.DEF Used for Link step for IBM C-Set++
LIBRARY RXFILEDB INITINSTANCE TERMINSTANCE
DESCRIPTION 'OS/2 Test Dynamic Link Library (c)AFS 1994'
DATA MULTIPLE NONSHARED READWRITE LOADONCALL
CODE LOADONCALL
EXPORTS
    FileDBLoadFuncs
    FileDBDropFuncs
    FileDBStart
    FileDBFinish

    FileDBAPI
    FileDBClose
    FileDBError
    FileDBNext
    FileDBOpen

/* commands for compiling and linking for Watcom */
call wpp386 /bd /bm /d2 rxfiledb.cpp
call wlink @rxfiledb.lnk

;RXFileDB.LNK Used for Link step for Watcom

system os2v2 dll initinstance terminstance
option manyautodata
debug all
option symfile
export FileDBLoadFuncs
export FileDBDropFuncs
export FileDBStart
export FileDBFinish
export FileDBAPI
export FileDBClose
export FileDBError
export FileDBNext
export FileDBOpen
library rexx
file rxfiledb


Listing Four


/* C program to test the FileDB class. Pass in the name of a file to list. */
#include "FileDB.hpp"
#include <iostream.h>
int main(int argc, char * argv[])
  {
  FileDB * f ;
  if (argc != 2)
    return -1 ;
  f = new FileDB(argv[1]) ;
  while (0 == f->Error())
    cout << f->Next() ;
  return 0 ;
  }


Listing Five


/* t.cmd  -- Functions with no arguments= call func or rc=func() */
say "Registering the RXFILEDB functions.  If the program halts when trying"
say "to call RXFileDB, make sure the RxFileDB.DLL file is in your LIBPATH."
say ""
call RXFuncAdd 'FileDBLoadFuncs', 'RXFILEDB', 'FileDBLoadFuncs'
say "Load Funcs added"
call FileDBLoadFuncs
say "Load Funcs finished"
GotAFile = 0
call FileDBStart
do forever
  say "Main Menu "
  say "1. Choose a File"
  say "2. List a File"
  say "0. Quit"
  say ""
  say "Make a Selection"
  pull input
  select
    when input = 1 then call Chooser
    when input = 2 then call Lister
    when input = 0 then leave
  otherwise
    say "Make a Selection from 1 to 2"
  end
end
say "Left"
call FileDBFinish
say "Finish"
/*call FileDBDropFuncs*/
return
CHOOSER:
GotAFile = 0
say "What is the name of the file? Please include the extension."
say "Example: TEST.EXT"
pull fname
say FileDBAPI("Open", fname)
if FileDBAPI("Error") = 0 then do
  GotAFile = 1
end ;
return
LISTER:
if GotAFile = 0 then do
  say "Choose a file first"
  return
end ;
do until FileDBError() <> 0
  say FileDBNext()
end
say "Fname = " fname
say FileDBOpen(fname) /* Reset to top of the file */
return


Listing Six


/*:VRX         File_Click   */
File_Click:
 filespec = VRFileDialog( VRWindow(), "File to open?", "O", "*.*" )
 if filespec = "" then do
     return
 end
  call VRMethod("List001", "Clear")
  x = FileDBOpen(filespec)
  do while FileDBError() = 0
    f_line = FileDBNext()
    if FileDBError() = 0 then do
      call VRMethod("List001", "AddString", f_line)
    end
  end
return
/*:VRX         Init */
Init:
    window = VRWindow()
    call VRMethod window, "CenterWindow"
    call VRSet window, "Visible", 1
    call VRMethod window, "Activate"
    call RXFuncAdd 'FileDBLoadFuncs', 'RXFILEDB', 'FileDBLoadFuncs'
    call FileDBLoadFuncs
    call FileDBStart
    drop window
return
/*:VRX         Quit  */
Quit:
    window = VRWindow()
    call VRSet window, "Shutdown", 1
    drop window
    call FileDBFinish
return


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.