=======================================================================
UDF'S IN PASCALL OR BASIC
=======================================================================
PRODUCT: R:BASE VERSION : 3.1C Or Higher
=======================================================================
AREA : PROGRAMMING CATEGORY: FUNCTIONS DOCUMENT#: 664
=======================================================================
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.
{ DOS VERSION: EXAMPLE.PAS }
{
* 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;
VAR
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 }
BEGIN
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 }
BEGIN
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
hello-world
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' . . .
PrmStr1[5]='o'.
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
routine.
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
facts.
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.
EXAMPLE.BAS
-----------
'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
HELLO-WORLD
SHO VAR verrvar