|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionEver wanted to tweak a value or two without having to compile the whole code section over again? Then maybe an embedded script is your answer. This is where Lua comes in handy. Lua is an embeddable script interpreter that is lightweight and fast. You can learn more on the Lua site. BackgroundOften I have come up against the situation in programming when the need arises to tweak and twiddle in order to arrange items or to set their values. This always requires rebuilding (or edit/continuing) the code over and over again, which wastes a lot of time and hence the idea of incorporating a scripting language in the program. This way I could just change the script, save it and re-run the program over and over again saving a lot of building time. So why Lua?Lua is a powerful light-weight, small footprint programming language designed for extending applications. Along with this Lua provides its own memory management through garbage collection (which can be specialized for your application). Lua is also fully re-entrant code with no global variables. Why not use LuaBind?For those out there who do not know about this, check out LuaBind. LuaBind is great product but for me it looked too complicated. For one the code is not easy to follow where the classes and objects are. Also seeing that I wanted to integrate Lua into a wxWidgets application, using templates was a bit of a no no (you can read cross-platform issues on the wxWidgets site). Starting with LuaFirst, you can download Lua from the official site Lua.org. At the time of writing, the latest version was 5.02. Lua comes ready to be built with any ANSI C compiler. The whole library is ANSI C compliant. After downloading the code it is quite easy to build a library (I personally stick to a static library for it). Seeing that we wanted to work in the C++ world we needed to convert the headers into C++. I created a new header file: #ifndef __LUA_INC_H__ #define __LUA_INC_H__ extern "C" { #include "lualib/lua.h" #include "lualib/lauxlib.h" #include "lualib/lualib.h" } #endif // __LUA_INC_H__ This file includes all the Lua components necessary for Lua. Now you can just include the header (luainc.h) as per normal in C++ files. To start with the easiest, create a console project so that you can see the print outs from Lua. Lua problems with C++Lua is written in C, the whole Lua API is C based. Hence converting Lua into the C++ world would seem rather difficult, but Lua does provide abilities to do this. I have read many reports saying that Lua cannot be used with classes but through a bit of manipulation this can be achieved. Embedding LuaTo embed Lua is quite easy. There is a lot of documentation listing the C-API for Lua so I am not going to go through that, but just want to give you the basic points. To control the running of Lua we create a Scripting with LuaIn my application the script is there to provide "answers" to the running program. Hence the script is a collection of functions. Lua provides lots of examples of how to write scripts but a simple "hello world" would be like the following: -- Lua Hello World (test.lua) function helloWorld () io.write ("hello World") end The int iErr = 0; lua_State *lua = lua_open (); // Open Lua luaopen_io (lua); // Load io library if ((iErr = luaL_loadfile (lua, "test.lua")) == 0) { // Call main... if ((iErr = lua_pcall (lua, 0, LUA_MULTRET, 0)) == 0) { // Push the function name onto the stack lua_pushstring (lua, "helloWorld"); // Function is located in the Global Table lua_gettable (lua, LUA_GLOBALSINDEX); lua_pcall (lua, 0, 0, 0); } } lua_close (lua); You should now see "hello World" printed on the console. OK, now we know how to call Lua functions. Now we will make Lua call C functions. What we have to do is register the function with Lua. For simplicity we create another printing function: static int printMessage (lua_State *lua) { assert (lua_isstring (lua,1)); const char *msg = lua_tostring (lua, 1); // debug output cout << "script: " << msg << endl; return 0; } ... luaopen_io (lua); // Load io library // setup global printing (trace) lua_pushcclosure (lua, printMessage, 0); lua_setglobal (lua, "trace"); ... And our script becomes: -- Lua Hello World (test.lua) function helloWorld () io.write ("hello World") trace ("trace working now :)") end You will now see "hello Worldscript: trace working now :)" printed out. So how does this help us? Getting into C++We have now seen how to get Lua call C functions but C is a long way from C++. The major thing about calling a C++ function is that you have to know the object you are referring to (i.e. Firstly our script changes to show the existence of the "this" table. The script does become slightly more complicated but is still easy enough to code. The major difference is that the functions now live in the "this" table rather than in the global table. The C++ code takes care of which "this" table is in operation. Also each script function has to pass the "this" table around if they want to access non-global functions. Our Hello World now becomes: function this.helloWorld (this) io.write ("hello World") trace ("trace working now :)") end For this you will see that the script can still call the global C functions. To start the C++ part, we need a few helpers to get us on our way. The first is a Every time a new Now what?Now that we have the theory, the trouble is how to put that into practice. Seeing that Lua can only call C functions (or static members of classes) we need a way of converting that to C++. Lua provides us with an ability to register a variable with the calling function. This allows us to be very cunning while registering our functions with Lua. In fact, we do not actually register the functions but register the index look-ups for them. Getting our hands dirtyWhen we create a lua_newtable (state); m_iThisRef = luaL_ref (state, LUA_REGISTRYINDEX); // Save the "this" table to index 0 of the "this" table CLuaRestoreStack rs (vm); lua_rawgeti (state, LUA_REGISTRYINDEX, m_iThisRef); lua_pushlightuserdata (state, (void *) this); lua_rawseti (state, -2, 0); You will notice a Now comes the compiling of files. We need to load the correct "this" table to the stack before we compile the script. In the constructor of // Save the old "this" table lua_getglobal (state, "this"); m_iOldRef = luaL_ref (state, LUA_REGISTRYINDEX); // replace it with our new one lua_rawgeti(state, LUA_REGISTRYINDEX, iRef); lua_setglobal (state, "this"); This saves the previous reference to a "this" table and loads the reference of our new one. Then in the destructor we return the stack. This is done so that the functions are all stored in the right table. Our compiling code then becomes: CLuaThis luaThis (m_vm, m_iThisRef); // Make available to // correct "this" table m_vm.RunFile (strFilename); // Compile the file After all this we come to registering those C++ functions. We always register the same C function with Lua for each C++ function. The difference is that each time we register with a unique index. This index is sent to the iMethodIdx = ++m_nMethods; // Register a function with the lua script. // Added it to the "this" table lua_rawgeti (state, LUA_REGISTRYINDEX, m_iThisRef); // Push the function and parameters lua_pushstring (state, strFuncName); lua_pushnumber (state, (lua_Number) iMethodIdx); lua_pushcclosure (state, LuaCallback, 1); lua_settable (state, -3); You will notice that the function is stored in the "this" table and not globally. So now every time you want to call a function from a script you need to refer to the table. Now say we register a function called "hello" in Lua, our script would access it by: function this.helloWorld (this) io.write ("hello World") trace ("trace working now :)") this:hello () end Using the codeCLuaScriptThe first class to consider is the CLuaVirtualMachineThe next class is class CMyScript : public CLuaScript { public: CMyScript (CLuaVirtualMachine& vm) : CLuaScript (vm) { m_iMethodBase = RegisterFunction ("hello1"); RegisterFunction ("hello2"); RegisterFunction ("hello3"); } ... int ScriptCalling (CLuaVirtualMachine& vm, int iFunctionNumber) { switch (iFunctionNumber - m_iMethodBase) { case 0: return Hello1 (vm); case 1: return Hello2 (vm); case 2: return Hello3 (vm); } return 0; } ... int Hello2 (CLuaVirtualMachine& vm) { lua_State *state = (lua_State *) vm; int iNumber = (int) lua_tonumber (state, -1); printf ("Hellow (2) -> %d\n", iNumber); return 0; } ... void HandleReturns (CLuaVirtualMachine& vm, const char *strFunc); ... }; void main (void) { CLuaVirtualMachine vm; vm.InitialiseVM (); ... CMyScript ms (vm); ms.CompileFile ("test.lua"); ... ms.SelectScriptFunction ("CountAndCall"); ms.AddParam (2); ms.Go (); ... } The othersThere are other classes as mentioned in the discussion. There is a debugging class as well, but I do not really use it much but when things go wrong it comes in handy to see where the script is going. The sample shows how it is used. History
|
||||||||||||||||||||||