Как работает Luabind? - PullRequest
       25

Как работает Luabind?

6 голосов
/ 24 мая 2011

Меня интересует, как оболочка Luabind позволяет передавать функцию без lua_State *L и без использования стека Lua.

Как Luabind:

  1. рассчитываетпараметры функции?
  2. связать параметры функции со стеком Lua?
  3. связать эти классы

Я не пытаюсь создать другую привязку, такую ​​как Luabind, к другим библиотекам,Мне просто интересно, как они это сделали.Просто любопытный человек.

Ответы [ 4 ]

13 голосов
/ 26 мая 2011

Хороший вопрос. У меня было какое-то смутное представление о том, как luabind делает то, что делает, но я не знал достаточно, чтобы ответить полностью и точно. Вооружившись IDE и отладчиком, я начал разбирать следующую очень простую часть:

struct C
{
    int i;
    int f(int x, const char* s)
};

    lua_State* L = luaL_newstate();

open(L);
module(L)
[
    class_<C>("C")
        .def_readwrite("index", &C::i)
        .def("f", &C::f)
];

Первое, что следует отметить, это то, что L часто передается в luabind, вызов open создает несколько глобальных переменных в состоянии Lua: __luabind_classes типа userdata и две функции class и * 1008. *. Luabind, похоже, не использует глобальные переменные - все, что ему нужно, сохраняется в среде lua.

Теперь мы получаем module(L)[...]. Оригинальный код - лучшее объяснение, сначала вот module:

inline module_ module(lua_State* L, char const* name = 0)
{
    return module_(L, name);
}

Достаточно просто, вот module_:

class LUABIND_API module_
{
public:
    module_(lua_State* L_, char const* name);
    void operator[](scope s);

private:
    lua_State* m_state;
    char const* m_name;
};

Итак, наша маленькая программа вызывает оператор [] для класса module_ с некоторыми определениями (это параметр scope), но класс module_ знает, в каком состоянии Lua работать. Класс scope также интересен (некоторые части опущены, а некоторые немного упрощены):

struct LUABIND_API scope
{
    //...
    explicit scope(detail::registration* reg);
    scope& operator,(scope s);
    void register_(lua_State* L) const;
private:
    detail::registration* m_chain;
};

scope строит связанный список detail::registration узлов, этот список получен с использованием operator,. Так, когда кто-то делает module(L) [class_<...>..., class_<...>...], class_, который наследует от scope, инициализирует свою базу с экземпляром detail::registration, то оператор запятой scope создает связанный список всех регистраций, это передается в module_::operator[] который вызывает scope::register_, который, в свою очередь, перечисляет цепочку и вызывает register_ для всех этих detail::registration объектов. lua_State всегда передается в register_.

Уф. Теперь давайте посмотрим, что происходит, когда кто-то делает class_<C>("C").def("f", &C::f). Это создает экземпляр class_<C> с определенным именем, которое входит в член detail::registration в class_. Вызов метода class_::def записывает в reg-структуру и еще много чего, но вот очень интересная строка в цепочке вызовов от def:

            object fn = make_function(
                L, f, deduce_signature(f, (Class*)0), policies);

Ооо, deduce_signature, я действительно хотел это увидеть. Теперь я хочу это увидеть, но способ, которым он работает, заключается в следующем: посредством темного колдовства препроцессора с помощью boost (BOOST_PP_ITERATE и некоторых других утилит) генерируется следующее для каждого N между единицей и LUABIND_MAX_ARITY:

template <class R, class T, class A1, classA2, ..., classAN>
boost::mpl::vectorN_PLUS_2<R, T, A1, A2, ..., AN>  // type of return value
     deduce_signature(R(T::*)(A1, A2, ..., AN))
     {
          return boost::mpl::vectorN_PLUS_2<R, T, A1, A2, ..., AN>()
     }

Опять же, такая функция генерируется для всех N от 1 до LUABIND_MAX_ARITY, которая по умолчанию равна 10. Существует несколько перегрузок для обработки методов const, виртуальных упаковщиков и свободных функций и т. Д., Что означает, что около 50 deduce_signature функций заканчиваются в ваших источниках сразу после препроцессора и до начала компиляции. Отсюда задача компилятора выбрать правильную перегрузку deduce_signature для функций, которые вы передаете в def, и это вернет правильный тип boost::mpl::vectorX. Оттуда make_function может делать все что угодно - у него есть список типов параметров [время компиляции], и с помощью некоторой дополнительной магии шаблонов они подсчитываются, преобразуются в значения Lua и из них и так далее.

Здесь я остановлюсь. Расследование основано на Luabind 0.8.1. Не стесняйтесь просматривать / отлаживать код Luabind для получения дополнительных ответов - это займет некоторое время, но это не так сложно после того, как вы привыкнете к стилю :) Удачи.

TL; DR: Магия ... черная магия

3 голосов
/ 25 мая 2011

luabind имеет шаблонные функции-оболочки для знакомого прототипа int luafunction(lua_State* L), который принимает C API.По сути, функция lua_CFunction создана для вас.Фактическая функция C или C ++ для вызова может быть сохранена как повышающее значение для оболочки.В случае функции-члена C ++ указатель this может быть взят из первого аргумента.

Пример кода, оборачивающего функцию C с использованием значений upvalue:

template<typename R, typename T1>
int arg1wrapper(lua_State* L)
{
    typedef R (*F)(T1);
    F func = (F)lua_touserdata(L, lua_upvalueindex(1));
    R retValue = func(luaToC<T1>(L, 1));
    push(L, retValue);
    return 1;
}

// example use
template<typename R, typename T1>
void push(R (*func)(T1))
{
    lua_pushlightuserdata(L, func);
    lua_pushcclosure(L, &arg1wrapper<R, T1>, 1);
}

(The luaToC шаблонная функция будет специализированной для каждого типа C и C ++, который будет поддерживать библиотека. Функция push будет перегружена аналогичным образом.)

Вы заметите, что приведенная выше пара функций будет работать только для одной конкретнойвид функции C;функции с ненулевым возвращаемым значением и одним параметром.Возврат пустот может быть легко обработан путем разложения операций с возвращаемым значением в третий шаблон, специализированный для void, но для поддержки других количеств параметров вам потребуется множество перегрузок.luabind делает это: у него есть одна перегрузка для каждого количества параметров, которые он поддерживает, включая один для 0 параметров (максимальное количество - произвольное число, которое они выбрали).

(обратите внимание, что в C ++ 0x вы можете использоватьшаблоны переменных для поддержки любого количества параметров с одним и тем же шаблоном)

0 голосов
/ 25 мая 2011

С руководство :

Первое, что вам нужно сделать, это вызвать luabind::open(lua_State*), который зарегистрирует функции для создания классов из Lua и инициализирует некоторое состояние-глобальные структуры, используемые luabind.

Так что мое (несколько образованное) предположение состоит в том, что вы делаете вещь luabind::open, и она кэширует состояние для использования в остальных функциях.(Ключ кроется в «инициализации некоторых глобальных структур».)

0 голосов
/ 25 мая 2011

Я не знаком с luabind, но вся идея "обертки" в том, что она построена поверх какой-то абстракции более низкого уровня, инкапсулируя ее.luabind почти наверняка использует использует lua_State внутренне .

...