Skip to main content

Invoking Lua from C#

💡 PuerTS 3.0 also supports invoking Javascript and Python from C#. Each language has its own syntax — click the links to see the corresponding tutorials.

Calling via Delegates

PuerTS provides a key capability: converting Lua functions into C# delegates. With this, you can call Lua functions from the C# side.

public delegate void TestCallback(string msg);

public class TestClass
{
public TestCallback Callback;

public void TriggerCallback()
{
if (Callback != null)
{
Callback("hello_from_csharp");
}
}
}

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendLua());
env.Eval(@"
local CS = require('csharp')
-- Create a C# object
local obj = CS.TestClass()
-- Assign a Lua function to the C# delegate property
obj.Callback = function(msg)
info = msg
end
-- Trigger the callback from C# side
obj:TriggerCallback()
");
// info is now 'hello_from_csharp'
env.Dispose();
}

⚠️ Note: When assigning a Lua function to a C# delegate property, use dot syntax obj.Callback = function(...) end. When calling instance methods, use colon syntax obj:TriggerCallback().

You can also directly invoke the delegate from Lua:

-- Directly invoke the delegate from Lua
obj.Callback:Invoke('hello_from_lua')

Passing Arguments from C# to Lua

When converting a Lua function to a delegate, you can convert it to a delegate with parameters, allowing you to pass C# variables to Lua. The type conversion rules are the same as when returning variables from C# to Lua.

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendLua());
// Get a Lua function as a C# delegate via Eval
System.Action<int> LogInt = env.Eval<System.Action<int>>(@"
return function(a)
print(a)
end
");

LogInt(3); // Output: 3
env.Dispose();
}

⚠️ Important difference: Unlike Javascript, Lua's Eval requires an explicit return statement to return values. If you forget return, the C# side will receive null.

Note: If your generated delegate has value-type parameters, you need to add a UsingAction or UsingFunc declaration. See the FAQ for details.


Calling Lua and Getting Return Values from C#

Similar to the previous section, just change the Action delegate to a Func delegate.

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendLua());
// Get a Lua function that returns a value
System.Func<int, int> Add3 = env.Eval<System.Func<int, int>>(@"
return function(a)
return 3 + a
end
");

System.Console.WriteLine(Add3(1)); // Output: 4
env.Dispose();
}

You can also use Eval<T> directly to get simple return values:

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendLua());
// Directly evaluate and get the return value
int result = env.Eval<int>("return 1 + 2");
System.Console.WriteLine(result); // Output: 3

string str = env.Eval<string>("return 'hello lua'");
System.Console.WriteLine(str); // Output: hello lua
env.Dispose();
}

⚠️ Reminder: Lua requires return to return values. This is one of the biggest differences from Javascript. In JS, the last expression's value is automatically returned; in Lua, without return there is no return value.

Note: If your generated delegate has value-type parameters, you need to add a UsingAction or UsingFunc declaration. See the FAQ for details.


Error Handling in Lua

When Lua code throws an error via error(), the C# side can catch it with try-catch:

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendLua());

// Lua error will be caught as a C# exception
try
{
env.Eval("error('something went wrong')");
}
catch (Exception e)
{
Debug.Log(e.Message); // Contains: something went wrong
}

// Errors in Lua functions converted to delegates are also catchable
try
{
var foo = env.Eval<Action>(@"
return function()
error('error in function')
end
");
foo(); // This will throw
}
catch (Exception e)
{
Debug.Log(e.Message); // Contains: error in function
}

env.Dispose();
}

Environment Disposal and Delegate Lifecycle

After the Lua environment (ScriptEnv) is Dispose()d, previously converted delegates will no longer be usable. Calling a delegate from a disposed environment will throw an exception — make sure to manage the lifecycle properly.

void Start()
{
var env = new Puerts.ScriptEnv(new Puerts.BackendLua());
System.Action<string> luaFunc = env.Eval<System.Action<string>>(@"
return function(msg)
print(msg)
end
");

luaFunc("before dispose"); // OK

env.Dispose();

// ❌ This will throw an exception!
// luaFunc("after dispose");
}

Implementing MonoBehaviour in Lua

Combining all the capabilities above, we can implement MonoBehaviour lifecycle callbacks in Lua:

using System;
using Puerts;
using UnityEngine;

public class LuaBehaviour : MonoBehaviour
{
public Action LuaStart;
public Action LuaUpdate;
public Action LuaOnDestroy;

static ScriptEnv luaEnv;

void Awake()
{
if (luaEnv == null) luaEnv = new ScriptEnv(new BackendLua());

var init = luaEnv.Eval<Action<MonoBehaviour>>(@"
return function(bindTo)
-- Bind Lua functions to C# delegate properties
bindTo.LuaUpdate = function()
print('update...')
end
bindTo.LuaOnDestroy = function()
print('onDestroy...')
end
end
");

if (init != null) init(this);
}

void Start()
{
if (LuaStart != null) LuaStart();
}

void Update()
{
if (LuaUpdate != null) LuaUpdate();
}

void OnDestroy()
{
if (LuaOnDestroy != null) LuaOnDestroy();
LuaStart = null;
LuaUpdate = null;
LuaOnDestroy = null;
}
}

⚠️ Key differences between Lua and JS:

  • Lua's Eval must use return to return functions
  • Assigning delegate properties uses dot syntax: bindTo.LuaUpdate = function() ... end
  • Calling C# instance methods uses colon syntax: bindTo:SomeMethod()

Key Differences Between Lua and Javascript for C# Invocation

FeatureJavascriptLua
Eval return valuesLast expression's value is auto-returnedMust use return explicitly
Function syntax(a) => { ... } or function(a) { ... }function(a) ... end
Delegate assignmentobj.Callback = (msg) => { ... }obj.Callback = function(msg) ... end
Method callsDot syntax for all: obj.Method()Instance methods use colon: obj:Method()
Console outputconsole.log()print()
Null valuenull / undefinednil

📖 Invoking other languages from C#: Invoking Javascript | Invoking Python | Multi-Language Comparison Cheat Sheet