воскресенье, 20 января 2008 г.

Lua и C++, функции.

Этот пост о том, как заставить интерпретатор lua вызывать функции написанные на С++. Чтобы интерпретатор мог вызвать функцию, программа, сперва, должна эту функцию зарегистрировать. lua_register(lua_State*, Имя функции, Адрес функции) Функция должна возвращать значение типа int и получать в качестве параметра указатель на lua_State:
int glue_function(lua_State* L);
Склеивающая функция должна следовать определенному протоколу для нормальной работы. В первую очередь она должна получить через lua_State количество переданных ей аргументов, это значение находится на вершине стека во время вызова.
int glue_function(lua_State* L);
Затем функция может обращаться к элементам в стеке ( lua_to...(lua_State*, index) [вместо многоточия следует поставить нужный тип, например bool string и тд] ), имеющим индексы i >= 1 i <= count. Эти элементы могут иметь разные типы, функция должна проверять перед использованием типы переданных ей аргументов (функциями lua_is...(lua_State*, index) ), так как интерпретатор lua в момент вызова функции из кода lua не выполняет таких проверок (он не проверяет даже количество переданных в функцию аргументов, не то что их тип ). Чтобы возвратить значение, функция должна поместить в стэк возвращаемые значения (функции lua_push...) и возвратить их количество. Функция может вернуть множество значений (это называется кортеж), причем для функции lua абсолютно естественно, возвращать переменное число параметров. Следующий пример иллюстрирует сказанное, программа регистрирует функцию print_arg, которая печатает количество переданных ей параметров, сами параметры и возвращает количество параметров. Скрипт использует эту функцию по назначению.
#include "stdafx.h"
extern "C" {
#    include "lua.h"
#    include "lauxlib.h"
#    include "lualib.h"
}
#include <stdlib.h>
#include <stdio.h>
#include <string>
//Эта функция может принимать переменное число параметров через L
int stack_print (lua_State* L)
{
    int n = lua_gettop(L);//получаем количество параметров переданных функции
    printf("args: %d\n", lua_gettop(L) );
    for (int i = 1; i <= n; ++i)
    {
        if (lua_isnumber(L, i))//проверяем тип i-го параметра
            //если это вещественное число выводим его на экран
            printf("result[%d] = %f\n", i, lua_tonumber(L, i));
        else if (lua_isstring(L, i))//проверка на строку
            printf("result[%d] = %s\n", i, lua_tostring(L, i));
        else if (lua_istable(L, i))//если таблица
        {    lua_pushnil(L);//кладем на вершину стека NULL
            while ( lua_next(L, i) )//эта функция берет значение из стека (по индексу -1),
            //и использует его в качестве ключа для поиска по таблице, которая
            //находится в стеке по индексу i, в случае если находит, добавляет в
            //стек пару ключ - значение, следующие в таблице после ключа находившегося в стеке
            //если больше нет значений, ф-я возвращает 0
            //lua_next используется для перебора элементов таблицы
            {
                /*
                    Состояние стека:
                    [-1] value  <--Вершина стека
                    [-2] key
                    ....
                    [i] table <-- текущий элемент
                 */
                //print key value
                if (lua_type(L, -2) == LUA_TNUMBER)
                {
                    printf ( "key = %f", lua_tonumber(L, -2) );
                } else if ( lua_type(L, -2) )
                {
                    printf ("key = %s", lua_tostring(L, -2));
                }
                //print value
                if (lua_type(L, -1) == LUA_TNUMBER)
                {
                    printf ( "\tvalue = %f\n", lua_tonumber(L, -1));
                } else if ( lua_type(L, -1) )
                {
                    printf ("\tvalue = %s\n", lua_tostring(L, -1));
                }
                //удаляем значение value из стэка, что-бы на следующей итерации
                //ключ использовался для нахождения следующей пары значений из таблицы
                lua_pop(L, 1);
                /*
                    Состояние стека:
                    [-1] key  <--Вершина стека
                    ....
                    [i] table <-- текущий элемент
                 */
            }
        }
        else if (lua_isuserdata(L, i)) //пользовательский тип данных
        {
            void* udata = lua_touserdata(L, i);
            printf("lua userdata %06", udata);
        }
    }
    //возвпащаем один параметр - количество переданных значений
    lua_pushinteger(L, (ptrdiff_t)n);
    return 1;//сие означает что возвращается одно значение
}
int main(void)
{
    int status, result;
    lua_State *L;
    L = lua_open();//Новая виртуальная машина луа
    
    luaL_openlibs(L);//Открываем библиотеки base table string io math 
    lua_register(L,"print_arg", &stack_print);//Регистрируем функцию
    
    
    status = luaL_loadfile(L, "script.lua");//загружаем файл
    if (status) {
        printf("file not found or syntax error\n");
        return 1;
    }
    result = lua_pcall(L, 0, LUA_MULTRET, 0);//выполняем программу, 
    //точка входа в которую, расположена на вершине стека
    if (result) {
        printf("runtime error\n");
        return 1;
    }
    lua_close(L); //Done
    return 0;
}
код скрипта:
-- script.lua
x = 8
x = print_arg(5, x, 'lua')
io.write('x = '..x..'\n')
local table        = {}
table['pi']    = 3.14159
table[3.14159]    = 'pi'
print_arg(table)
Этот пример, уже намного более практичен. С помощю склеивающих функций вы можете изменять часть логики, не перекомпилируя проект, просто изменив скрипт. В следующей статье я покажу как реализовать в lua возможность, которая не поддерживается этим языком напрямую – объектно ориентированное программирование.

Комментариев нет: