This article presents a thin wrapper around Lua, see [1], and Luabind, see [2], for .NET: with it you can embed a scripting engine with a C, C++ backbone into your .NET applications.
If you are not familiar with Lua and Luabind, a small introductory example is also given below.
Here are some quotes extracted from the Lua "about" page:
"Lua is a powerful light-weight programming language designed for extending C, C++ applications."
"Lua is a language engine that you can embed into your application. This means that, besides syntax and semantics, Lua has an API that allows the application to exchange data with Lua programs and also to extend Lua with C functions. In this sense, Lua can be regarded as a language framework for building domain-specific languages."
Usually, using Lua in an application is done with the following steps:
- Create a Lua state:
- Bind methods to Lua
- Execute Lua scripts
- Clean and deallocate state
"The Lua library is fully reentrant: it has no global variables. The whole state of the Lua interpreter (global variables, stack, etc.) is stored in a dynamically allocated structure of type lua_State
. A pointer to this state must be passed as the first argument to every function in the library, except to lua_open
, which creates a Lua state from scratch."
As mentioned above, lua_open
is used to allocate a Lua state:
lua_State* L = lua_open();
Suppose that the following method need to bound:
void my_print( const char* str )
{
printf( str );
}
First, we need to make a function wrapper for my_print
that receives a Lua state and returns an integer, the number of values it wants to return to Lua:
int my_print_lua(lua_State *L)
{
int n = lua_gettop(L);
if (n<1)
{
lua_pushstring(L, "not enough arguments");
lua_error(L);
}
my_print( lua_checkedstring( L, 1) );
return 0;
};
At last, the method is registered in Lua using:
lua_register(L, "pr", my_print_lua);
Remark: Handling the Lua stack can become tedious and error-prone. Hopefully, Luabind is here to simplify (a lot) the wrapping task.
Consider the following script:
s = 'this is a test';
pr( s );
As one can see, Lua syntax is quite straightforward. In the script, we see that the method we bound (pr
) is called. To execute this script in C, lua_dostring
is used:
const char* str = "s = 'this is a test';pr( s );";
lua_dostring(L, str);
The program will output:
this is a test
When you are finished, do not forget to call lua_close
to deallocate the Lua state:
lua_close(L);
As mentioned before, handling the Lua state is a tedious work that we would like to avoid. Luabind uses template meta-programming to simplify things for us.
Using Luabind, the previous steps become:
- Create a state:
using namespace luabind;
lua_State* L =lua_open(L);
luabind::open(L);
- Bind method:
module(L)
[
def("pr", &my_print);
];
Remarks:
- We did need to write
my_print_lua
wrapper, Luabind does this for us.
- Luabind can also bind classes.
- Check the Luabind page for more information as their documentation is really well done.
So what about executing Lua script in .NET applications? This should not be a major problem, just the matter of writing a managed C++ wrapper.
Remarks:
- A detailed class documentation can be built by running the Documentation tool on the LuaNET project.
- All the classes live in the
Lua
namespace.
The managed class State
wraps up the lua_State
structure. It handles the calls to lua_open
and lua_close
.
Here's a small example that creates a state, sets some variables and executes a script:
Note that get_Global
returns a LuaObject
which is then cast to a double
using ToDouble
.
LuaObject
enables you to set and retrieve values from Lua. Supported values are:
string
double
, int
bool
table
Since Lua is dynamically typed, you need to check the type of the LuaObject
before casting it. Otherwise, if cast fails, exceptions will be raised:
L.DoString("s='string';");
L.DoString("print('s: ' .. tostring(s));");
LuaObject s=state.get_Global("s");
s.ToString();
s.ToDouble();
If the object is a table
, LuaObject
implements the IDictionnary
interface on the table
and TableEnumerator
implements the IDictionnaryEnumerator
interface:
L.DoString("t={1,'string'};");
LuaObject t=L.get_Global("t");
System.Collections.IDictionaryEnumerator te = t.GetEnumerator();
while( te.MoveNext() )
{
System.Console.WriteLine("t...(" + te.Key + "," + te.Value +")");
};
Lua comes with a set of default APIs to handle strings, table, IO, files etc...To load these APIs, use DefaultLibrary.Load
:
Lua.Libraries.DefaultLibrary.Load( L );
Luabind is loaded using LuabindLibrary.Load
:
Lua.Libraries.LuabindLibrary.Load( L );
You can wrap up your API and load them in .NET using the same method as DefaultLibrary
or LuabindLibrary
.
Using the demo
The demo features Lua 5.0 and Luabind. You need to recompile all the projects and launch LuaNetTest.
- 8/8/2004: Initial release