PRODUCT:  R:BASE          VERSION :  3.1C Or Higher
     One of the most powerful new R:BASE features is the User Defined
     Function  (UDF).  UDF's have the ability to revolutionize a
     developer's approach in creating R:BASE applications.  They can be
     used anywhere R:BASE allows a function to be used:  forms, reports,
     entry/exit procedures (EEP's), computed columns, and applications.  A
     UDF can be created in any programming language that allows the
     resulting program to write to far memory pointers. 
     Many pre-written UDF's are available from Microrim and other sources
     (Mayer Labs, for example).  In addition, the R:BASE documentation has
     an example program (EXAMPLE.C) written in the C programming language;
     however, not everyone is proficient in C or has the time to learn a
     new language.  Many other popular PC programming languages can be
     used to write UDF's, notably Turbo Pascal and BASIC.
     UDF Pascal Program Example                            
     From: Donald L. Swartz
           Data Language Systems
           10520 W. SR 32
           Yorktown, IN 47396
           (317) 759-7367
           Compuserve 72331,240
     Don is a Manufacturing Design Engineer for the Inland Fisher
     Guide Division of General Motors.  He also has a part-time PC
     software development and consulting business, Data Language Systems,
     specializing in software written in Turbo Pascal and Assembler.
     The following Turbo Pascal UDF program (EXAMPLE.PAS) is equivalent to
     EXAMPLE.C.  I created it using Turbo Pascal 6.0, but it also should
     compile on earlier versions of Turbo Pascal.
     *  Partial program that takes the first command-line parameter and
     *  returns it to the R:BASE UDF.   This program is the Pascal
     *  equivalent of EXAMPLE.C documented on page 2-59 in the R:BASE 
     *  Upgrade Express "Guide to Software Installation and New Features", 
     *  version 3.1C.
     PROGRAM Example;
       i4     : LONGINT;      { Address of R:BASE variable passed by UDF }
       ValErr : INTEGER;      { Error code returned from call to VAL function }
       Pt     : POINTER;      { Pointer to i4 (R:BASE address) }
       PrmStr1: STRING;       { Stores 1st parameter passed by UDF }
     BEGIN { PROGRAM Example }
       IF ParamCount>1 THEN   { See if there was an address to store the }
                              {  result }
           PrmStr1 := ParamStr(1);    { Get 1st command-line parameter }
           PrmStr1 := PrmStr1 + #0;   { Place NULL at end of parameter }
           Val(ParamStr(2),i4,ValErr);  { Convert the second command-line    }
                                        { argument to a long integer address }
           IF ValErr=0 THEN     { ParamStr(2) correctly converted to numeric }
                                { format }
               Pt := Ptr(i4 SHR 16,i4 AND $FFFF); { Set the char pointer to  }
                         {Segment}   {Offset}     { the memory address where }
                                                  { R:BASE is expecting to   }
                                                  { receive the returned     }
                                                  { text value.              }
               { NOTE: Since the Pascal STRING type is a maximum of 255       }
               { characters in length, it is not necessary to test for        }
               { PrmStr1 length<1500 which is done at this point in EXAMPLE.C }
               Move(PrmStr1[1],Pt^,Length(PrmStr1)) { Copy 1st command-line   }
                                                    { parameter to the        }
                                                    { address passed in by    }
                                                    { the R:BASE UDF.         }
             END { IF ValErr=0 }
           ELSE { Error.  ParamStr(2) is not a number. }
             Halt(1) { Pass ERRORLEVEL=1 to DOS }
          END { IF ParamCount>1 }
       ELSE { Error. Insufficient parameters. }
         Halt(1) { Pass ERRORLEVEL=1 to DOS }
     END. { PROGRAM Example }
     EXAMPLE.PAS is identical in function to EXAMPLE.C.  Once it
     is compiled, and you have created EXAMPLE.EXE, start R:BASE 
     and go to the R> prompt.  Enter:
         R>SET VAR v1=(UDF('EXAMPLE','hello-world'))
         R>SHOW VAR v1
     Some things to consider when programming Turbo Pascal UDF's are:
     1. A Turbo Pascal STRING type is 256 bytes long and can hold a
        maximum of 255 characters.
     2. The STRING index starts at zero.  The value in index zero of a
        string is the string's length.  The length is not in numeric format,
        but is in character format.  For example, if the string PrmStr1 is
        ten characters long, the ASCII character #10 (Linefeed character) is
        found in PrmStr1[0].
     3. The characters in the string start at index number one.  For
        example, if the string 'hello' is in PrmStr1, PrmStr1[1]='h' . . .
     4. Always reference the string variable's name with index one when
        using the Turbo Pascal Move procedure to copy strings to the UDF's
        R:BASE address.  If you do not specify an index number with the
        string's name, zero is assumed. Consequently the length character is
        the first character copied to the UDF's R:BASE address.  This results
        in a garbage character in the UDF function's result.  If you are
        comfortable using Turbo Pascal strings and standard Turbo Pascal
        string routines, this point may be a little confusing.  Standard
        Turbo Pascal string routines take care of the string index
        referencing automatically and you probably never had to worry about
        it before.  The reason you have to specify indexing when using the
        Move procedure is that it is NOT a string routine.  It is a memory
     5. Turbo Pascal strings are not automatically terminated by the NULL
        character required by R:BASE's UDF variable.  In EXAMPLE.PAS the NULL
        character is added (concatenated) to PrmStr1 by the command line: 
        PrmStr1 := PrmStr1 + #0;  (i.e., #0 is the representation for NULL in
        Turbo Pascal.)
     6. PC memory addressing is a complex subject and is well
        beyond the scope of this article.   But here are a few basic
     PC memory addresses are twenty bits (three bytes) long.  Unfortunately
     the CPU can only handle sixteen bits (two bytes/one word) at a time. 
     To overcome this problem, the CPU addresses memory in what some people
     have called the most awkward manner ever conceived:
     The PC's memory addresses are divided into two 16-bit portions
     called segments and offsets.  Segments are generally thought of as 64K
     paragraphs in memory. Offsets are the number of bytes a memory
     location is from the start of a segment.  
     To address a far memory location, you need its segment and offset.
     The address that UDF passes in the command-line parameter contains
     both elements.  They are just cleverly spliced together.  In the
     resulting LONGINT (double-word/32-bit) variable i4 in EXAMPLE.PAS,
     the page segment address number is in the high-order word (i.e., the
     left-most sixteen bits) and the offset address is in the low-order
     word (i.e., the right-most sixteen bits).  In EXAMPLE.PAS, the
     command: Pt :=  Ptr(i4 SHR  16,i4 AND $FFFF); converts i4 into the
     segment and offset addresses and leaves Pt pointing to the far memory
     location R:BASE has assigned for the UDF's return value.
     UDF BASIC Program Example
     From: Ron Radzieta
           Geologic Software
           6152 Dunraven St.
           Golden, CO 80403
           (303) 278-9439
     Ron is a geologist and a computer consultant to the geologic,
     environmental, and recreational whitewater boating industries.
     To aid in writing a UDF in BASIC, the following Microsoft BASIC
     program (EXAMPLE.BAS) is equivalent to EXAMPLE.C.  I use Microsoft's
     Basic PDS 7+ to create R:BASE UDF's.  The following BASIC program
     example was compiled using the /Fs and /O compile options.
          'Delcare StringAssign to be subroutine,  StringAssign is
          'available in Microsoft BASIC PDS 7.0 and later
     DECLARE SUB StringAssign (BYVAL sourcefarptr&, BYVAL sourcelen%,
                               BYVAL destfarptr&, BYVAL destlen%)
          'Get parameter list as if EXAMPLE.BAS was executed from DOS prompt
     parameterlist$ = COMMAND$
          'Find the blank space in the paramter list
     blank% = INSTR(parameterlsit$, " ")
          'Parse the string passed by the RBASE UDF in the parameter list
     sourcestr$ = MID$(parameterlist$, 1, blank% - 1)
          'Add the required Null terminator to the passed string
     sourcestr$ = sourcestr$ + CHR$(0)
          'Get the destination far pointer address passed in the paramter list
     destfarptr& = MID$(parameterlist$, blank% + 1)
          'Use SSEGADD to get the pointer for the passed string
     sourcefarptr& = (SSEGADD(sourcestr$))
          'Convert the far pointer address from a string to a long integer'
     destfarptr& = VAL(destfarptr$)
          'Determine the length of the string
     sourcelen% = LEN(sourcestr$)
          'Set destination length to the the same as the source string length
     destlen% = sourcelen%
          'Write to the far pointer specified by RBASE
     CALL StringAssign(sourcefarptr&, sourcelen%, destfarptr&, destlen%)
     R:BASE sample command file and results
     SET ERROR VAR errvar
     SER VAR v1=(UDF('example','hello-world'))
     SET VAR verrvar=(.errvar)
     SHO VAR v1
     SHO VAR verrvar