
Lua это мощный, быстрый, легкий, встраиваемый язык сценариев. С его помощью можно легко и быстро добавить поддержку скриптинга в вашу программу.
Это может понадобиться в тех случаях, когда вы хотите дать возможность пользователям производить самостоятельную донастройку (кастомизацию) вашей программы, когда вы не хотите перекомпилировать весь проект, при внесении каких-либо изменений в логику работы программы, либо хотите разделить работу над движком и работу над логикой между разработчиками (например, при написании игр).
В этой статье, с помощью простой программы, я хочу показать пример встраивания Lua в ваш проект.
Примеров программ, которые используют Lua достаточно много. Далеко не полный список программ, использующих Lua, можно посмотреть здесь Lua Wiki и здесь Wikipedia
Я приведу пример простой программы, которая принимает в качестве аргументов путь к директории, перечисляет файлы и подкаталоги, передает их в скрипт. Скрипт, с помощью регулярных выражений ищет соответствие в путях, передаваемых файлов и, если находит, переименовывает файл согласно определенным правилам.
Пример я создавал в Visual Studio под Windows. Несмотря на это, приведенный код, за исключением нескольких функций (перечисление файлов, переименование файла), специфичных для Windows, после небольшой адаптации будет работать и на других платформах, т.к. Lua является кроссплатформенным языком сценариев.
Начнем с того, что посетим официальный сайт и скачаем Lua для своей платформы. Для Windows подойдет вот этот архив, включающий в себя библиотеки линковщика, динамические библиотеки и заголовочные файлы Lua.
Распаковываем, указываем в настройках Visual Studio пути к заголовочным файлам и библиотекам линковщика (Tools -> Options -> Projects and Solutions -> VC++ Directories).
Если кратко, по шагам, то добавление Lua скриптинга в проект происходит в несколько шагов:
- Добавление в проект заголовочных файлов и библиотеки компоновщика
- Инициализация виртуальной машины Lua
- Реализация и регистрация экспортируемых функций
Создадим консольный проект. Назвать его можно как угодно. Первым делом добавим заголовочные файлы:
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
Почему extern «C»? Lua написан на ANSI C, если попытаться включить файлы без extern «C» то мы получим множество ошибок, таких как:
unresolved external symbol "char const * __cdecl lua_tolstring(struct lua_State *,int,unsigned int *)" (?lua_tolstring@@YAPBDPAUlua_State@@HPAI@Z)
Это вызвано тем, что соглашения о вызовах в C отличаются от соглашений в C++.
Не забудем подключить библиотеку линковщика:
#pragma comment(lib, "lua51.lib")
Теперь необходимо объявить и инициализировать экземпляр Lua интерпретатора.
// Объявляем
lua_State *g_LuaVM = NULL;
int _tmain(int argc, _TCHAR* argv[])
{
// Инициализируем экземпляр
g_LuaVM = lua_open();
...
// Закрываем
lua_close(g_LuaVM);
}
Теперь нам необходимо объявить и реализовать две функции, которые будут вызываться из Lua. Первая будет искать соответствие имени файла регулярному выражению:
int LuaMatchString(lua_State *luaVM)
Вторая — переименовывает файл:
int LuaRenameFile(lua_State *luaVM)
Остановимся подробнее на реализации второй:
// В качестве параметров принимает текущий путь к файлу и новое имя файла
// В случае успеха возвращает 1, иначе 0
int LuaRenameFile(lua_State *luaVM)
{
// Получаем число переданных из Lua скрипта аргументов
int argc = lua_gettop(luaVM);
// Если аргументов меньше двух - возвращаем ошибку
if(argc < 2)
{
cerr << "RenameFile - wrong number of arguments!" << endl;
// Вернем 0 в Lua скрипт
lua_pushnumber(luaVM, 0);
// Количество возвращаемых значений
return 1;
}
// Проверяем типы аргументов, если это не строки
// возвращаем 0 (признак ошибки)
if(!lua_isstring(luaVM, 1) || !lua_isstring(luaVM, 2))
{
cout << "RenameFile - invalid arguments!" << endl;
lua_pushnumber(luaVM, 0);
// Количество возвращаемых значений
return 1;
}
// Получаем значение первого аргумента, путь к файлу
// Обратите внимание - индекс первого элемента - 1, а не 0, как принято в С++
string strSource = lua_tostring(luaVM, 1);
// Получаем значение второго аргумента, новое имя файла
string strDestination = lua_tostring(luaVM, 2);
strDestination = strSource.substr(0, strSource.rfind('\\') + 1) + strDestination;
int nResult = (int)::MoveFileEx(strSource.c_str(), strDestination.c_str(), MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH);
// Возвращаем в Lua скрипт результат выполнения MoveFileEx
lua_pushnumber(luaVM, nResult);
// Количество возвращаемых значений
return 1;
}
Как видно — ничего сложного: проверяем количество переданных аргументов, проверяем тип аргументов, извлекаем, выполняем переименование файла и возвращаем результат.
Теперь нам необходимо дать Lua знать, о экспортируемых функциях, делается это просто:
lua_register(g_LuaVM, "RenameFile", LuaRenameFile);
lua_register(g_LuaVM, "MatchString", LuaMatchString);
RenameFile и MatchString это имена функций, которые будут «видны» в скрипте.
Создадим скрипт, выполняющий всю работу:
-- Вызывается для обработки имени файла
-- В качестве параметра передается полный путь к файлу
-- и да, -- это комментарии в Lua
function onFileFound(fileName)
patt = MatchString('103([0-9]{1,12}).txt', fileName)
-- Если совпадение найдено patt будет содержать
-- найденно совпадение - ([0-9]{1,12})
-- иначе nil
if patt ~= nil then
-- Переименовываем файл, заменяя 103 на XXX
-- Пример: было 103180998.txt, станет XXX180858.txt
RenameFile(fileName, 'XXX' .. patt .. '.txt')
end
end
Чтобы совсем стало понятно, привожу кусок кода, который вызывает эту функцию
// Переместить на начало стека функцию onFileFound
lua_getglobal(g_LuaVM, "onFileFound");
// Поместить следующим элементом в стек путь к найденному файлу (fileName в скрипте)
lua_pushstring(g_LuaVM, strFilePath.c_str());
// Вызвать функцию onFileFound
if(lua_pcall(g_LuaVM, 1, 0, 0) != 0)
{
// Проверить возвращенное значение,
// если это не 0, сообщить об ошибке
// lua_tostring(g_LuaVM, -1) содержит описание ошибки
cerr << "Error calling function onFileFound: " << lua_tostring(g_LuaVM, -1) << endl;
}
Осталось только загрузить скрипт из нашей программы:
BOOL LoadScript()
{
// Получение полного пути к скрипту
CHAR szScriptPath[1024] = {0};
GetModuleFileName(NULL, szScriptPath, _countof(szScriptPath) - 1);
TCHAR *szSlashPos = strrchr(szScriptPath, '\\');
if(szSlashPos != NULL)
{
szScriptPath[szSlashPos - szScriptPath + 1] = '\0';
}
strcat_s(szScriptPath, _countof(szScriptPath), _T("script.lua"));
// Загружаем скрипт
int s = luaL_loadfile(g_LuaVM, szScriptPath);
// Если результат загрузки не 0 - произошла ошибка
// lua_tostring(g_LuaVM, -1) содержит описание ошибки
if(s != 0)
{
cout << "Error while loading script file!" << endl << "Error: " << lua_tostring(g_LuaVM, -1) << endl;
return FALSE;
}
// Выполняем крипт
s = lua_pcall(g_LuaVM, 0, LUA_MULTRET, 0);
return TRUE;
}
Как видно встраивание Lua скриптинга в программу является, по сути, тривиальной задачей и вместе с тем дает широкое поле для творчества.
Исходники проекта
Ниже приведен список ресурсов, на которых можно почитать о Lua более подробно.
Официальный сайт
Live Demo
Русская документация
Я люблю Lua. I love Lua
Создание встраиваемых сценариев на языке Lua