Шаг 3
На этом шаге мы рассмотрим, как создать на С(++) и использовать модуль
Lua. ++ я написал в скобках, поскольку для решения этой
задачи достаточно простого С. Приступим. Создаем папку
проекта. Я
назову ее cmodule. Поскольку проект тестовый, то сейчас мы не будем
городить иерархию папок и подпапок (возможность сделать это у
нас появится чуть позже, в более сложном проекте). Из
интернета
возьмем необходимые Lua библиотеки. Можно, конечно, воспользоваться
Step1 и собрать Lua самостоятельно, но в этот раз мы пойдем
более
традиционным путем и поживимся готовеньким. Идем на
www.lua.org.
Далее идем в download ->
binaries-> download. Скачиваем lua5_1_2_Win32_bin.zip.
Распаковываем
его в нашу папку проекта. Также скачиваем lua5_1_2_Win32_dll_lib.zip и
распаковываем lua5.1.lib и содержимое папки include в папку проекта.
Запускаем Code::blocks. Создаем dll проект, сохраняем его в
папке
проекта. Проект назовем красноречиво test. Меню
Project->Properties
вкладка Targets. Изменим Output filename на lua-test.dll. Снимем
галочку с Create import library (она нам не нужна).
Далее нажимаем кнопку Target's build options. На вкладке Linker
добавляем библиотеку lua5.1.lib.
Нажимаем Ok. Возвращаемся в закладку Targets.
Обратите внимание, что Code::blocks создал файл main.c. Это
значит, что файл будет компилироваться С компилятором (а не С++). Чтобы
этот файл компилировался компилятором С++ выделим этот файл в списке
файлов и нажмем кнопку Selected file options. Открываем закладку
Advanced. Изменяем Compiler variable с CC на CPP.
Нажимаем Ok.
Для того, чтобы эта Lua загрузила нашу dll как свой модуль, эта dll
должна соответствовать некоторым правилам. В частности, она должна
экспортировать функцию вида:
int
luaopen_имя_модуля(lua_State* L)
которая будет вызвана во время загрузки модуля. Внутри этой функции
вызывается функция
void luaL_register
(lua_State *L,
const char *libname,
const luaL_Reg *functionList);
которая открывает библиотеку libname. Если libname равно NULL,
то
все функции из functionList регистрируются в таблице, находящейся на
вершине стека. Если libname не равно NULL, то создается новая таблица
t, значение глобальной переменной libname = t и package.loaded[libname]
= t и в этой таблице регистрируются все функции из functionList. Если
таблица с именем package.loaded[libname] уже существует, то
используется существющуая таблица, а новая не создается (подробнее о
модели загрузки модулей я писал здесь).
Что за структура
luaL_Reg? Вот ее описание:
typedef struct
luaL_Reg {
const
char *name;
lua_CFunction func;
} luaL_Reg;
lua_CFunction
это указатель на функцию вида
int fun(lua_State* L);
Теперь определимся, какие функции мы хотим экспортировать в Lua?
Я сделаю четыре функции, которые покроют весь спектр решаемых задач.
Все остальные функции смогут быть выведены из данных по аналогии.
Первая функция fun0
- не принимает и не возвращает никаких аргументов:
static int
fun0(lua_State* L)
{
// stack:
printf("fun0\n");
return 0;
}
Число возвращаемое функцией - это количество значений, которые
возвращает функция в Lua (мы помним, что функции Lua могут возвращать
несколько значений?). Данная функция возвращает 0, то есть функция
ничего не возвращает.
Вторая функция fun1()
- принимает одно значение и возвращает одно значение:
static int
fun1(lua_State* L)
{
// stack:
string
const
char* text = lua_tostring(L, 1);
printf("fun1 argument[%s]\n", text);
lua_pushnumber(L, strlen(text)); // stack: string number
return 1;
}
Небольшое замечание. Рядом с функциями, которые изменяют сотояние Lua
стека полезно писать его новое состояние. Так будет проще вычислять
индексы элементов в стеке и не запутаться в нем самом.
Итак, что мы сделали? При вызове этой функции мы ожидаем, что нижним
элементом стека будет строка.
Нижний элемент стека имеет индекс 1 (а верхний, как мы помним, -1).
Пытаемся преобразовать этот элемент стека в строку:
const char* text =
lua_tostring(L, 1);
В случае неудачи text будет равен NULL. Но здесь мы верим в удачу.
Теперь наша функция должна что-то вернуть. Пусть это будет число -
длина переданной строки.
Отлично! Берем и кладем это число на вершину стека:
lua_pushnumber(L,
strlen(text)); // stack: string number
А затем возвращаем 1, тем самым говоря Lua о том, что на стеке
появилось одно значение.
Третья функция fun2()
принимает два значения и возвращает два значения. Можно догадаться, что
работать она будет аналогично первой. Сначала два значения будут
прочитаны из низа стека, а затем два значения будут помещены на его
вершину. Отгадайте, какое число должна вернуть функция?
static int
fun2(lua_State* L)
{
// stack:
number string
double
number = lua_tonumber(L, 1);
const
char* text = lua_tostring(L, 2);
printf("fun2 arg1[%lf] arg2[%s]\n", number, text);
lua_pushstring(L, text); // stack: number string text
lua_pushnumber(L, number); // stack: number string text number
return 2;
}
Четвертая функция funVar()
принимает произвольное число параметров и возвращает количество переданных параметров:
static int
funVar(lua_State* L)
{
// stack:
...
const int
count = lua_gettop(L);
printf("funVar arguments count %d\n", count);
for(int i
= 1; count >= i; ++i)
{
printf("funVar argument %d is ", i);
if(lua_isnumber(L, i))
{
printf("%lf\n", lua_tonumber(L, i));
}
else if(lua_isstring(L, i))
{
printf("%s\n", lua_tostring(L, i));
}
else
{
printf("invalid argument type\n");
}
}
lua_pushnumber(L, count); // stack: ... count
return 1;
}
Функция lua_gettop
возвращает максимальный индекс элементов в стеке. Для стеков,
которые передаются в функцю, это количество переданных аргументов. Для данной
функции число правильных типов аргументов мы ограничим двумя типами -
число и строка.
ВсёВну, все функции написаны, пора их регистрировать в нашем модуле.
Создаем массив из структур luaL_Reg.
static const
luaL_Reg test[] = {
{"Fun0",
fun0},
{"Fun1",
fun1},
{"Fun2",
fun2},
{"FunVar", funVar},
{0, 0}
};
Последним элементом массива должна быть стрктура {0, 0}, по
которой Lua поймет, что дальше ничего нет.
Наконец, заключительный штрих:
extern "C"
__declspec(dllexport) int luaopen_test(lua_State* L)
{
luaL_register(L, "test", test);
return 1;
}
Собственно функция регистрирующая наш модуль под именем test. Запускаем
сборку проекта, получаем lua-test.dll.
Пишем тест для нашего модуля. Создаем файл testmodule.lua
package.path =
'./?.lua' -- шаблон для Lua-модулей
package.cpath =
'./lua-?.dll' -- шаблон для С-модулей
require 'test'
test.Fun0()
print(test.Fun1('I
love Lua!'))
print('It is kind of
swap', test.Fun2(100, "la-la-la"))
print('Arguments',
test.FunVar(100, "aaa", 'bbb', true, 500))
Запускаем:
C:\Stuff\cmodule>lua5.1.exe testmodule.lua
получаем:
fun0
fun1 argument[I love Lua!]
11
fun2 arg1[100.000000] arg2[la-la-la]
It is kind of swap
la-la-la
100
funVar arguments count 5
funVar argument 1 is 100.000000
funVar argument 2 is aaa
funVar argument 3 is bbb
funVar argument 4 is invalid argument type
funVar argument 5 is 500.000000
Arguments 5
Ну, вроде пишет все так, как и ожидалось.
На сегодня все. Следующий урок уже пишется.
До новых встречЪ.
Все файлы можно взять здесь.
Назад