Использовать Lua c С++ легче, чем вы думаете. Tutorial по LuaBridge
Данная статья — перевод моего туториала, который я изначально писал на английском. Однако этот перевод содержит дополнения и улучшения по сравнению с оригиналом.Туториал не требует знания Lua, а вот C++ нужно знать на уровне чуть выше базового, но сложного кода здесь нет.Когда-то я написал статью про использование Lua с C++ с помощью Lua C API. В то время, как написать простой враппер для Lua, поддерживающий простые переменные и функции, не составляет особого труда, написать враппер, который будет поддерживать более сложные вещи (функции, классы, исключения, пространства имён), уже затруднительно.Врапперов для использования Lua и C++ написано довольно много. С многими из них можно ознакомиться здесь.Я протестировал многие из них, и больше всего мне понравился LuaBridge. В LuaBridge есть многое: удобный интерфейс, exceptions, namespaces и ещё много всего.Но начнём по порядку, зачем вообще использовать Lua c С++?
Зачем использовать Lua? Конфигурационные файлы. Избавление от констант, магических чисел и некоторых define’ов Данные вещи можно делать и с помощью простых текстовых файлов, но они не так удобны в обращении. Lua позволяет использовать таблицы, математические выражения, комментарии, условия, системные функции и пр. Для конфигурационных файлов это бывает очень полезно.Например, можно хранить данные в таком виде:
window = { title = «Test project», width = 800, height = 600 } Можно получать системные переменные:
homeDir = os.getenv («HOME») Можно использовать математические выражения для задания параметров:
someVariable = 2 * math.pi Скрипты, плагины, расширение функциональности программы C++ может вызывать функции Lua, а Lua может вызывать функции C++. Это очень мощный функционал, позволяющий вынести часть кода в скрипты или позволить пользователям писать собственные функции, расширяющие функциональность программы. Я использую функции Lua для различных триггеров в игре, которую я разрабатываю. Это позволяет мне добавлять новые триггеры без рекомпиляции и создания новых функций и классов в C++. Очень удобно.
Немного о Lua. Lua — язык с лицензией MIT, которая позволяет использовать его как в некоммерческих, так и в коммерческих приложениях. Lua написан на C, поэтому Lua работает на большинстве ОС, что позволяет использовать Lua в кросс-платформенных приложениях без проблем.
Установка Lua и LuaBridge Итак, приступим. Для начала скачайте Lua и LuaBridgeДобавьте include папку Lua и сам LuaBridge в Include Directories вашего проектаТакже добавьте lua52.lib в список библиотек для линковки.
Создайте файл script.lua со следующим содержанием:
— script.lua testString = «LuaBridge works!» number = 42 Добавьте main.cpp (этот код лишь для проверки того, что всё работает, объяснение будет чуть ниже):
// main.cpp
#include
LuaBridge works! And here’s our number:42
Примечание: если программа не компилируется и компилятор жалуется на ошибку «error C2065: «lua_State»: undeclared identifier» в файле LuaHelpers.h, то вам нужно сделать следующее:1) Добавьте эти строки в начало файла LuaHelpers.h
extern «C» { # include «lua.h» # include «lauxlib.h» # include «lualib.h» }
2) Измените 460ую строку Stack.h с этого: lua_pushstring (L, str.c_str (), str.size ()); На это: lua_pushlstring (L, str.c_str (), str.size ()); Готово!
А теперь подробнее о том, как работает код.
Включаем все необходимые хэдеры:
#include
using namespace luabridge; Создаём lua_State
lua_State* L = luaL_newstate (); Открываем наш скрипт. Для каждого скрипта не нужно создавать новый lua_State, можно использовать один lua_State для множества скриптов. При этом нужно учитывать коллизию переменных в глобальном нэймспейсе. Если в script1.lua и script2.lua будут объявлены переменные с одинаковыми именами, то могут возникнуть проблемы
luaL_dofile (L, «script.lua»); Открываем основные библиотеки Lua (io, math, etc.) и вызываем основную часть скрипта (т.е. если в скрипте были прописаны действия в глобальном нэймспейсе, то они будут выполнены)
luaL_openlibs (L); lua_pcall (L, 0, 0, 0); Создаём объект LuaRef, который может хранить себе всё, что может хранить переменная Lua: int, float, bool, string, table и т.д.
LuaRef s = getGlobal (L, «testString»); LuaRef n = getGlobal (L, «number»); Преобразовать LuaRef в типы C++ легко:
std: string luaString = s.cast
Что, если скрипт Lua не найден? if (luaL_loadfile (L, filename.c_str ()) || lua_pcall (L, 0, 0, 0)) { … // скрипт не найден } Что, если переменная не найдена? Переменная может быть не объявлена, либо её значение — nil. Это легко проверить с помощью функции isNil ()
if (s.isNil ()) { std: cout << "Variable not found!" << std::endl; } Переменная не того типа, который мы ожидаем получить
Например, ожидается, что переменная имет тип string, тогда можно сделать такую проверку перед тем как делать каст:
if (s.isString ()) {
luaString = s.cast
Создайте script.lua с таким содержанием:
window = { title = «Window v. 0.1», width = 400, height = 500 } Код на C++, позволяющий получить данные из этого скрипта:
LuaRef t = getGlobal (L, «window»);
LuaRef title = t[«title»];
LuaRef w = t[«width»];
LuaRef h = t[«height»];
std: string titleString = title.cast
Как видите, можно получать различные элементы таблицы, используя оператор []. Можно писать короче:
int width = t[«width»].cast
t[«width»] = 300 Это не меняет значение в скрипте, а лишь значение, которое содержится в ходе выполнения программы. Т.е. происходит следующее:
int width = t[«width»].cast
Пусть теперь таблица выглядит так:
window = { title = «Window v. 0.1», size = { w = 400, h = 500 } } Как можно получить значение window.size.w? Вот так:
LuaRef t = getGlobal (L, «window»);
LuaRef size = t[«size»];
LuaRef w = size[«w»];
int width = w.cast
void printMessage (const std: string& s) { std: cout << s << std::endl; } И напишем вот это в скрипте на Lua:
printMessage («You can call C++ functions from Lua!») Затем мы регистрируем функцию в C++
getGlobalNamespace (L). addFunction («printMessage», printMessage); Примечание 1: это нужно делать до вызова «luaL_dofile», иначе Lua попытается вызвать необъявленную функциюПримечание 2: Функции на C++ и Lua могут иметь разные имена
Данный код зарегистрировал функцию в глобальном namespace Lua. Чтобы зарегистрировать его, например, в namespace «game», нужно написать следующий код:
getGlobalNamespace (L). beginNamespace («game») .addFunction («printMessage», printMessage) .endNamespace (); Тогда функцию printMessage в скриптах нужно будет вызывать данным образом:
game.printMessage («You can call C++ functions from Lua!») Пространства имён в Lua не имеют ничего общего с пространствами имён C++. Они скорее используются для логического объединения и удобства.
Теперь вызовем функцию Lua из 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 can still call C++ functions from Lua functions!Result:9
Разве не замечательно? Не нужно указывать LuaBridge сколько и каких аргументов у функции, и какие значения она возвращает.Но есть одно ограничение: у одной функции Lua не может быть более 8 аргументов. Но это ограничение легко обойти, передав таблицу, как аргумент.
Если вы передаёте в функцию больше аргументов, чем требуется, LuaBridge молча проигнорирует их. Однако, если что-то пойдёт не так, то LuaBridge сгенерирует исключение LuaException. Не забудьте словить его! Поэтому рекомендуется окружать код блоками try/catch
Вот полный код примера с функциями:
— 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