uCalc API Version: 2.1.3-preview.2 Released: 6/16/2026
Warning
uCalc API Preview Release Notice:The documentation describes the intended behavior of the API. The current preview build contains incomplete features, unoptimized performance, and is subject to breaking changes.
Binding to Native Code with Callbacks
Product:
Class:
Learn how to expose high-performance native host application code to the uCalc expression engine using callback functions.
Remarks
🤝 Binding to Native Code with Callbacks
While defining simple functions as inline expressions is fast and convenient, the true power of uCalc's extensibility comes from callback binding. This feature creates a seamless bridge between the flexible uCalc scripting environment and your high-performance, native host application code (C#, C++, VB.NET).
A callback allows an expression to call a function in your compiled code, passing it arguments and receiving a value back. This is the primary mechanism for integrating uCalc with existing application logic, accessing system resources (like files or databases), or implementing complex algorithms that would be too slow or cumbersome for an expression string.
1. The Two-Step Process
Every callback binding involves two key parts: the definition in uCalc and the implementation in your host code.
A. The Definition
You use DefineFunction or DefineOperator to tell the uCalc engine about your function. You provide the function's signature as a string, but instead of an expression body, you pass the memory address of your native callback function.
// The second argument is a pointer/delegate to our native callback.uc.DefineFunction("MyNativeFunc(x As Int)", MyNativeFunc_Callback);B. The Implementation (The Callback)
This is a function you write in your host language. When uCalc evaluates an expression containing your custom function, it calls this native code. Your callback function receives a special Callback object (typically named cb in examples) which acts as the interface for communicating with the engine.
Inside the callback, you use the cb object to:
- Get Arguments: Retrieve the values passed from the expression using methods like
cb.Arg(n),cb.ArgStr(n), etc. - Return a Value: Send a result back to the expression using
cb.Return(value),cb.ReturnStr(value), etc.
It's crucial to understand that you do not use your language's standard return keyword to send a value back to uCalc. You must use the methods on the Callback object.
2. Getting Arguments and Returning Values
Retrieving Arguments
The Callback object provides a family of type-safe methods for retrieving arguments.
cb.Arg(n): Gets the nth argument as adouble. The generic default.cb.ArgStr(n): Gets the nth argument as astring.cb.ArgInt32(n): Gets the nth argument as a 32-bit integer.cb.ArgBool(n): Gets the nth argument as a boolean.
⚠️ Important: Argument indexing is 1-based, not 0-based. The first argument is at index 1, the second at index 2, and so on.
Returning a Value
Similarly, you use type-specific methods to set the return value.
cb.Return(value): Returns adouble.cb.ReturnStr(value): Returns astring.cb.ReturnInt32(value): Returns a 32-bit integer.
The simple example below demonstrates this entire flow.
✨ Why uCalc? (Comparative Analysis)
vs. Other Scripting Engines (Lua, Python): Binding C++ or C# functions to embedded scripting engines often requires complex binding libraries (like LuaBridge or Boost.Python), code generation, or significant boilerplate to marshal data types. uCalc's callback system is designed to be lightweight and direct, making it exceptionally easy to expose a single native function to the expression engine.
vs. Native Code: The most significant advantage is runtime dynamism. In a compiled language, a function call is resolved at compile-time. uCalc allows a string—which can be created at runtime from user input or a configuration file—to execute high-performance, pre-compiled native code. This provides the flexibility of a scripting language with the speed of a native application.
Advanced Metaprogramming: uCalc's callback system goes beyond simple value passing. With advanced argument modifiers like
ByExpr,ByHandle, andByRef, your native callback can receive unevaluated expressions, inspect argument metadata, or modify variables in the caller's scope. For more details, see the Creating Custom Functions tutorial.
Examples
Defines a simple `Add` function that is implemented by a native callback to perform addition.
using uCalcSoftware;
var uc = new uCalc();
static void MyAdd(uCalc.Callback cb) {
var x = cb.Arg(1);
var y = cb.Arg(2);
cb.Return(x + y);
}
// Link the uCalc function 'Add' to the native 'MyAdd' callback.
uc.DefineFunction("Add(x, y)", MyAdd);
// Now the native code can be called from an expression.
Console.WriteLine(uc.Eval("Add(10, 5)"));
15 using uCalcSoftware; var uc = new uCalc(); static void MyAdd(uCalc.Callback cb) { var x = cb.Arg(1); var y = cb.Arg(2); cb.Return(x + y); } // Link the uCalc function 'Add' to the native 'MyAdd' callback. uc.DefineFunction("Add(x, y)", MyAdd); // Now the native code can be called from an expression. Console.WriteLine(uc.Eval("Add(10, 5)"));
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call MyAdd(uCalcBase::Callback cb) {
auto x = cb.Arg(1);
auto y = cb.Arg(2);
cb.Return(x + y);
}
int main() {
uCalc uc;
// Link the uCalc function 'Add' to the native 'MyAdd' callback.
uc.DefineFunction("Add(x, y)", MyAdd);
// Now the native code can be called from an expression.
cout << uc.Eval("Add(10, 5)") << endl;
}
15 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call MyAdd(uCalcBase::Callback cb) { auto x = cb.Arg(1); auto y = cb.Arg(2); cb.Return(x + y); } int main() { uCalc uc; // Link the uCalc function 'Add' to the native 'MyAdd' callback. uc.DefineFunction("Add(x, y)", MyAdd); // Now the native code can be called from an expression. cout << uc.Eval("Add(10, 5)") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub MyAdd(ByVal cb As uCalc.Callback)
Dim x = cb.Arg(1)
Dim y = cb.Arg(2)
cb.Return(x + y)
End Sub
Public Sub Main()
Dim uc As New uCalc()
'// Link the uCalc function 'Add' to the native 'MyAdd' callback.
uc.DefineFunction("Add(x, y)", AddressOf MyAdd)
'// Now the native code can be called from an expression.
Console.WriteLine(uc.Eval("Add(10, 5)"))
End Sub
End Module
15 Imports System Imports uCalcSoftware Public Module Program Public Sub MyAdd(ByVal cb As uCalc.Callback) Dim x = cb.Arg(1) Dim y = cb.Arg(2) cb.Return(x + y) End Sub Public Sub Main() Dim uc As New uCalc() '// Link the uCalc function 'Add' to the native 'MyAdd' callback. uc.DefineFunction("Add(x, y)", AddressOf MyAdd) '// Now the native code can be called from an expression. Console.WriteLine(uc.Eval("Add(10, 5)")) End Sub End Module
Demonstrates a practical callback that retrieves a 'host application setting', simulating I/O or access to native configuration.
using uCalcSoftware;
var uc = new uCalc();
static void GetSetting(uCalc.Callback cb) {
// Get the name of the setting to retrieve.
var settingName = cb.ArgStr(1);
// In a real app, this would read from a config file, database, or registry.
// We simulate it by evaluating another variable in the parent uCalc context.
var value = cb.uCalc.EvalStr(settingName);
cb.ReturnStr(value);
}
// Simulate a host application's configuration store using uCalc variables.
uc.DefineVariable("AppName = 'uCalc Demo'");
uc.DefineVariable("Version = '1.2.3'");
// Define the function that provides a bridge to the 'host'.
uc.DefineFunction("GetHostSetting(name As String) As String", GetSetting);
// Use the custom function to build a string from the host settings.
Console.WriteLine(uc.EvalStr("GetHostSetting('AppName') + ' v' + GetHostSetting('Version')"));
uCalc Demo v1.2.3 using uCalcSoftware; var uc = new uCalc(); static void GetSetting(uCalc.Callback cb) { // Get the name of the setting to retrieve. var settingName = cb.ArgStr(1); // In a real app, this would read from a config file, database, or registry. // We simulate it by evaluating another variable in the parent uCalc context. var value = cb.uCalc.EvalStr(settingName); cb.ReturnStr(value); } // Simulate a host application's configuration store using uCalc variables. uc.DefineVariable("AppName = 'uCalc Demo'"); uc.DefineVariable("Version = '1.2.3'"); // Define the function that provides a bridge to the 'host'. uc.DefineFunction("GetHostSetting(name As String) As String", GetSetting); // Use the custom function to build a string from the host settings. Console.WriteLine(uc.EvalStr("GetHostSetting('AppName') + ' v' + GetHostSetting('Version')"));
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call GetSetting(uCalcBase::Callback cb) {
// Get the name of the setting to retrieve.
auto settingName = cb.ArgStr(1);
// In a real app, this would read from a config file, database, or registry.
// We simulate it by evaluating another variable in the parent uCalc context.
auto value = cb.uCalc().EvalStr(settingName);
cb.ReturnStr(value);
}
int main() {
uCalc uc;
// Simulate a host application's configuration store using uCalc variables.
uc.DefineVariable("AppName = 'uCalc Demo'");
uc.DefineVariable("Version = '1.2.3'");
// Define the function that provides a bridge to the 'host'.
uc.DefineFunction("GetHostSetting(name As String) As String", GetSetting);
// Use the custom function to build a string from the host settings.
cout << uc.EvalStr("GetHostSetting('AppName') + ' v' + GetHostSetting('Version')") << endl;
}
uCalc Demo v1.2.3 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call GetSetting(uCalcBase::Callback cb) { // Get the name of the setting to retrieve. auto settingName = cb.ArgStr(1); // In a real app, this would read from a config file, database, or registry. // We simulate it by evaluating another variable in the parent uCalc context. auto value = cb.uCalc().EvalStr(settingName); cb.ReturnStr(value); } int main() { uCalc uc; // Simulate a host application's configuration store using uCalc variables. uc.DefineVariable("AppName = 'uCalc Demo'"); uc.DefineVariable("Version = '1.2.3'"); // Define the function that provides a bridge to the 'host'. uc.DefineFunction("GetHostSetting(name As String) As String", GetSetting); // Use the custom function to build a string from the host settings. cout << uc.EvalStr("GetHostSetting('AppName') + ' v' + GetHostSetting('Version')") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub GetSetting(ByVal cb As uCalc.Callback)
'// Get the name of the setting to retrieve.
Dim settingName = cb.ArgStr(1)
'// In a real app, this would read from a config file, database, or registry.
'// We simulate it by evaluating another variable in the parent uCalc context.
Dim value = cb.uCalc.EvalStr(settingName)
cb.ReturnStr(value)
End Sub
Public Sub Main()
Dim uc As New uCalc()
'// Simulate a host application's configuration store using uCalc variables.
uc.DefineVariable("AppName = 'uCalc Demo'")
uc.DefineVariable("Version = '1.2.3'")
'// Define the function that provides a bridge to the 'host'.
uc.DefineFunction("GetHostSetting(name As String) As String", AddressOf GetSetting)
'// Use the custom function to build a string from the host settings.
Console.WriteLine(uc.EvalStr("GetHostSetting('AppName') + ' v' + GetHostSetting('Version')"))
End Sub
End Module
uCalc Demo v1.2.3 Imports System Imports uCalcSoftware Public Module Program Public Sub GetSetting(ByVal cb As uCalc.Callback) '// Get the name of the setting to retrieve. Dim settingName = cb.ArgStr(1) '// In a real app, this would read from a config file, database, or registry. '// We simulate it by evaluating another variable in the parent uCalc context. Dim value = cb.uCalc.EvalStr(settingName) cb.ReturnStr(value) End Sub Public Sub Main() Dim uc As New uCalc() '// Simulate a host application's configuration store using uCalc variables. uc.DefineVariable("AppName = 'uCalc Demo'") uc.DefineVariable("Version = '1.2.3'") '// Define the function that provides a bridge to the 'host'. uc.DefineFunction("GetHostSetting(name As String) As String", AddressOf GetSetting) '// Use the custom function to build a string from the host settings. Console.WriteLine(uc.EvalStr("GetHostSetting('AppName') + ' v' + GetHostSetting('Version')")) End Sub End Module
Internal Test: A variadic function that sums only the arguments matching a specific data type name, testing argument introspection capabilities.
using uCalcSoftware;
var uc = new uCalc();
static void SumIfType(uCalc.Callback cb) {
var typeName = cb.ArgStr(1);
double total = 0;
string totalStr = "";
var i = 0;
// Loop through all arguments starting from the second one.
for ( i = 2; i <= cb.ArgCount(); i++) {
var item = cb.ArgItem(i);
// Check if the argument's data type name matches.
if (item.DataType.Name == "double") {
total = total + item.Value();
} else if (item.DataType.Name == "string") {
totalStr = totalStr + item.ValueStr();
}
}
if (typeName == "double") {
totalStr = total.ToString();
}
cb.ReturnStr(totalStr);
}
// Define the variadic function.
uc.DefineFunction("SumIfType(typeName As String, ByHandle other As AnyType ...) As String", SumIfType);
// This call will sum only the double values (5.0 and 10.123456), ignoring the integer.
Console.WriteLine(uc.EvalStr("SumIfType('double', 5.0, 'Hello ', 10.123456, 'world!')"));
// This call will concatinate only the string values.
Console.WriteLine(uc.EvalStr("SumIfType('string', 5.0, 'Hello ', 10.123456, 'world!')"));
15.123456
Hello world! using uCalcSoftware; var uc = new uCalc(); static void SumIfType(uCalc.Callback cb) { var typeName = cb.ArgStr(1); double total = 0; string totalStr = ""; var i = 0; // Loop through all arguments starting from the second one. for ( i = 2; i <= cb.ArgCount(); i++) { var item = cb.ArgItem(i); // Check if the argument's data type name matches. if (item.DataType.Name == "double") { total = total + item.Value(); } else if (item.DataType.Name == "string") { totalStr = totalStr + item.ValueStr(); } } if (typeName == "double") { totalStr = total.ToString(); } cb.ReturnStr(totalStr); } // Define the variadic function. uc.DefineFunction("SumIfType(typeName As String, ByHandle other As AnyType ...) As String", SumIfType); // This call will sum only the double values (5.0 and 10.123456), ignoring the integer. Console.WriteLine(uc.EvalStr("SumIfType('double', 5.0, 'Hello ', 10.123456, 'world!')")); // This call will concatinate only the string values. Console.WriteLine(uc.EvalStr("SumIfType('string', 5.0, 'Hello ', 10.123456, 'world!')"));
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call SumIfType(uCalcBase::Callback cb) {
auto typeName = cb.ArgStr(1);
double total = 0;
string totalStr = "";
auto i = 0;
// Loop through all arguments starting from the second one.
for ( i = 2; i <= cb.ArgCount(); i++) {
auto item = cb.ArgItem(i);
// Check if the argument's data type name matches.
if (item.DataType().Name() == "double") {
total = total + item.Value();
} else if (item.DataType().Name() == "string") {
totalStr = totalStr + item.ValueStr();
}
}
if (typeName == "double") {
totalStr = to_string(total);
}
cb.ReturnStr(totalStr);
}
int main() {
uCalc uc;
// Define the variadic function.
uc.DefineFunction("SumIfType(typeName As String, ByHandle other As AnyType ...) As String", SumIfType);
// This call will sum only the double values (5.0 and 10.123456), ignoring the integer.
cout << uc.EvalStr("SumIfType('double', 5.0, 'Hello ', 10.123456, 'world!')") << endl;
// This call will concatinate only the string values.
cout << uc.EvalStr("SumIfType('string', 5.0, 'Hello ', 10.123456, 'world!')") << endl;
}
15.123456
Hello world! #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call SumIfType(uCalcBase::Callback cb) { auto typeName = cb.ArgStr(1); double total = 0; string totalStr = ""; auto i = 0; // Loop through all arguments starting from the second one. for ( i = 2; i <= cb.ArgCount(); i++) { auto item = cb.ArgItem(i); // Check if the argument's data type name matches. if (item.DataType().Name() == "double") { total = total + item.Value(); } else if (item.DataType().Name() == "string") { totalStr = totalStr + item.ValueStr(); } } if (typeName == "double") { totalStr = to_string(total); } cb.ReturnStr(totalStr); } int main() { uCalc uc; // Define the variadic function. uc.DefineFunction("SumIfType(typeName As String, ByHandle other As AnyType ...) As String", SumIfType); // This call will sum only the double values (5.0 and 10.123456), ignoring the integer. cout << uc.EvalStr("SumIfType('double', 5.0, 'Hello ', 10.123456, 'world!')") << endl; // This call will concatinate only the string values. cout << uc.EvalStr("SumIfType('string', 5.0, 'Hello ', 10.123456, 'world!')") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub SumIfType(ByVal cb As uCalc.Callback)
Dim typeName = cb.ArgStr(1)
Dim total As Double = 0
Dim totalStr As String = ""
Dim i = 0
'// Loop through all arguments starting from the second one.
For i = 2 To cb.ArgCount()
Dim item = cb.ArgItem(i)
'// Check if the argument's data type name matches.
If item.DataType.Name = "double" Then
total = total + item.Value()
ElseIf item.DataType.Name = "string" Then
totalStr = totalStr + item.ValueStr()
End If
Next
If typeName = "double" Then
totalStr = total.ToString()
End If
cb.ReturnStr(totalStr)
End Sub
Public Sub Main()
Dim uc As New uCalc()
'// Define the variadic function.
uc.DefineFunction("SumIfType(typeName As String, ByHandle other As AnyType ...) As String", AddressOf SumIfType)
'// This call will sum only the double values (5.0 and 10.123456), ignoring the integer.
Console.WriteLine(uc.EvalStr("SumIfType('double', 5.0, 'Hello ', 10.123456, 'world!')"))
'// This call will concatinate only the string values.
Console.WriteLine(uc.EvalStr("SumIfType('string', 5.0, 'Hello ', 10.123456, 'world!')"))
End Sub
End Module
15.123456
Hello world! Imports System Imports uCalcSoftware Public Module Program Public Sub SumIfType(ByVal cb As uCalc.Callback) Dim typeName = cb.ArgStr(1) Dim total As Double = 0 Dim totalStr As String = "" Dim i = 0 '// Loop through all arguments starting from the second one. For i = 2 To cb.ArgCount() Dim item = cb.ArgItem(i) '// Check if the argument's data type name matches. If item.DataType.Name = "double" Then total = total + item.Value() ElseIf item.DataType.Name = "string" Then totalStr = totalStr + item.ValueStr() End If Next If typeName = "double" Then totalStr = total.ToString() End If cb.ReturnStr(totalStr) End Sub Public Sub Main() Dim uc As New uCalc() '// Define the variadic function. uc.DefineFunction("SumIfType(typeName As String, ByHandle other As AnyType ...) As String", AddressOf SumIfType) '// This call will sum only the double values (5.0 and 10.123456), ignoring the integer. Console.WriteLine(uc.EvalStr("SumIfType('double', 5.0, 'Hello ', 10.123456, 'world!')")) '// This call will concatinate only the string values. Console.WriteLine(uc.EvalStr("SumIfType('string', 5.0, 'Hello ', 10.123456, 'world!')")) End Sub End Module