ucDefineFunction
See Also: ucDefine, ucDefineOperator, ucArg, ucArgCount, ucArgHandle, ucReturn
Defines a function.
ucDefineFunction(Definition [, FunctionAddress
[, tHandle]])
There are three types of function definitions: 1) Self-contained definitions, 2) Native callback definitions, and 3) Non-native callback definitions. They are referred to in the next section, but explained in more detail further down. First, here is a breakdown of what you can pass to ucDefineFunction:
Parameters
Definition
Required. This string argument should consist of a function name, followed by parenthesis, which optionally enclose one or more arguments, followed optionally by a return type. If no return type is specified, then the default type (Double or Extended, depending on the compiler) is implied. If this is a self-contained definition, then you continue with an equal sign, followed by an expression that represents the body of code that is executed when the function is invoked. If this were a callback function, then instead of an equal sign followed by an expression, you would provide a function address in the FunctionAddress argument.
Argument
parts
An argument has the following syntax and parts:
[ByVal | ByRef | ByHandle | ByExpr] argname [As argtype] [= default] [[,] ...]
ByRef
Optional. For use only with non-native callbacks. Indicates that the argument is passed by reference, and the value can be modified by your callback routine.
ByVal
Optional. For use only with non-native callbacks. Indicates that the argument is passed by value.
Optional. For use only with callbacks (either native or non-native). Indicates that the value received by the callback is a handle to the expression that is being passed. This allows an expression to be passed without first being evaluated. Your callback has the option of either not evaluating the expression at all, or evaluating it one or more times. For a native routine, the argument's expression handle is retrieved with ucArgHandle. For a non-native routine, the callback argument must be set up as a 32-bit integer (such as Long or DWORD), which is passed by value, regardless of the actual type of the argument. This 32-bit handle is to be used with ucEvaluate() or ucEvaluateStr(). See Example B4.
Optional. For use only with callbacks (either native or non-native). Indicates that the value being passed is the handle of the argument. Given the handle of an argument, your callback can retrieve its properties. For instance, if the argument is a variable, the callback can use the handle to determine the variable's data type, name, address in memory, etc. For a native routine, an argument handle is retrieved with ucArgHandle. For a non-native routine, the callback argument must be set up as a 32-bit integer (such as Long or DWORD), which is passed by value, regardless of the actual type of the argument. See Example B5.
argname
Required. This is the name of your argument.
argtype
Optional. This can be any of the data types supported by uCalc, including: Single, Double, Long, String, WideString, LPCSTR, etc. If no type is specified, then the default type is used (Extended or Double depending on the compiler, or something else if you have changed the default).
default
Optional. Unlike some languages, the word "Optional" is not needed. An argument is considered optional by virtue of having an equal sign followed by a default value. Also unlike some languages, the default does not have to be a constant; it can be an expression as well. See Example A1.
...
Optional. For use only with native callbacks. Three consecutive dots (...) indicate that the function takes an indefinite number of arguments from that point on. This is valid only with, or as the last argument. Your callback should use ucArgCount to retrieve the number of arguments that were passed. See Example B2.
FunctionAddress
Required for callbacks (native or non-native). Omit FunctionAddress, or set it to 0 for self-contained definitions. If you are defining a callback function, then this argument represents the address of the function in your source code (or a DLL) that you want to associate with your uCalc definition. If your function is named MyFunction, then you would pass CodePtr(MyFunction) in PowerBASIC, AddressOf MyFunction in VB Classic, @MyFunction in Delphi, and MyFunction by itself in C++. VB.NET and C# handle this quite differently, using delegates. See Example B1.
tHandle
Optional. You can supply the handle of a given thread so that the function definition is effective only in that particular thread. See Thread Handling.
In a self-contained function definition, the function name and its argument(s), as well as the body of the definition (the code depicting the operations to be performed by the function) are all located within the Definition argument of ucDefineFunction. This allows the end-users of your application to define functions.
Self-contained definitions can be recursive.
Self-contained definitions can be bootstrapped. This means that you can add to the definition of an already defined function (even if the previous definition was not self-contained). This is done by simply using the same function in the body of the definition, as though it were another function. A bootstrapped definition is distinguished from a recursive one by prepending "bootstrap ~~" to the definition.
The body of the definition may contain multiple statements/expressions, which are separated by a semicolon. The last one should be an expression that evaluates to a value that matches the return data type of the function. The value of this last expression is the one that is returned.
Example A1: Miscellaneous self-contained definitions
This example demonstrates various self-contained definitions. It features various concepts including overloading (which means several definitions may share the same function name), as demonstrated with the function named Twice(). It also shows an optional argument in the function named g(), recursion in Factorial(), and specified data types. Where a data type is not specified for the argument or return type, Double or Extended is implied depending on the compiler.
Visual Basic
ucDefineFunction "f(x) = x^2" ucDefineFunction "g(x, y=5) = x + y" ucDefineFunction "Twice(value) = value + value" ucDefineFunction "Twice(value As String) As String =
value + value" ucDefineFunction "ToLong(value
As Long) = value" ucDefineFunction "ToByte(value)
As Byte = value" ucDefineFunction "Factorial(x) = iif(x>1,
x*Factorial(x-1), 1)" Print ucEvalStr("f(5)") ' returns 25 Print ucEvalStr("g(10)") ' returns 15 Print ucEvalStr("g(10,
20)") ' returns 30 Print ucEvalStr("Twice(35)") ' returns 70 Print ucEvalStr("Twice('Bye
')") ' returns Bye Bye Print ucEvalStr("ToLong(123.456)") ' returns 123 Print ucEvalStr("Factorial(5)") ' returns 120 |
This example demonstrates a bootstrapped definition. The original Cos() function expects arguments to be in radians. The definition below uses the original Cos() function in order to create a new Cos() function that expects arguments in degrees. This example is only for demonstration. There is a ucTrigMode routine for changing the trig mode. Note that the original Cos() definition is not actually modified. Instead, the new definition takes precedence. However, in the final block of code, the new definition is deleted with ucReleaseItem(NewDef), and the original Cos() definition is now back in effect. Note: In version 3.0 the Bootstrap keyword must precede the rest of the definition (in version 2.96, the example placed it at the end).
Visual Basic
Dim NewDef As Long ucDefine
"Const: pi = Atan(1)*4" Print ucEvalStr("Cos(pi)") ' returns -1 Print ucEvalStr("Cos(180)")
' returns -0.59846... NewDef
= ucDefineFunction("Bootstrap ~~ Cos(x) = Cos(x*pi/180)") Print ucEvalStr("Cos(pi)") ' returns
0.99849... Print ucEvalStr("Cos(180)")
' returns -1 ucReleaseItem
NewDef ' After deleting the new definition, we
are back to the old Cos() Print ucEvalStr("Cos(pi)") ' returns -1 Print ucEvalStr("Cos(180)")
' returns -0.59846... |
Example A3: Redefining with Overwrite
This example demonstrates redefining a function with the Overwrite keyword. When such a function is redefined, other functions that call it will return a result based on the redefined version. This is useful in spreadsheet-like situations. If the Overwrite keyword were omitted, modifying f(x) would not affect g(x). g(x) would continue to return a result based on the original definition of f(x).
Visual Basic
ucDefine
"Overwrite ~~ func: f(x) = 10 * x" ucDefine
"func: g(x) = f(x) + 1) Print ucEvalStr("f(5)")
' returns 50 Print ucEvalStr("g(5)")
' returns 51 ucDefine
"Overwrite ~~ func: f(x) = 100 * x" Print ucEvalStr("f(5)")
' returns 500 Print ucEvalStr("g(5)")
' returns 501 |
Callback definitions (native and non-native)
In addition to self-contained function definitions, uCalc FMP allows you to link a function definition to the address of a routine found in a DLL, or a routine in your source code that is written in the language of your host compiler. Both native and non-native definitions have their advantages and disadvantages. Native definitions do not use extra overhead, and are therefore considerably faster than non-native definitions. However, a native callback requires uCalc code. So to link directly to an API routine that wasn't written for uCalc, your definition would need to be non-native.
Functions and operators that come with uCalc FMP, such as Sin(), Cos(), Exp(), Sqr(), +, -, *, etc., are defined using a method that is native to the way uCalc operates. Such native callback routines use little overhead, making them fast. uCalc allows you to create native routines as well. The function name in the definition should be preceded with "Native:", as in the examples below. The exception to this is .NET (C# and VB.NET), where because of the use of delegates, native callbacks are the only supported callbacks. So in C# or VB.NET, whether or not you use the word Native:, the definition will be the same.
A shortcut for ucDefineFunction "Native: ..." is ucDefine "Func:: ...".
Regardless of the number of arguments, and the data type of return value, the routine's outer structure is always the same. It must always be a routine that doesn't return a value (in other words, a void in C++ or C#, a procedure in Delphi, or a Sub in Visual Basic and PowerBASIC). The callback always receives exactly one argument, which is a 32-bit integer (it doesn't matter if it's a long, DWORD, or an equivalent). Although you may give the argument any name, it is best to name it Expr for consistency with this help file. This is a handle for the expression.
Your callback should use ucArg (see further down for another alternative) to retrieve numeric arguments, ucArgStr for string arguments, or ucArgHandle to retrieve the handle of the argument if it was passed ByHandle or ByExpr. ucArgCount returns the number of arguments that are being passed. The return value of your callback function is set with ucReturn (for numbers) or ucReturnStr (for strings).
If your function is named MyFunction, then the first line(s) of your native callback should always look almost exactly like one of the following, with the only variation being based on the name of the function and the compiler you are using:
Borland C++ Builder (always remember to use _stdcall here)
void _stdcall MyArea(DWORD Expr) |
C#
public uCalc.ucCallback d_MyArea = new uCalc.ucCallback(MyArea); static void MyArea(int Expr) |
Delphi (always remember to use stdcall here)
procedure MyArea(Expr: Longword); stdcall; |
PowerBASIC
Sub MyArea(ByVal Expr As ucExpression) |
Visual Basic
Sub MyArea(ByVal Expr As Long) |
Visual Basic.NET
Sub MyArea(ByVal Expr As Integer) |
Visual C++ (always remember to use _stdcall here)
void _stdcall MyArea(DWORD Expr) |
Note: If you are using C++ or Delphi, you should always remember to use the stdcall directive. uCalc has no way of detecting whether or not you remembered to do this. C++ or Delphi callbacks without stdcall may cause the callback to malfunction in subtle (and sometimes not-so-subtle) ways.
Example B1: A native callback with two numeric arguments
This example creates a native function named MyArea, which returns the product of the two arguments. If either of the arguments is a negative number, then ucSetErrorMesage raises an error using the error message string that is supplied to it. Once defined, MyArea(3, 4) would return 12. And if you are using ucEvalStr(), then MyArea(-5, 3) would return the message: Length cannot be negative instead of a numeric value.
// The following line can be placed in
TForm1::FormCreate() ucDefineFunction("Native: MyArea(Length,
Width)", MyArea); // The following callback routine can
be placed just before TForm1::FormCreate() // IMPORTANT: Always remember to use stdcall for uCalc callbacks (in C++) void _stdcall MyArea(DWORD Expr) { long double
MyLength, MyWidth; MyLength = ucArg(Expr, 1); MyWidth = ucArg(Expr, 2); if(MyLength < 0) ucRaiseErrorMessage(Expr,
"Length cannot be negative"); if(MyWidth < 0) ucRaiseErrorMessage(Expr, "Width cannot be negative"); ucReturn(Expr, MyLength * MyWidth); } |
// The following line can be placed in
Form1_Load() uc.ucDefineFunction("Native: MyArea(Length,
Width)", d_MyArea); // The following callback routine can be placed just before Form1_Load() public uCalc.ucCallback d_MyArea = new uCalc.ucCallback(MyArea); static void MyArea(int Expr) { double MyLength, MyWidth; MyLength = uCalc.ucArg(Expr, 1); MyWidth = uCalc.ucArg(Expr, 2); if(MyLength < 0) uCalc.ucRaiseErrorMessage(Expr,
"Length cannot be negative"); if(MyWidth < 0) uCalc.ucRaiseErrorMessage(Expr, "Width cannot be negative"); uCalc.ucReturn(Expr, MyLength * MyWidth); } |
// The following line can be placed in
TForm1.FormCreate() ucDefineFunction('Native: MyArea(Length,
Width)', @MyArea); // The following code can be placed
just before TForm1.FormCreate() // IMPORTANT: Always remember to use stdcall for uCalc callbacks procedure MyArea(Expr: Longword); stdcall; var MyLength, MyWidth: Extended; begin MyLength := ucArg(Expr, 1); MyWidth := ucArg(Expr, 2); if MyLength < 0 then ucRaiseErrorMessage(Expr,
'Length cannot be negative'); if MyWidth < 0
then ucRaiseErrorMessage(Expr, 'Width cannot be negative'); ucReturn(Expr, MyLength * MyWidth); end; |
' The following line can be placed in PBMain() ucDefineFunction
"Native: MyArea(Length,
Width)", CodePtr(MyArea) ' The following callback routine can be placed just before PBMain() Sub MyArea(ByVal Expr As ucExpression) Dim MyLength As Extended, MyWidth
As Extended MyLength = ucArg(Expr, 1) MyWidth = ucArg(Expr, 2) If MyLength < 0 Then ucRaiseErrorMessage Expr,
"Length cannot be negative" If MyWidth < 0
Then ucRaiseErrorMessage
Expr, "Width cannot be negative" ucReturn(Expr, MyLength * MyWidth) End Sub |
' The following line can be placed in Form_Load() ucDefineFunction
"Native: MyArea(Length,
Width)", AddressOf MyArea ' The following callback routine goes in a separate module, such as DemoVB.Bas Sub MyArea(ByVal Expr As Long) Dim MyLength As Double, MyWidth As
Double MyLength = ucArg(Expr, 1) MyWidth = ucArg(Expr, 2) If MyLength < 0 Then ucRaiseErrorMessage Expr,
"Length cannot be negative" If MyWidth < 0
Then ucRaiseErrorMessage
Expr, "Width cannot be negative" ucReturn Expr, MyLength * MyWidth End Sub |
' The following lines can be placed in
Form1_Load() Static d_MyArea As ucCallback
= New ucCallback(AddressOf
MyArea) ucDefineFunction("Native: MyArea(Length,
Width)", d_MyArea) ' The following callback routine goes in a separate module, such as DemoNET.vb Sub MyArea(ByVal Expr As Integer) Dim MyLength As Double, MyWidth As
Double MyLength = ucArg(Expr, 1) MyWidth = ucArg(Expr, 2) If MyLength < 0 Then ucRaiseErrorMessage(Expr,
"Length cannot be negative") If MyWidth < 0
Then ucRaiseErrorMessage(Expr, "Width cannot be negative") ucReturn(Expr, MyLength * MyWidth) End Sub |
Example B2: A native callback with any number of arguments
This example features a function with an indefinite number of arguments. Based on the way it is defined, MyAverage() takes at least one numeric argument. Beyond that, it can accept any additional number of numeric arguments. Once defined, MyAverage(3, 4) would return 3.5, and MyAverage(3, 4, 5, 6) would return 4.5. MyAverage(3, 4, "5") would return an error, because the last argument, which is a string is not compatible with the first argument. In the previous example the keyword Native: was used in all the examples. In the example below, it was not used in C# and VB.NET. That's because the presence or absence of Native: has no effect in those two compilers, since because of their use of delegates for callbacks, they only support native callbacks.
// The following line can be placed in
TForm1::FormCreate() ucDefineFunction("Native: MyAverage(x ...)", MyAverage); // The following callback routine can
be placed just before TForm1::FormCreate() // IMPORTANT: Always remember to use stdcall for uCalc callbacks (in C++) void _stdcall MyAverage(DWORD Expr) { long x; double
Total = 0; for(x =
1; x <= ucArgCount(Expr);
x++) Total
= Total + ucArg(Expr, x); ucReturn(Expr, Total / ucArgCount(Expr)); } |
// The following line can be placed in
Form1_Load() uc.ucDefineFunction("Native: MyAverage(x ...)", d_MyAverage); // The following callback routine can be placed just before Form1_Load() public uCalc.ucCallback d_MyAverage = new uCalc.ucCallback(MyAverage); static void MyAverage(int Expr) { int x; double
Total = 0; for(x=1;
x <= uCalc.ucArgCount(Expr);
x++) Total
= Total + uCalc.ucArg(Expr,
x); uCalc.ucReturn(Expr, Total / uCalc.ucArgCount(Expr)); } |
// The following line can be placed in
TForm1.FormCreate() ucDefineFunction('Native: MyAverage(x ...)', @MyAverage); // The following code can be placed
just before TForm1.FormCreate() // IMPORTANT: Always remember to
use stdcall for uCalc
callbacks procedure MyAverage(Expr: Longword); stdcall; var x: Longint; Total:
Extended; begin Total :=
0; for x :=
1 to ucArgCount(Expr) do
Total := Total + ucArg(Expr,
x); ucReturn(Expr, Total / ucArgCount(Expr)); end; |
' The following line can be placed in PBMain() ucDefineFunction
"Native: MyAverage(x ...)", CodePtr(MyAverage) ' The following callback routine can be
placed just before PBMain() Sub MyAverage(ByVal Expr As ucExpression) Dim x As Long,
Total As Extended For x = 1
To ucArgCount(Expr) Total
= Total + ucArg(Expr, x) Next ucReturn(Expr, Total / ucArgCount(Expr)) End Sub |
' The following line can be placed in Form_Load() ucDefineFunction
"Native: MyAverage(x ...)", AddressOf
MyAverage ' The following callback routine goes in a separate module, such as DemoVB.Bas Sub MyAverage(ByVal Expr As Long) Dim x As
Long, Total As Double For x = 1
To ucArgCount(Expr) Total
= Total + ucArg(Expr, x) Next ucReturn Expr, Total / ucArgCount(Expr) End Sub |
' The following lines can be placed in
Form1_Load() Static d_MyAverage As ucCallback = New ucCallback(AddressOf MyAverage) ucDefineFunction("Native: MyAverage(x ...)", d_MyAverage) ' The following callback routine goes in a separate module, such as DemoNET.vb Sub MyAverage(ByVal Expr As Integer) Dim x As
Integer, Total As Double For x = 1
To ucArgCount(Expr) Total
= Total + ucArg(Expr, x) Next ucReturn(Expr, Total / ucArgCount(Expr)) End Sub |
Note: In version 2.96, the keyword “Native:” was not required for some compilers. Now it’s required equally by all compilers.
Example B3: Native callback string functions
The callback examples above dealt only with numeric values so far. This example here demonstrates a function that has a string argument and also returns a string. The main conceptual difference between example B1 and this one is that we have a string argument that is retrieved with ucArgStr instead of ucArg, and the function's string result is returned with ucReturnStr instead of ucReturn. The rest of it works much the same. This function returns the left-most characters of a string, which is supplied as the first argument. The second argument determines the number of characters to return. For instance MyLeft("Hello World", 2) would return "He", and MyLeft("Hello World", 7) would return "Hello W".
// The following line can be placed in
TForm1::FormCreate() ucDefineFunction("Native:
MyLeft(Text As String,
Count) As String", MyLeft); // The following callback routine can
be placed just before TForm1::FormCreate() // IMPORTANT: Remember to use _stdcall for your uCalc
callbacks void _stdcall MyLeft(DWORD Expr) { AnsiString MyString; MyString = ucArgStr(Expr, 1); // Notice that ".c_str()"
is appended to the end of the returned AnsiString
variable ucReturnStr(Expr, MyString.SubString(1, ucArg(Expr, 2)).c_str()); } |
// The following line can be placed in
Form1_Load() // The keyword Native: was
omitted. It has no effect in C# uc.ucDefineFunction("MyLeft(Text As String,
Count) As String", d_MyLeft); // The following callback routine can be placed just before Form1_Load() public uCalc.ucCallback d_MyLeft = new uCalc.ucCallback(MyLeft); static void MyLeft(int Expr) { uCalc.ucReturnStr(Expr, Microsoft.VisualBasic.Strings.Left(uCalc.ucArgStr(Expr,
1), (int)uCalc.ucArg(Expr, 2))); } |
// The following line can be placed in
TForm1.FormCreate() ucDefineFunction('Native:
MyLeft(Text As String,
Count) As String', @MyLeft); // The following code can be placed
just before TForm1.FormCreate() // IMPORTANT: Always remember to
use stdcall for uCalc
callbacks procedure MyLeft(Expr: Longword); stdcall; begin ucReturnStr(Expr, copy(ucArgStr(Expr, 1), 1, Trunc(ucArg(Expr, 2)))); end; |
' The following line can be placed in PBMain() ucDefineFunction
"Native: MyLeft(Text As String, Count) As
String", CodePtr(MyLeft) ' The following callback routine can be
placed just before PBMain() Sub MyLeft(ByVal Expr As ucExpression) ucReturnStr(Expr, Left$(ucArgStr(Expr, 1), ucArg(Expr, 2))) End Sub |
' The following line can be placed in Form_Load() ucDefineFunction
"Native: MyLeft(Text As String, Count) As
String", AddressOf MyLeft ' The following callback routine goes in a separate module, such as DemoVB.Bas Sub MyLeft(ByVal Expr As Long) ucReturnStr Expr, Left$(ucArgStr(Expr, 1), ucArg(Expr, 2)) End Sub |
' The following lines can be placed in
Form1_Load() ' The keyword Native: was omitted.
It has no effect in VB.NET Static d_MyLeft As ucCallback = New ucCallback(AddressOf MyLeft) ucDefineFunction("MyLeft(Text As String,
Count) As String", d_MyLeft) ' The following callback routine goes
in a separate module, such as DemoNET.vb Sub MyLeft(ByVal Expr As Integer) ucReturnStr(Expr, Left$(ucArgStr(Expr, 1), ucArg(Expr, 2))) End Sub |
// The following line can be placed in a
main or startup function ucDefineFunction("Native:
MyLeft(Text As String,
Count) As String", MyLeft); // IMPORTANT: Remember to use _stdcall for your uCalc
callbacks void _stdcall MyLeft(DWORD Expr) { CString MyString; MyString = ucArgStr(Expr, 1); ucReturnStr(Expr, MyString.Left(ucArg(Expr, 2))); } |
Example B4: Native callback ByExpr arguments
This example demonstrates arguments passed ByExpr. If the first argument of MyIIF is non-zero, then MyIIF returns the result of evaluating the second argument, otherwise it returns the result of evaluating the 3rd argument. So, MyIIF(1, 2, 3) would return 2. MyIIF(0, 2, 3) would return 3. uCalc allows several definitions for the same function name, as long as each definition has a different number of arguments, or the arguments in the definitions are not of the same data types. So a string version of MyIIF is also defined. MyIIF(1, "True", "False") would return "True", while MyIIF(0, "True", "False") would return "False". If MyIIF() were defined using ordinary arguments, such as those of example B1, then MyIIF(1, 2, 1 / 0) would raise an error (assuming the FPU setting for division by 0 is not masked), instead of returning a 2, even though the last argument is irrelevant when the first argument is non-zero. With the second and third arguments passed ByExpr, the callback is able to select which of the two arguments to evaluate, instead of evaluating both.
Visual Basic
' The following two lines can be placed
in Form_Load() ucDefineFunction
"Native: MyIIf(cond, ByExpr
TruePart, ByExpr FalsePart)",
AddressOf MyIIF_Numeric ucDefineFunction
"Native: MyIIf(cond, ByExpr
TruePart As String, ByExpr FalsePart
As String) As String", AddressOf MyIIF_String ' The following callback routines go in a separate module, such as DemoVB.Bas Sub MyIIF_Numeric(ByVal Expr As Long) Dim
Condition As Long, TruePart As Long, FalsePart As Long Condition
= ucArg(Expr, 1) TruePart = ucArgHandle(Expr, 2) FalsePart = ucArgHandle(Expr, 3) If
Condition <> 0 _ Then ucReturn Expr, ucEvaluate(TruePart) _ Else ucReturn Expr, ucEvaluate(FalsePart) End Sub Sub MyIIF_String(ByVal Expr As Long) Dim Condition
As Long, TruePart As Long, FalsePart
As Long Condition
= ucArg(Expr, 1) TruePart = ucArgHandle(Expr, 2) FalsePart = ucArgHandle(Expr, 3) If
Condition <> 0 _ Then ucReturnStr Expr, ucEvaluateStr(TruePart) _ Else ucReturnStr Expr, ucEvaluateStr(FalsePart) End Sub |
Example B5: Native callback ByHandle argument
The following example demonstrates the use of an argument that is passed ByHandle. The TypeName() function defined below takes the handle of the argument, and returns the name of its data type. The argument can be an item, such as a function or variable, or it can be any expression.
Note: uCalc() and uCalcStr() are not supported as part of the math parser itself.
Visual Basic
' The following line can be placed in Form_Load() ucDefineFunction
"Native: TypeName(ByHandle x As AnyType) As String", AddressOf
Func_TypeName ' Create a command button named
Command1 and place the following in Command1_Click() ucDefineVariable
"x As Long" ucDefineVariable
"y As Byte" Print ucEvalStr("TypeName(x)") ' Returns "Long" Print ucEvalStr("TypeName(y)") ' Returns "Byte" Print ucEvalStr("TypeName('Hello World')") ' Returns
"String" Print ucEvalStr("TypeName(x + y)") ' Returns "Double" (some
compilers will return "Extended") ' x
+ y is converted to double because there are no native integer operators. ' Native
operators of any type, however, can be created if necessary. ' The following callback routine goes in a separate module, such as DemoVB.Bas Sub Func_TypeName(ByVal Expr As Long) Dim ArgHandle As Long Dim TypeHandle As Long Dim TypeName As String ArgHandle = ucArgHandle(Expr, 1) TypeHandle = uCalc(uc_GetItemData, "", ArgHandle,
uc_DataType) TypeName = uCalcStr(uc_GetItemData,
"", TypeHandle, uc_SymbolName) ucReturnStr Expr, TypeName End Sub |
See the following examples as well:
Local variable, and ByHandle argument (Summation)
Local variable, and ByHandle argument (Equation Solver)
The main advantage of non-native callbacks is that you are not required to adapt the callback in any way for it to work with uCalc. This means, for instance, that you can attach a uCalc function definition directly to the address of a Windows API routine, or a routine in a pre-existing DLL that was not created with uCalc in mind. Or you can attach it to ordinary routines in your source code without having to modify them or add any uCalc code to it. The disadvantage is that this requires some additional overhead, which makes it slower than native routines. Also, non-native callbacks can only handle functions with a fixed number of arguments. Since C# and VB.NET use delegates for callbacks, those two compilers cannot use non-native uCalc callbacks (at least not directly).
The actual callback function can be defined in almost any way. The main considerations are that arguments that are defined ByRef should be received by reference in your callback, and those that are ByVal, should be received by value. These are not interchangeable. In the uCalc definition, the data types for the function arguments, as well as the function's return value, must match those of the callback routine. Unlike errors in a self-contained definition, which uCalc can detect, uCalc has absolutely no way of knowing whether a function's arguments and return type in your definition correctly match those found in your callback, and whether you remembered to use stdcall (if you're using Delphi or C++). A mismatch may cause the callback to malfunction in subtle (and sometimes not-so-subtle) ways.
If you define a uCalc argument with ByExpr, or ByHandle, the callback should receive a 32-bit integer (Long, Dword, or equivalent) by value for the handle (regardless of the actual type of the argument).
Example C1: A non-native callback
This example demonstrates a non-native function that has a mix of arguments of various numeric types, and that are passed ByVal or ByRef. NonNativeFunc() simply adds up the three numeric arguments and returns a result. So NonNativeFunc(3, 4, 5) returns 12.
// The following line can be placed in
TForm1::FormCreate() ucDefineFunction("NonNativeFunc(ByVal a As Double,
ByRef b As Long, ByVal c
As Byte) As Extended", NonNativeFunc); // The following callback routine can
be placed just before TForm1::FormCreate() // IMPORTANT: Always remember to
use stdcall for uCalc
callbacks (in C++) long double _stdcall NonNativeFunc(double a, long& b, byte c) { return a
+ b + c; } |
C#
You cannot create non-native callbacks with C#.
// The following line can be placed in
TForm1.FormCreate() ucDefineFunction('NonNativeFunc(ByVal a As
Double, ByRef b As Long, ByVal
c As Byte) As Extended', @NonNativeFunc); // The following code can be placed
just before TForm1.FormCreate() // IMPORTANT: Always remember to use stdcall for uCalc callbacks function NonNativeFunc(a:
Double; var b: Longint;
c: Byte): Extended; stdcall; begin NonNativeFunc := a + b + c; end; |
' The following line can be placed in PBMain() ucDefineFunction
"NonNativeFunc(ByVal
a As Double, ByRef b As Long, ByVal
c As Byte) As Extended", CodePtr(NonNativeFunc) ' The following callback routine can be placed just before PBMain() Function NonNativeFunc(ByVal a As Double, ByRef b As
Long, ByVal c As Byte) As Extended NonNativeFunc = a + b + c End Function |
' The following line can be placed in Form_Load() ucDefineFunction
"NonNativeFunc(ByVal
a As Double, ByRef b As Long, ByVal
c As Byte) As Double", AddressOf NonNativeFunc ' The following callback routine goes in a separate module, such as DemoVB.Bas Function NonNativeFunc(ByVal a As Double, ByRef b As
Long, ByVal c As Byte) As Double NonNativeFunc = a + b + c End Function |
Visual Basic .NET
You cannot create non-native callbacks with VB.NET.
// IMPORTANT: Remember to use _stdcall for your uCalc
callbacks long double _stdcall NonNativeFunc(double a, long& b, byte c) { return a
+ b + c; } // The following can be placed in
main() ucDefineFunction("NonNativeFunc(ByVal a As
Double, ByRef b As Long, ByVal
c As Byte) As Extended", NonNativeFunc); |
Example C2: Strings in non-native functions
Dealing with numeric types in non-native routines is pretty straightforward. Dealing with strings in non-native routines, however, can be pretty tricky. A variable of given numeric type typically has a pre-set size, which stays the same, and remains in the same memory location. On the other hand, some strings types can have a different but fixed size for different variables. Or another string type might allow the same variable to change size. The string data may be at a fixed memory location, or it may change when the data changes. A Double usually represents the same numeric type throughout the various supported compilers, whereas a String in one compiler might be different from a String in another compiler. A string might take one byte per character, or it might take several. Special consideration must be given for the lifespan of the return string, etc. For this reason, when possible, it may be best to deal with native callbacks instead, especially when strings are involved. If you must use strings in non-native callbacks, special care should be taken.
This example demonstrates a function in which one of the arguments is a string. This function also returns a string. Note how different compilers use different kinds of strings. For instance, String in uCalc matches String in PowerBASIC, while WideString in uCalc is what matches String in Visual Basic. uCalc's String arg corresponds with Delphi's AnsiString, while the return value uses PChar. In VC++ you’re dealing with const char* and LPTCSR. Special string-related keywords to take note of are highlighted in color. StringRepeat(MyString, n) repeats MyString n number of times. So StringRepeat("Ha ", 3) would return "Ha Ha Ha ".
// The following line can be placed in TForm1::FormCreate() ucDefineFunction("StringRepeat(ByRef MyString As String,
ByVal Count As Long) As String",
StringRepeat); // The following callback routine can
be placed just before TForm1::FormCreate() // IMPORTANT: Always remember to
use stdcall for uCalc
callbacks (in C++) char*
_stdcall StringRepeat(AnsiString& MyString,
long Count) { long x; static AnsiString TotalString; TotalString = ""; for(x =
1; x <= Count; x++) TotalString = TotalString + MyString; return TotalString.c_str(); } |
C#
You cannot create non-native callbacks with C#.
// The following line can be placed in
TForm1.FormCreate() ucDefineFunction('StringRepeat(ByRef MyString As String,
ByVal Count As Long) As String',
@StringRepeat); // The following code can be placed
just before TForm1.FormCreate() // IMPORTANT: Always remember to use stdcall for uCalc callbacks var
TotalString: AnsiString; function StringRepeat(var MyString: AnsiString;
Count: Longint): PChar; stdcall; var x: Longint; begin TotalString := ''; for x :=
1 To Count do TotalString := TotalString
+ MyString; StringRepeat := PChar(TotalString); end; |
' The following line can be placed in PBMain() ucDefineFunction
"StringRepeat(ByRef MyString As String,
ByVal Count As Long) As String",
CodePtr(StringRepeat) ' The following callback routine can be placed just before PBMain() Function StringRepeat(ByRef MyString As String, ByVal Count
As Long) As String Dim x As
Long, TotalString As String For x = 1
To Count TotalString = TotalString + MyString Next StringRepeat = TotalString End Function |
' The following line can be placed in Form_Load() ' Notice here that uCalc's
WideString is used instead of String ucDefineFunction
"StringRepeat(ByRef MyString As WideString, ByVal Count
As Long) As WideString",
AddressOf StringRepeat ' The following callback routine goes in a separate module, such as DemoVB.Bas Function StringRepeat(ByRef MyString As String, ByVal Count
As Long) As String Dim x As
Long, TotalString As String For x = 1
To Count TotalString = TotalString + MyString Next StringRepeat = TotalString End Function |
Visual Basic .NET
You cannot create non-native callbacks with VB.NET.
// IMPORTANT: Remember to use _stdcall for your uCalc
callbacks char*
_stdcall StringRepeat(const char* MyString,
long Count) { long x; static CString TotalString; TotalString = ""; for(x =
1; x <= Count; x++) TotalString = TotalString + MyString; return TotalString.GetBuffer(80);
} // The following can be placed in
main() ucDefineFunction("StringRepeat(ByRef MyString As LPCSTR,
ByVal Count As Long) As String",
StringRepeat); |
Example C3: Non-native callback with ByExpr arguments
This example is a non-native version of the string IIF function from Example B4. nn_MyIIf(1, "T", "F") would return "T". nn_MyIIf(0, "T", "F") would return "F". Notice that although the last two arguments are strings, the callback must receive them as 32-bit values, since they are passed ByExpr. The same is true for arguments that are passed ByHandle.
Non-native callbacks are supported in order to accommodate callback routines that were designed without uCalc in mind. Since ByExpr and ByHandle are unique to uCalc, it may make better sense to use those with native callbacks instead.
Visual Basic
' The following line can be placed in Form_Load() ' Notice here that uCalc's
WideString is used instead of String for the return value ucDefineFunction
"nn_MyIIf(ByVal cond As Long, ByExpr TruePart
As String, ByExpr
FalsePart As String) As WideString", AddressOf
nn_MyIIFStr ' The following callback routine goes in a separate module, such as DemoVB.Bas Function nn_MyIIFStr(ByVal cond As Long, ByVal TruePart As Long, ByVal FalsePart As Long) As
String If cond Then nn_MyIIFStr = ucEvaluateStr(TruePart) Else nn_MyIIFStr = ucEvaluateStr(FalsePart) End Function |
Remarks
New or enhanced in version 3.0
Note:
· The previous help file showed the Bootstrap keyword at the end of the definition. In 3.0, Bootstrap must precede the rest of the definition.
· uCalc() and uCalcStr() used in example B5 are no longer supported as part of the math parser.
·
Note: In version 2.96, the keyword Native: was not required for some
compilers when defining native callbacks.
Now it’s required equally by all compilers.
New or enhanced in version 2.96
Note: ucParam, ucParamStr, and ucParamCount were renamed ucArg, ucArgStr, and ucArgCount in version 3.0.
Issues related to version 2.96
New or enhanced in version 2.9+
Issues for users migrating from version 2.0
ucDefineFunction "FunctionName(ArgCount)", CodeAddress
Functions defined this way were faster than regular callback definitions. However, you were limited to two arguments; and they could only be numeric. Now, use native callbacks for speed.