📜 ⬆️ ⬇️

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

This article is a translation of my tutorial , which I originally wrote in English. However, this translation contains additions and improvements over the original.
Tutorial does not require knowledge of Lua, but C ++ needs to be known at a level slightly above basic, but there is no complex 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 not difficult, writing a wrapper that will support more complex things (functions, classes, exceptions, namespaces) is already difficult.
Quite a lot of vrappers for using Lua and C ++ are written. Many of them can be found here .
I tested many of them, and most of all I liked LuaBridge. There is a lot in LuaBridge: a convenient interface, exceptions, namespaces and so much more.
But let's start in order, why bother to use Lua with C ++?


Why use Lua?


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


These things can be done using simple text files, but they are not so easy to handle. Lua allows you to use tables, mathematical expressions, comments, conditions, system functions, etc. This can be very useful for configuration files.
For example, you can store data in this form:

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


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


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


Scripts, plugins, functionality extension


C ++ can call Lua functions, and Lua can call C ++ functions. This is a very powerful feature that allows you to put some 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 recompilating and creating new functions and classes in C ++. Very comfortably.

A little bit about Lua. Lua is a language with a MIT license that allows you to use it in both non-commercial and commercial applications. Lua is written in C, so Lua works on most operating systems, which allows using Lua in cross-platform applications without problems.

Installing Lua and LuaBridge



So let's get started. Start by downloading Lua and LuaBridge
Add the include Lua folder and LuaBridge itself in the Include Directories of your project
Also add lua52.lib to the list of link libraries.

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


Add main.cpp (this code is just to check that everything works, the explanation will be slightly lower):
 // main.cpp #include <LuaBridge.h> #include <iostream> 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<std::string>(); int answer = n.cast<int>(); 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 is not compiled and the compiler complains about the error “error C2065: 'lua_State': undeclared identifier” in the file LuaHelpers.h , then you need to do the following:
1) Add these lines to the beginning of the LuaHelpers.h file.
 extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } 

2) Change the 460th line to Stack.h with 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 <LuaBridge.h> #include <iostream> extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } 


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

 using namespace luabridge; 


Create lua_State

 lua_State* L = luaL_newstate(); 


Open our script. For each script, you do not need to create a new lua_State, you can use one lua_State for multiple scripts. Thus it is necessary to take into account the collision of variables in the global namespace. If variables with the same name are declared in script1.lua and script2.lua, problems may arise

 luaL_dofile(L, "script.lua"); 


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

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


Create a LuaRef object that can store everything that the 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<std::string>(); int answer = n.cast<int>(); 


Error checking and correction



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

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 check with the isNil () function.
 if (s.isNil()) { std::cout << "Variable not found!" << std::endl; } 


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

For example, it is expected that the variable has the type string, then you can do such a check before you make a caste:

 if(s.isString()) { luaString = s.cast<std::string>(); } 


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 assign keys of different types to values ​​and variables. Tables allow you to submit and receive configuration files in a beautiful and easy to read form.

Create a script.lua with this content:

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


C ++ code to retrieve 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<std::string>(); int width = w.cast<int>(); int height = h.cast<int>(); 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 the various elements of the table using the operator []. You can write shorter:
 int width = t["width"].cast<int>(); 


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<int>(); // 400 t["width"] = 300 width = t["width"].cast<int>(); // 300 


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

Let now the table looks 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<int>(); 


Functions



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


And write this in the Lua script:
 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 the “luaL_dofile” call, 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 Lua. To register it, for example, in the namespace "game", you need to write the following code:
 getGlobalNamespace(L). beginNamespace("game") .addFunction("printMessage", printMessage) .endNamespace(); 


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


Namespaces in Lua have nothing to do with C ++ namespaces. They are rather used for logical combination 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? You do not need to specify 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 restriction is easy to get around by passing the table as an argument.

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

Here is the complete code of the function example:

 -- 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 <LuaBridge.h> #include <iostream> 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 something else?



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

Source: https://habr.com/ru/post/237503/


All Articles