#82021 - 2003-03-15 09:12 PM
Re: How to write a UDF
|
Sealeopard
KiX Master
Registered: 2001-04-25
Posts: 11164
Loc: Boston, MA, USA
|
========================================
Part V: Implementing UDFs into your code
========================================
The following section was provided by Richard Howarth and was originally posted under GOSUB vs. FUNCTIONAn example might help.
Say you write an application that uses a database to store records. You want the very basic facilities like add a record, delete a record, find a record, list all records.
You could write these as subroutines in your application but there are two problems with that.
? 1) The code may be useful in other applications - using subroutines means that you need to cut and past the code - a big overhead in script size and maintenance.
? 2) You have to pass variables to/from the subroutine using global variables. This means that to use the subroutines you have to know what the variables are called, and your subroutine is in danger of overwriting variables that you are using in the main script.
You can solve "1" by simply putting each of your subroutines in a file on it's own, and CALLing the file. This will execute the script in the file as if it was in your main script. For example, you would place your add record routine in a file called "AddRecord.kix", and when you want to add a record you use the script command
Code:
CALL "AddRecord.kix"
In this case your variables may be local, but you still need to know what they are, and you still need to be careful about overwriting variable in the main script.
Also there is the overhead of reading and parsing the file every time you make the CALL.
So, we come to the best all round option - libraries of functions.
Functions can be designed as black-boxes in almost all cases - there are a couple of exceptions which I'll note later.
The benefit of this is that you only ever have to know the function name and expected parameters. You don't need to know how it works, and you don't need to know what it is using as variables internally.
An additional but often overlooked benefit of functions is that you can set @ERROR on exiting the function. You use this in your main script to determine whether the function call worked or not.
For your database routines, you create a file called "SQLroutines.kix". In this file you create all of your functions:
Code:
Function fnAddRecord($sKey,$asRecord)
;... Add Record Code ...
$fnAddRecord=$iSuccess
Exit $iSuccess
EndFunction
Function fnDeleteRecord($sKey)
;... Delete Record Code ...
$fnDeleteRecord=$iSuccess
Exit $iSuccess
EndFunction
Function fnFindRecord($sSearchString)
;... Find Record Code ...
$fnFindRecord=$sKey
Exit $iSuccess
EndFunction
Function fnGetRecord($sKey)
;... Get Record Cose ...
$fnGetRecord=$asData
Exit $iSuccess
EndFunction
;and so-on ...
To use these functions you call the script file once at the start of the script. This has the effect of loading the functions into memory, where they will stay until you exit your main script. You can now call the functions as often as you like, without the overhead of reading and parsing the script file.
You call the function in the same way as internal KiXtart functions, so the following are all valid:
Code:
$UserLogin='jdoe'
$UserData[0]='John'
$UserData[1]='Doe'
$UserData[2]='Trainee Clown'
If fnAddRecord($UserLogin,$UserData)
? 'ERROR: Could not add record for '+$UserLogin
? ''+@ERROR+': '+@SERROR
Else
? 'Record added OK.'
EndIf
If fnDelRecord('jdoe')
? 'ERROR: Could not delete record'
EndIf
$UserLogin=fnFindRecord('Clown')
If @ERROR
? "No clowns in this organisation."
Else
$UserData=fnGetRecord($UserLogin)
If @ERROR
? 'ERRROR: Invalid key '+$UserData
Else
? $UserData[1]+' '+$UserData[2]+' is a clown.'
EndIf
EndIf
Neat, huh?
For advanced usage the library method has another major advantage.
Say you want to deploy your database applications to many sites. Some are very small sites which use flat text files for storing data, some are medium sized sites which use Access for storing data and some are large sites which use LDAP or SQL for storing data.
Now you could write applications for each, but wouldn't it be easier to do something like this at the start of your script:
Code:
$DATABASETYPE=fnGetDBType()
Select
Case $DATABASETYPE='TXT'
CALL 'TXTroutines.kix'
Case $DATABASETYPE='SQL'
CALL 'SQLroutines.kix'
Case $DATABASETYPE='ODBC'
CALL 'ODBCroutines.kix'
Case $DATABASETTYPE='LDAP'
CALL 'LDAProutines.kix'
EndSelect
or more simply:
Code:
CALL fnGetDBType + 'routines.kix'
Each of these files will have a "fnAddRecord()" defined. Your script doesn't need to know which database method is being used when you add a record. The correct version will have been loaded by the "CALL" at the starte of your script.
You may remember that I mentioned that there are some cases when you cannot make a routine in a "black-box" fashion.
The first problem is when you want to keep some information and re-use it in your function. These types of variables are known as "static" variables in some languages.
For example, you may only want to set up a connection to your database once as it is an expensive thing to do in computer terms. At present the only way to keep this information between function calls is to define it in a GLOBAL variable. This variable will have the scope of your entire script, so can clash with your main script variables.
The other problem area is when you want to change the value of one of the parameters. A simple classic example is the "POP" routine. This takes a stack, "pops" the top element off the stack and returns the popped off element. Consider a function to "pop" off the first character in a string:
Code:
Function fnPopString($sString)
If $sString=''
Exit 1
EndIf
$fnPopString=Left($sString,1)
$sString=SubStr($sString,2)
Exit 0
EndFunction
Looks good, eh?
Well no, it won't work. The variable $sString is LOCAL to the function, and is destroyed on exit. It is a copy of the string that you passed in your main script. Passing this type of information is known as "passing by value"
For functions which need to change variables outside their scope you need to use a method known as "pass by reference", or "pass by address". This method passes a pointer to the original variable so that it can be manipulated in the function.
KiXtart does not currently support pass by reference, so again you will need to use GLOBAL variables to achieve the result.
There is an advanced coding technique which you can use to abstract the variable name, but the variable you want to change still must be global.
Edited by kdyer (2004-02-09 05:05 PM)
_________________________
There are two types of vessels, submarines and targets.
|
Top
|
|
|
|
Moderator: Jochen, Radimus, Glenn Barnas, Allen, Arend_, ShaneEP, Mart
|
0 registered
and 330 anonymous users online.
|
|
|