Шаг 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 (она нам не нужна).

project_properties_targets

Далее нажимаем кнопку Target's build options. На вкладке Linker добавляем библиотеку lua5.1.lib.

project_properties_linker

Нажимаем Ok. Возвращаемся в закладку Targets.
Обратите внимание, что Code::blocks создал файл main.c. Это значит, что файл будет компилироваться С компилятором (а не С++). Чтобы этот файл компилировался компилятором С++ выделим этот файл в списке файлов и нажмем кнопку Selected file options. Открываем закладку Advanced. Изменяем Compiler variable с CC на CPP.

project_compiler_variable

Нажимаем 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

Ну, вроде пишет все так, как и ожидалось.
На сегодня все. Следующий урок уже пишется.
До новых встречЪ.

Все файлы можно взять здесь.

Назад
Hosted by uCoz