25 марта 2010

C + Lua == "Часть 1: Вводная"

Привет. Это первая часть статьи про то, как подружить C с Lua. Зачем это надо? Тут я хотел написать долгий рассказ, о том как это удобно менять часть поведения программы без перекомпиляции, но оно вам надо? Думаю не очень. Lua интерпретируемый язык программирования, он прост и лаконичен, и обладает простым API для интеграции в C, что делает его идеальным языком для игровых скриптов.

Вам наверное интересно почему именно Lua, а не Python. Ответ прост. Хотите что бы я его озвучил? Ну ладно, если вам это надо, я его озвучу. Простота API. Да, Lua проще. К тому же у Lua есть много хороших рекомендаций.

Не поймите меня не правильно, я люблю Python, это очень мощный и удобный язык. Тут дело в том что разумнее написать на C модуль для Python чем встраивать Python в C.

Давайте уже приступим от слов к делу. Вот просто пример интеграции Lua в C:

#if 0
gcc -o simple1 -llua -lm $0 ; exit
#endif
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int
main()
{
  const char *str;
  /* lua это наша связь с Lua. */
  lua_State *lua = luaL_newstate();
  if (!lua)
    {
      fprintf(stderr,
              "%s (%s : %d)\n",
              strerror(errno), __FILE__, __LINE__);
      exit(EXIT_FAILURE);
    }
  /*
    Изначально Lua не знает даже о `print`,
    поэтому необходимо инициализировать все стандартные
    библиотеки Lua для текущего состояния интерпретатора.
   */
  luaL_openlibs(lua);
  /* Выполняем строку на языку Lua. */
  if (luaL_dostring(lua, "print 'hello world!'"))
    {
      str = lua_tostring(lua, 1);
      fprintf(stderr,
              "Error: %s (%s : %d)\n",
              str, __FILE__, __LINE__);
      exit(EXIT_FAILURE);
    }
  /* Освобождаем память используемую Lua */
  lua_close(lua);
  return EXIT_SUCCESS;
}

Что бы скомпилировать программу достаточно набрать в терминале 'sh simple1.c'. Запустив ее вы увидите надпись "hello world!".

Это просто. Давайте усложним себе задачу. Выполнять код на другом языке, это здорово. Но давайте свяжем C с Lua узлом. Сразу хочу заметить что работа с Lua, напоминает работу со стеком. Я не будем вдаваться в подробности, просто примите это как факт.

#if 0
gcc -o simple2 -llua -lm $0 ; exit
#endif
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

int
print(lua_State *lua);

int
main(int argc, char** argv)
{
  int i;
  const char *str;
  lua_State *lua = luaL_newstate();
  if (!lua)
    {
      fprintf(stderr,
              "%s (%s : %d)\n",
              strerror(errno), __FILE__, __LINE__);
      exit(EXIT_FAILURE);
    }
  /*
    Это С-функция которая помещается в стек Lua.
    Прототип этой функции описан выше, перед main.
  */
  lua_pushcfunction(lua, print);
  /*
    Поместить функцию в стек, не означает дать ей имя.
    Так как в Lua не знает как называется функция, мы
    должны сами ей это сказать. Название для Lua не обязательно
    должно совпадать с названием для C.
   */
  lua_setglobal(lua, "cprint");
  /* Выполняем скрипт на языку Lua. */
  if (luaL_dofile(lua, "script.lua"))
    {
      str = lua_tostring(lua, 1);
      fprintf(stderr,
              "Error: %s (%s : %d)\n",
              str, __FILE__, __LINE__);
      exit(EXIT_FAILURE);
    }
  /*
    В файлу script.lua была объявлена функция main.
    Именно ее мы и попытаемся вызвать.
  */
  lua_getglobal(lua, "main");
  /*
    Поместив имя функции в стек, мы теперь должны
    поместить аргументы этой функции.

    lua_pushnumber - помещает в стек число, обычна под числами
    в Lua понимается тип double.
  */
  lua_pushnumber(lua, argc);
  /*
    Теперь вторым параметром нам надо передать массив.

    Массивы, как и многое другое в Lua представляются в
    виде таблиц.
  */
  lua_newtable(lua);
  for (i = 0; i < argc; ++i)
    {
      /*
        Данные в таблицу заносятся следующим образом,
        сначала заноситься индекс, потом значение.
      */
      lua_pushnumber(lua, i+1);
      lua_pushstring(lua, argv[i]);
      /* Заносим индекс и значение в таблицу. */
      lua_rawset(lua,-3);
    }
  /*
    Чтобы вызвать нашу функцию, нужно указать количество
    аргументов функции и количество возвращаемых значений.
  */
  lua_call(lua, 2, 1);
  /*
    lua_tonumber переводит значение в стеке в число.
    Сейчас в стеке как раз результат вызова функции main.
  */
  fprintf(stdout, "Lua return: %.0f\n", lua_tonumber(lua,1));
  /*
    Правилом хорошо тона является изъятия из стека ненужных
    элементов. В данном случае результата вызова функции.
  */
  lua_pop(lua, 1);
  lua_close(lua);
  return EXIT_SUCCESS;
}

int
print(lua_State *lua)
{
  int n;
  int i;
  const char *str;
  /*
    Что бы узнать сколько аргументов передастся в функцию,
    а передаваться может переменное количество аргументов,
    достаточно узнать сколько элементов в стеке Lua.
  */
  n = lua_gettop(lua);
  /*
    Так как это функция выводит все строки переданные ей
    в качестве аргументов, то если аргументов нет, то эта
    ошибка.
   */
  if (n == 0)
    {
      lua_pushstring(lua, "no arguments");
      lua_error(lua);
    }
  /*
    Хоть с Lua работа осуществляется по средствам стека,
    обращаться можно к любому элементу стека по индексу.
    Индексы в Lua начинаются с 1, а не 0, как в C.
   */
  for (i = 1; i <= n; ++i)
    {
      /* Все аргументы должны быть строками. */
      if (!lua_isstring(lua,i))
        {
          lua_pushstring(lua, "incorrect argument");
          lua_error(lua);
        }
      str = lua_tostring(lua, i);
      fprintf(stdout, "%s", str);
    }
  return 0;
}

А вот тот самый script.lua:

function main(argc, argv)
   if argc == 1 then
      return 1
   end
   cprint("argc: "..argc.."\n")
   for i = 1, argc do
      cprint("argv["..i.."]: ",argv[i],"\n")
   end
   return 0
end

Вы наверное заметили интересные закономерности в API Lua. Если вам необходимо проверить что за элемент в стеке вы используете lua_isblabla, если хотите извлечь элемент из стека, то вам необходимо lua_toblabla, а если хотите что-то положить в стек, то воспользуйтесь lua_pushblabla. Я же говорил, все просто.

За полезной информацией совету посетить сайт Lua, посмотреть в документацию, а так же полазить по wiki.

Продолжение следует...