Using Lua with C ++ is easier than you think. Tutorial by LuaBridge

  • Tutorial
This article is a translation of my tutorial , which I originally wrote in English. However, this translation contains additions and improvements over the original.
The tutorial does not require knowledge of Lua, but C ++ needs to be known at a level slightly higher than the base, but there is no complicated code here.

I once wrote an article about using Lua with C ++ using the Lua C API . While writing a simple wrapper for Lua that supports simple variables and functions is easy, writing a wrapper that will support more complex things (functions, classes, exceptions, namespaces) is already difficult.
Quite a lot has been written about the use of Lua and C ++. Many of them can be found here .
I tested many of them, and most of all I liked LuaBridge. LuaBridge has a lot: a user-friendly interface, exceptions, namespaces and much more.
But let's start in order, why use Lua with C ++ at all?


Why use Lua?



Configuration files. Getting rid of constants, magic numbers and some define'ov


These things can be done with the help of simple text files, but they are not so convenient to use. Lua allows you to use tables, mathematical expressions, comments, conditions, system functions, etc. For configuration files, this can be very useful.
For example, you can store data in this form:

window = {
    title = "Test project",
    width = 800,
    height = 600
}


You can get system variables:
homeDir = os.getenv("HOME")


You can use mathematical expressions to set parameters:
someVariable = 2 * math.pi


Scripts, plugins, extension of program functionality


C ++ can call Lua functions, and Lua can call C ++ functions. This is a very powerful functionality that allows you to put part of the code into scripts or allow users to write their own functions that extend the functionality of the program. I use Lua functions for various triggers in the game that I am developing. This allows me to add new triggers without recompiling and creating new functions and classes in C ++. Very comfortably.

A bit about Lua. Lua is a MIT-licensed language that allows it to be used in both non-commercial and commercial applications. Lua is written in C, so Lua runs on most OSs, which allows you to use Lua in cross-platform applications without any problems.

Install Lua and LuaBridge



So let's get started. To get started, download Lua and LuaBridge.
Add the include folder Lua and LuaBridge itself in the Include Directories of your project.
Also add lua52.lib to the list of libraries for linking.

Create a script.lua file with the following contents:
-- script.lua
testString = "LuaBridge works!"
number = 42


Add main.cpp (this code is only to verify that everything works, the explanation will be a bit lower):
// main.cpp
#include 
#include 
extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}
using namespace luabridge;
int main() {
    lua_State* L = luaL_newstate();
    luaL_dofile(L, "script.lua");
    luaL_openlibs(L);
    lua_pcall(L, 0, 0, 0);
    LuaRef s = getGlobal(L, "testString");
    LuaRef n = getGlobal(L, "number");
    std::string luaString = s.cast();
    int answer = n.cast();
    std::cout << luaString << std::endl;
    std::cout << "And here's our number:" << answer << std::endl;
}


Compile and run the program. You should see the following:

LuaBridge works!
And here's our number: 42


Note : if the program does not compile and the compiler complains about the error “error C2065: 'lua_State': undeclared identifier” in the LuaHelpers.h file , then you need to do the following:
1) Add these lines to the beginning of the LuaHelpers file. h
extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}

2) Change the 460th line of Stack.h from this:
lua_pushstring (L, str.c_str(), str.size());

On this:
lua_pushlstring (L, str.c_str(), str.size());


Done!

And now more about how the code works.

We include all the necessary headers:
#include 
#include 
extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}


All functions and classes of LuaBridge are placed in the namespace luabridge, and in order not to write “luabridge” many times, I use this construction (although it is better to put it in those places where LuaBridge is used)

using namespace luabridge;


Create lua_State

lua_State* L = luaL_newstate();


We open our script. For each script, you do not need to create a new lua_State, you can use one lua_State for many scripts. In this case, it is necessary to take into account the collision of variables in the global namespace. If variables with the same names are declared in script1.lua and script2.lua, then problems may occur

luaL_dofile(L, "script.lua");


We open the main Lua libraries (io, math, etc.) and call the main part of the script (i.e. if actions were specified in the script in the global namespace, they will be executed)

luaL_openlibs(L);
lua_pcall(L, 0, 0, 0);


We create a LuaRef object that can store everything that a Lua variable can store: int, float, bool, string, table, etc.
LuaRef s = getGlobal(L, "testString");
LuaRef n = getGlobal(L, "number");


Converting LuaRef to C ++ types is easy:
std::string luaString = s.cast();
int answer = n.cast();


Checking and Correcting Errors



But some things can go wrong, and it is worth checking and processing errors. Consider the most important and common mistakes

What if the Lua script is not found?


if (luaL_loadfile(L, filename.c_str()) ||
    lua_pcall(L, 0, 0, 0)) {
    ... // скрипт не найден
}


What if a variable is not found?


The variable may not be declared, or its value is nil. This is easy to verify using the isNil () function.
if (s.isNil()) {
    std::cout << "Variable not found!" << std::endl;
}


The variable is not of the type that we expect to receive.

For example, it is expected that the variable is of type string, then you can do this check before doing the cast:

if(s.isString()) {
    luaString = s.cast();
}


Tables



Tables are not just arrays: tables are a wonderful data structure that allows you to store Lua variables of any type in them, other tables and put keys of different types in accordance with values ​​and variables. Tables allow you to present and receive configuration files in a beautiful and easy to read form.

Create a script.lua with the following content:

window = {
    title = "Window v.0.1",
    width = 400,
    height = 500
}


C ++ code that allows you to get data from this script:

LuaRef t = getGlobal(L, "window");
LuaRef title = t["title"];
LuaRef w = t["width"];
LuaRef h = t["height"];
std::string titleString = title.cast();
int width = w.cast();
int height = h.cast();
std::cout << titleString << std::endl;
std::cout << "width = " << width << std::endl;
std::cout << "height = " << height << std::endl;


You should see the following on the screen:
Window v.0.1
width = 400
height = 500


As you can see, you can get various table elements using the [] operator. You can write shorter:
int width = t["width"].cast();


You can also change the contents of the table:
t["width"] = 300


This does not change the value in the script, but only the value that is contained during the execution of the program. Those. the following happens:
int width = t["width"].cast(); // 400
t["width"] = 300
width = t["width"].cast(); // 300


To preserve the value, you need to use table serialization, but this tutorial is not about that.

Now let the table look like this:
window = {
    title = "Window v.0.1",
    size = {
        w = 400,
        h = 500
    }
}


How can I get the value of window.size.w ?
Like this:
LuaRef t = getGlobal(L, "window");
LuaRef size = t["size"];
LuaRef w = size["w"];
int width = w.cast();


Functions



Let's write a simple function in C ++
void printMessage(const std::string& s) {
    std::cout << s << std::endl;
}


And write this in a script on Lua:
printMessage("You can call C++ functions from Lua!")


Then we register the function in C ++
getGlobalNamespace(L).
   addFunction("printMessage", printMessage);


Note 1 : this must be done before calling “luaL_dofile”, otherwise Lua will try to call an undeclared function.
Note 2 : Functions in C ++ and Lua may have different names.

This code registered the function in the global namespace of Lua. To register it, for example, in namespace "game", you need to write the following code:
getGlobalNamespace(L).
    beginNamespace("game")
        .addFunction("printMessage", printMessage)
    .endNamespace();


Then the printMessage function in the scripts will need to be called in this way:
game.printMessage("You can call C++ functions from Lua!")


Lua namespaces have nothing to do with C ++ namespaces. Rather, they are used for logical integration and convenience.

Now call the Lua function from C ++
-- script.lua
sumNumbers = function(a,b)
    printMessage("You can still call C++ functions from Lua functions!")
    return a + b
end


// main.cpp
LuaRef sumNumbers = getGlobal(L, "sumNumbers");
int result = sumNumbers(5, 4);
std::cout << "Result:" << result << std::endl;


You should see the following:
You can still call C ++ functions from Lua functions!
Result: 9


Isn't it great? No need to tell LuaBridge how many and what arguments the function has and what values ​​it returns.
But there is one limitation: a single Lua function cannot have more than 8 arguments. But this limitation is easily circumvented by passing the table as an argument.

If you pass more arguments to the function than required, LuaBridge will silently ignore them. However, if something goes wrong, then LuaBridge will throw a LuaException. Do not forget to catch it! Therefore, it is recommended to surround the code with try / catch blocks.

Here is the full example code with functions:

-- script.lua
printMessage("You can call C++ functions from Lua!")
sumNumbers = function(a,b)
    printMessage("You can still call C++ functions from Lua functions!")
    return a + b
end


// main.cpp
#include 
#include 
extern "C" {
# include "lua.h"
# include "lauxlib.h"
# include "lualib.h"
}
using namespace luabridge;
void printMessage(const std::string& s) {
    std::cout << s << std::endl;
}
int main() {
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    getGlobalNamespace(L).addFunction("printMessage", printMessage);
    luaL_dofile(L, "script.lua");
    lua_pcall(L, 0, 0, 0);
    LuaRef sumNumbers = getGlobal(L, "sumNumbers");
    int result = sumNumbers(5, 4);
    std::cout << "Result:" << result << std::endl;
    system("pause");
}


What? Is there anything else?



Yes. There are some more wonderful things that I will write about in the following parts of the tutorial: classes, creating objects, the lifespan of objects ... A lot of things!
I also recommend reading this dev log , in which I talked about how I use scripts in my game, practical examples are always useful.

Also popular now: