The most exciting thing about this world is its ever changing quality.

Saturday, February 14, 2009

Lua in .Net

ALthough I have mentioned before that there are a few options to enable scripting in .Net projects, I was thinking write a interface library to wrap up the unmanaged C style Lua library into a CLR compliant module. It is actually pretty straightforward process. Some nice people have already written ref class LuaDLL to solve the marshalling between unmanaged functions to unmanaged APIs. Essentially, you could use LuaDLL directly within your .Net project. To simplify things, what I was looking for is to wrap Lua into an object oriented way, as some OO nuts like to brand it. What LuaDLL does not do is to offer an easy way allowing Lua script accessing CLR type objects.

Without too much efforts, to pack all the fancy Lua interpreter, table, function and user data into classes, and some counter-effective strings to be executed by Lua runtime to set the meta data. Here we are, a nice interface class - LuaInterface, which defeats my original plan to be the first integrator ... And, it was done long time before my consciousness.

Anyway, to use this interface could not be easier. You can access CLR objects from Lua scripts which essentially will be string format to be executable by Lua runtime and manipulated from CLR objects vice versa. What I like about this is to hook up event handler and use delegate just as easily. On the contrary, I could not locate any good summary on this subject thus I listed in this post.

  • Define delegate in C# to use Lua functions:

// Lua interpreter
private Lua lua;
// function pointers to functions in Lua
public delegate double PlusDelegate(double a, double b);

public Script()
{
lua = new Lua();
// define variables in Lua
lua["num"] = 2;
lua["str"] = "a string";
// define functions in Lua

lua.DoString(@"
function plus(x, y)
return x + y
end
");

// use functions defined in Lua
PlusDelegate plus = lua.GetFunction(typeof(PlusDelegate), "plus") as PlusDelegate;

if (IsDefinedInLua(add))
{
double res = plus(10, 2);
rtbOutput.Text = (String.Format("result: {0}", res));
}
}

private bool IsDefinedInLua(Delegate delegateOfFunction)
{
if (delegateOfFunction.Target is LuaDelegate)
return (delegateOfFunction.Target as LuaDelegate).function != null;

return false;
}

  • Define 'delegate' in Lua to use C# functions:

public void CallCSharp(string s)
{
rtbOutput.Text += ;
}
lua.RegisterFunction("callCSharp", this, this.GetType().GetMethod("CallCSharp"));

Just to be aware that the function has to be public to be registered into Lua. You could also use Lua function by instance as:

// use a Lua function by instance
LuaFunction luaFunction = lua.GetFunction("minus");
double ret = (double) luaFunction.Call(10,2).GetValue(0);


  • Implement event defined in C# from Lua, a.k.a bind events to scripted handlers.

public delegate void RaiseAttentionEventHandler(object sender, EventArgs e);
public event RaiseAttentionEventHandler RaiseAttention;

lua.DoString(@"
function clickMe (sender)
sender.Text = 'It worked'
end
");

lua.DoString(@"
newPanel = ScriptPanel('aNewPanel')
newPanel.RaiseAttention:Add(clickMe)
");
if (this.RaiseAttention != null)
RaiseAttention(sender, null);

  • Implement event defined in Lua from C# is a little twisted logic. I could not think of a good use case right now but for the sake of completeness sake, I did a little trial and error. Before moving on, you might want to refresh a little about the event support in Lua here. Another good event module implementation using C library could be found here. The easiest way I found is to register C# functions into Lua as global function and use them to handle the events in Lua. 

private void btLua_Click(object sender, EventArgs e)
{
// Implement event defined in Lua from C# and trigger it in Lua
LuaEventHandler handler = new LuaEventHandler();
lua.RegisterFunction("eventInLua", this, this.GetType().GetMethod("EventInLua"));
handler.handler = lua.GetFunction("eventInLua");
handler.handleEvent(sender, e);
}

public void EventInLua(object sender, EventArgs e)
{
rtbOutput.Text = ("Trigger from Lua ");
return;
}

No comments: