Germany | Finland | Saint Petersburg | Drive

C API для Lua

Опубликовано в QLua

Описание API. Пример создания простейшей Dll.

 

Подобно OpenGL и другим библиотекам все функции в Lua начинаются с префикса lua_, а константы - с префикса LUA_.

Для работы с Lua необходимо сначала создать контекст для работы. Иногда вместо термина контекст используется термин виртуальная машина. У вас может быть несколько независимых контекстов, в каждом из которых происходит работа. Все остальные функции C API получают этот контекст как первый параметр. Для создания контекста служат следующие функции: 

lua_State *lua_open      ();
lua_State *lua_newstate  ( lua_Alloc f, void * ud );
lua_State *luaL_newstate ();

Первая из этих функция на самом деле сейчас определена как макрос, вызывающий luaL_newstate и является устаревшей функцией. Функция lua_newstate создает новое состояния и при этом получает в качестве параметров функцию выделения памяти и параметр, передаваемый этой функции при ее вызове. Функция luaL_newstate использует стандартную функцию выделения памяти, основанную на функции realloc и служит заменой lua_open.

При ошибке создания контекста возвращается значение NULL.

Тип lua_Alloc определен следующим образом: 

typedef void * (*lua_Alloc) ( void * ud, void * ptr, size_t oldSize, size_t newSize );

Для освобождения контекста служит функция lua_close:

void lua_close ( lua_State * lua );

Одной из особенностей Lua является то, что передача значений между программой на С/С++ и Lua происходит через специальный стек, каждый элемент этого стека является значением в Lua.

При вызове функции С/С++ из Lua вызываемая функция получает свой стек, независимый от предыдущих стеков и стеков других функций, активных на данный момент.

Изначально стек содержит все аргументы для функции в прямом порядке (т.е. первый аргумент помещается на стек первым), на этот же стек функция заносит возвращаемые значения.

При этом используемый стек (в отличии от традиционного понимания стека) позволяет доступ (как на чтение, так и на запись) к каждому своему элементу при помощи целочисленного индекса. Положительный индекс соответствует абсолютному положению (индексы начинается с единицы), отрицательные значения соответствуют смещению с вершины стека к его началу.

Так если стек содержит n элементов, то индекс 1 соответствует первому элементу (первому аргументу вызываемой функции), индекс 2 - второму, индекс n соответствует последнему (верхушке стека). Индекс -1 соответствует последнему элементу, -2 - предпоследнему элементу, индекс -n - первому элементу (см. рис). Индекс i является валидным (т.е. может использоваться для обращения к элементам стека) если1<=abs(i)<=n. Нулевое значение не является валидным индексом.

Рис 2. Индексация в стеке.

Вы сами должны отвечать за то, чтобы не произошло переполнение стека (размер стека фиксирован, по умолчанию он равен 20). Для явного задания размера стека служит функция lua_checkstack: 

int lua_checkstack ( lua_State * lua, int extra );

После этого вызова размер стека будет составлять не менее extra элементов. При ошибке (не удалось увеличить размер стека) возвращается ноль. Эта функция может только увеличить размер стека, но не может уменьшить.

Кроме стандартных индексов для обращения в стек можно использовать и так называемые псевдо-индексы. Псевдо-индексы соответствуют доступным значениям Lua, не находящимся на стеке.

Доступ к глобальным переменным осуществляется через псевдоиндекс LUA_GLOBALSINDEX. Текущее окружение доступно через псевдоиндекс LUA_ENVIRONINDEX.

Для доступа к глобальной переменной используется обычный механизм доступа к полям таблицы. Так для доступа к глобальной переменной x используется следующий вызов: 

lua_getfield ( lua, LUA_GLOBALSINDEX, "x" );

Этот вызов возьмет значение глобальной переменной x и добавит на вершину стека.

Как и в "маленьких мягких окошках" в Lua есть свой реестр. Только здесь это просто специальная таблица, в которой код из С/С++ может хранить значения. Для доступа к этой таблице служит псевдоиндекс LUA_REGISTRYINDEX. Не рекомендуется осуществлять запись в таблицу реестра по целочисленным ключам - они используются самой Lua.

Далее мы рассмотрим основные (наиболее часто используемые) функции для работы с Lua, а потом перейдем к практическим примерам использования Lua в программах на С/С++. 

void lua_call  ( lua_State * lua, int numArgs, int numResults );
void lua_pcall ( lua_State * lua, int numArgs, int numResults, int errFunc );

Эта функция служит для вызова функции. Перед ее вызовом следует сперва поместить вызываемую функцию на стек, а затем все передаваемые аргументы в прямом порядке (первый аргумент первым, затем второй и т.д.).

В параметре numArgs передается количество переданных аргументов. После вызова все переданные аргументы автоматически убираются со стека, а вместо них в стек заносятся результаты выполнения функции в естественном порядке.

Если значение параметра numResults не равно LUA_MULTRET, то на стеке будет оставлено ровно numResults значений. Лишние значения убираются со стека, вместо недостающих заносятся nil.

Функция lua_pcall отличается тем, что в случае ошибки просто помещает на стек сообщение об ошибке и возвращает код ошибки. Код ошибки может быть равен нулю (все в порядке) или же принимать одно из следующих значений - LUA_ERRRUN (ошибка во время выполнения), LUA_ERRMEM (ошибка выделения памяти) и LUA_ERRERR (ошибка во время выполнения обработчика ошибок).

Если errFunc равно нулю, то возвращается стандартное сообщение об ошибке. В противном случае errFunc - это индекс функции для обработки ошибок (в данной реализации это не может быть псевдоиндекс). 

void lua_getfield ( lua_State * lua, int index, const char * key );

Данная функция берет значение из стека по заданному индексу, извлекает из него поле с заданным именем и значение этого поля помещается на вершину стека.

Пусть в стеке была таблица t. Тогда следующие рисунки иллюстрируют результаты вызовов функции lua_getfield.

Рис 3. Работа функции lua_getfield.

 

 

void lua_setfield ( lua_State * lua, int index, const char * key );

Данная функция в таблице, находящуюся в стеке по заданному индексу устанавливает значение поля с заданным именем в значение с вершины стека. При этом значение снимается с вершины стека.

Рис 4. Работа функции lua_setfield

 

 

void lua_pop ( lua_State * lua, int n );

Данный вызов удаляет с вершины стека n значений.

 

void lua_pushboolean ( lua_State * lua, int value );

Функция добавляет на вершину стека логическое значение value (0 или 1).

 

void lua_pushinteger ( lua_State * lua, lua_Integer value );

Функция добавляет на вершину стека число value.

 

 

void lua_pushlstring ( lua_State * lua, const char * str, size_t len );

Функция добавляет на вершину стека копию строки с длиной len (маркер '\0' игнорируется).

 

void lua_pushnil ( lua_State * lua );

Функция помещает на вершину стека значение nil.

 

void lua_pushnumber ( lua_State * lua, lua_Number value );

Функция добавляет на вершину стека число value.

 

void lua_pushstring ( lua_State * lua, const char * str );

Функция помещает на вершину стека строку, законченную символом '\0'.

 

 

void lua_pushvalue ( lua_State * lua, int index );

Функция помещает копию значения, расположенного по заданному индексу на вершину стека.

 

 

void lua_pushfstring ( lua_State * lua, const char * fmt, ... );

Аналогично функции sprintf помещает на вершину стека результат форматированного вывода в строку. Функция сама отслеживает необходимый объем памяти под строку. Однако есть ограничения на использование спецификаций форматов - не поддерживается точность и флаги.

 

 

void lua_remove ( lua_State * lua, int index );

Удаляет элемент с заданным индексом из стека, сдвигая выше расположенные элементы.

Рис 5. Работы функции lua_remove.

 

 

int lua_gettop ( lua_State * lua );

Возвращает индекс элемента на вершине стека, т.е. количество элементов в стеке. 

 

int lua_settop ( lua_State * lua, int index );

Устанавливает новое количество элементов в стеке. При этом лишние элементы автоматически удаляются, при необходимости стек дополняется nil-ми.

 

int lua_insert ( lua_State * lua, int index );

Перемещает элемент с вершины стека по заданному индексу сдвигая элементы.

Рис. 6. Работа функции lua_insert.

 

int lua_gettable ( lua_State * lua, int index );

Помещает на вершину стека значение t[k], где t - таблица, расположенная по заданному индексу, а ключ k - элемент с вершины стека.

 

int lua_createtable ( lua_State * lua, int narr, int nass );

Создает новую пустую таблицу и помещает ее на вершину стека. При этом в таблице сразу резервируется место для narr элементов массива и nass для элементов хэша (обычно только один из этих параметров отличен от нуля).

 

int lua_newtable ( lua_State * lua );

Создает новую таблицу, эквивалентен lua_createtable ( lua, 0, 0 ).

 

 

void * lua_newuserdata (lua_State * lua, size_t size );

Функция выделяет блок памяти заданного размера и создает соответствующий объект типа userdata, помещая его на стек. Возвращается указатель на выделенный блок памяти.

 

 

int lua_equal ( lua_State * lua, int index1, int index2 );

Возвращает единицу, если элементы по заданным индексам равны и ноль в противном случае.

 

int lua_getmetatable ( lua_State * lua, int index );

Помещает на вершину стека метатаблицу объекта, расположенного в стеке по заданному индексу. Если индекс не является валидным или у соответствующего объекта нет метатаблицы, то возвращается ноль и на стек ничего не кладется.

 

int lua_setmetatable ( lua_State * lua, int index );

Снимает таблицу с вершины стека и устанавливает ее в качестве метатаблицы для объекта по заданному индексу.

 

 

int lua_isboolean ( lua_State * lua, int index );

Возвращает единицу, если значение по заданному индексу является логическим (типа boolean) и ноль в противном случае.

 

int lua_isfunction ( lua_State * lua, int index );

Возвращает единицу, если значение по заданному индексу является функцией (типа funtion) и ноль в противном случае.

 

int lua_isnil ( lua_State * lua, int index );

Возвращает единицу, если значение по заданному индексу является nil и ноль в противном случае.

 

int lua_isnone ( lua_State * lua, int index );

Возвращает единицу, если переданный индекс является недопустимым.

 

int lua_isoneornil ( lua_State * lua, int index );

Возвращает единицу, если либо переданный индекс является недопустимым, либо по этому индексу расположен nil.

 

int lua_isnumber ( lua_State * lua, int index );

Возвращает единицу, если значение по заданному индексу является числом (типа number) и ноль в противном случае.

 

int lua_isstring ( lua_State * lua, int index );

Возвращает единицу, если значение по заданному индексу является строкой (типа string) и ноль в противном случае.

 

int lua_istable ( lua_State * lua, int index );

Возвращает единицу, если значение по заданному индексу является таблицей (типа table) и ноль в противном случае.

 

int lua_isuserdata ( lua_State * lua, int index );

Возвращает единицу, если значение по заданному индексу является типа userdata и ноль в противном случае.

 

int lua_toboolean ( lua_State * lua, int index );

Возвращает единицу, если значение, расположенное по заданному индексу истинно и ноль в противном случае.

 

lua_Integer lua_tointeger ( lua_State * lua, int index );

Преобразует значение по заданному индексу в знаковое целое число и возвращает его. Соответствующее значение должно быть числом или строкой.

 

const char * lua_tolstring ( lua_State * lua, int index, size_t * length );

Преобразует значение со стека по заданному индексу в строку, если length не равен NULL, то в него заносится длина строки. Возвращается указатель на завершенную '\0' строку, расположенную во внутреннем буфере Lua. Соответствующее значение должно быть строкой или числом.

 

lua_Number lua_tonumber ( lua_State * lua, int index );

Преобразует значение по заданному индексу в число (обычно это double) и возвращает его. Значение должно быть строкой или числом.

 

const char * lua_tostring ( lua_State * lua, int index );

Эквивалентно lua_tolstring ( lua, index, NULL ).

 

void * lua_tosuserdata ( lua_State * lua, int index );

Если значение по заданному индексу имеет тип userdata, то возвращает указатель на соответствующий блок памяти. Иначе возвращает NULL.

 

int lua_type ( lua_State * lua, int index );

Возвращает тип объекта по заданному индексу или LUA_TNONE в случае недопустимого индекса. Тип объекта принимает одно из следующих значений - LUA_TNIL, LUA_TNUMBER, LUA_TBOOLEAN, LUA_TSTRING, LUA_TTABLE, LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD и LUA_TLIGHTUSERDATA. 

 

const char * lua_typename ( lua_State * lua, int typeCode );

Переводит константы типов в соответствующие строковые значения.

 

void lua_register ( lua_State * lua, const char * name, lua_CFunction func  );

Создает новую глобальную функцию с именем name, на основе С-функции и делает ее доступной программам на Lua.

Тип lua_CFunction определен следующим образом: 

 

typedef int (*lua_CFunction) ( lua_State * );

Фактически каждая функция на С/С++, которую мы хотим экспортировать должна иметь такой вид - все аргументы она получает на стеке, результаты также помещает на стек и возвращает количество результатов, записанных на стек.

 

void lua_rawget ( lua_State * lua, int index );

Полностью аналогично lua_gettable, но для обращения к таблице не использует метаметоды.

 

void lua_rawgeti ( lua_State * lua, int index, int n );

Если через t обозначить таблицу по индексу index в стеке, то данный вызов возвращает значение (без использования метаметодов) t[n].

 

void lua_rawset ( lua_State * lua, int index );

Полностью аналогично lua_settable, но для записи в таблицу не использует метаметоды.

 

void lua_rawseti ( lua_State * lua, int index, int n );

Если через t обозначить таблицу по индексу index в стеке, а через v значение на вершине стека, то данный вызов делает прямую запись (без использования метаметодов) t[n]=v.

 

Пусть у нас есть следующий фрагмент кода на Lua: 

 

a = f ( "how", t.x, 14 )

Он будет эквивалентен следующему фрагменту кода на С/С++. 

 

lua_getfield    ( lua, LUA_GLOBALSINDEX, "f" );   // push global function f on stack
lua_pushstring  ( lua, "how" );                   // push first argument on stack
lua_getfield    ( lua, LUA_GLOBALSINDEX, "t" );   // push global table t on stack
lua_getfield    ( lua, -1, "x" );                 // push t.x on stack
lua_remove      ( lua, -2 );                      // remove t from stack
lua_pushinteger ( lua, 14 );                      // push last argument
lua_call        ( lua, 3, 1 );                    // call function taking 3 argsuments and getting one return value
lua_setfield    ( lua, LUA_GLOBALSINDEX, "a" );   // store result from top of stack to global variable a

Рис. 7. Стек во время вызова.

Кроме перечисленных функция в C API также в входит дополнительная библиотека. Заголовочным файлом для нее служит файл lauxlib.h, а имена всех функций начинаются с префикса luaL_.

Все функции дополнительной библиотеки построены на основе основного API, но в ряде случаев более удобны.

 

void luaL_openlibs ( lua_State * lua );

Открывает и делает доступными в текущем контексте для скриптов на Lua все стандартные библиотеки.

 

int luaL_callmeta ( lua_State * lua, int index, const char * eventName );

Если объект по заданному индексу содержит метатаблицу и в ней есть поле с именем eventName, то это поле вызывается (как функция) и ей в качестве параметра передается исходный объект (с вершины стека). 

 

int luaL_loadfile ( lua_State * lua, const char * fileName );

Загружает файл с именем fileName и помещает его на стек как функцию (поэтому после загрузки необходимо сделать вызов lua_call илиlua_pcall).

При успехе возвращает 0, иначе один из следующих кодов - LUA_ERRSYNTAX, LUA_ERRMEM или LUA_ERRFILE.

 

int luaL_loadstring ( lua_State * lua, const char * str );

Загружает код на Lua из строки и помещает его на стек как функцию.

 

int luaL_dofile ( lua_State * lua, const char * fileName );

Загружает и выполняет заданный файл. Определен как следующий макрос:

 

(luaL_loadfile(lua, filename) || lua_pcall(lua, 0, LUA_MULTRET, 0))
 
void luaL_register ( lua_State * lua, const char * libName, const lua_Reg * reg );

Открывает библиотеку с заданным именем libName. Если libName равно NULL, то просто регистрирует все функции из массива reg(концом массива служит запись с обоими значениями равными NULL).

Если libName не равно NULL, то создается новая таблица как глобальная переменная и именем модуля и в ней регистрируются все функции из массива reg.

 


 

Примеры интеграции скриптов на Lua в программы на C/C++

Первый наш пример просто загружает скрипт и выполняет его. Ниже приводится соответствующий скрипт, его задача простейшая - просто вывести сообщение. 

-- simple test of calling lua from C

print ( 'Hello World !' )

Ниже приводится код на С++. Его задача создать контекст, открыть стандартные библиотеки и выполнить заданный файл.

Все это и делает приведенный ниже исходный код.

 

#ifdef	__cplusplus
extern "C" {
#endif

#include	"lua.h"
#include	"lauxlib.h"
#include	"lualib.h"

#ifdef	__cplusplus
};
#endif

int main ()
{
    lua_State * lua = lua_open ();         // create Lua context

    if ( lua == NULL )
    {
        printf ( "Error creating Lua context.\n" );

        return 1;
    }
	
    luaL_openlibs ( lua );                  // open standart libraries
                                            // load and execute a file
    if ( luaL_dofile ( lua, "test-1.lua" ) )
        printf ( "Error opening test-1.lua\n" );

    lua_close     ( lua );                  // close Lua context
	
    return 0;
}

Следующий пример будет несколько сложнее - нашей задачей будет вызвать определенную в скрипте функцию и получить от нее результат.

Пример функции приведен ниже, он просто печатает входные параметры и возвращает их суммы, хотя это может быть любая требуемая функциональность.

 

function foo ( x, y )
   print ( 'foo: ', x, y )
   return x + y
end

Соответствующий код на С++ должен не просто создать констекст и загрузить библиотеки. Также необходимо загрузить файл с нашей функций и вызвать lua_pcall, чтобы вызвать этот файл и определенная в нем функция стала доступна.

После этого мы просто помещаем вызываемую функцию и необходимые параметры на стек и делаем вызов lua_pcall. После него на стеке останется результат выполнения функции.

 

#ifdef	__cplusplus
extern "C" {
#endif

#include	"lua.h"
#include	"lauxlib.h"
#include	"lualib.h"

#ifdef	__cplusplus
};
#endif

int main ()
{
    lua_State * lua = lua_open ();                      // create Lua context

    if ( lua == NULL )
    {
        printf ( "Error creating Lua context.\n" );

        return 1;
    }
	
    luaL_openlibs ( lua );                              // open standart libraries
                                                        // load and execute a file
    if ( luaL_loadfile ( lua, "test-2.lua" ) )
        printf ( "Error opening test-2.lua\n" );

    lua_pcall       ( lua, 0, LUA_MULTRET, 0 );
    lua_getfield    ( lua, LUA_GLOBALSINDEX, "foo" );   // push global function f on stack
    lua_pushstring  ( lua, "17" );                      // push first argument on stack
    lua_pushinteger ( lua, 3 );                         // push second argument on stack
    lua_pcall       ( lua, 2, 1, 0 );                   // call function taking 2 argsuments and getting one return value

                                                        // get return value and print it
    printf ( "Result: %d\n", lua_tointeger ( lua, -1 ) );

    lua_close       ( lua );                            // close Lua context

    return 0;
}

Наш следующий пример сделает обратное - определит функцию на С++ и проэкспортирует ее в Lua. После этого будет выполнен скрипт, который вызовет нашу функцию.

В качестве примера экспортируемой функции возьмем нахождение среднего из переданных аргументов. При этом пусть возвращается как само среднее значение, так и число переданных аргументов. Ниже приводится скрипт, демонстрирующий использование данной функции.

 

 

av, n = ave ( 1, 2, 3, 4, 5, 6, 7, 8 )
print ( 'Average ', av )
print ( 'Count ', n )

Нам необходимо создать функцию и экспортировать ее в Lua. После этого можно звать сам скрипт. Ниже приводится соответствующий исходный код, обратите внимание, на то что функция возвращает 2, т.к. ровно два выходных значения положены на стек.

 

 

#ifdef	__cplusplus
extern "C" {
#endif

#include	"lua.h"
#include	"lauxlib.h"
#include	"lualib.h"

#ifdef	__cplusplus
};
#endif


int	ave ( lua_State * lua )                             // function to be exported to Lua
{
    int     num = lua_gettop ( lua );                   // get # of arguments
    double  sum = 0;

    for ( int i = 1; i <= num; i++ )
        sum += lua_tonumber ( lua, i );

    lua_pushnumber ( lua, sum / num );                  // return average
    lua_pushnumber ( lua, num );                        // return # of arguments

    return 2;                                           // we returned two vaues
}

int main ()
{
    lua_State * lua = lua_open ();                      // create Lua context

    if ( lua == NULL )
    {
        printf ( "Error creating Lua context.\n" );

        return 1;
    }
	
    luaL_openlibs ( lua );                              // open standart libraries
    lua_register  ( lua, "ave", ave );                  // register out function
                                                        // load and execute a file
    if ( luaL_dofile ( lua, "test-3.lua" ) )
        printf ( "Error opening test-3.lua\n" );
	
    lua_close     ( lua );                              // close Lua context

    return 0;
}

 

 

Комментарии   

# Владимир_ 02.08.2017 12:55
Что тут комментировать? Ясно, просто, понятно. Спасибо огромное. Хоть какое понимание появилось как работает этот "дурацкий" Lua стек.

Недостаточно прав для комментирования

Архив QLua