======================================================================= 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