- PVSM.RU - https://www.pvsm.ru -
Понадобилось мне прикрутить Lua к проекту на C++. Писать обертки в ручную — лень (слишком много писать), готовые не подходили по тем или иным причинам. Решил написать свою. А потому задался вопросом, как максимально упростить интерфейс? От одной только мысли об этом в голову лезли жутчайшие конструкции из шаблонов. Так оно в последствии и оказалось, но гораздо проще, чем представлялось.
В C++11 появились шаблоны с переменным числом аргументов, это позволяет писать шаблонные функции/классы так, как в C++03 было невозможно вовсе. Такие шаблоны сильно упрощают задачу.
Первым делом понадобилось написать обертку над простейшими действиями с интерпретатором (можно было бы обойтись простыми вызовами к C API Lua, но держать в памяти кучу индексов различных значений в стеке мне не хочется. Поэтому я обернул их в несколько функций, которые помимо того, что избавляют от необходимости передавать в каждую функцию указатель на состояние интерпретатора, практически не требуют индексов, так как они имеют значения по умолчанию.
В итоге хотелось увидеть интерфейс близкий к следующему:
lua.export_function(some_function);
Можно попробовать. Однако интерфейс будет все-таки чуточку сложнее. Нужно указать интерпретатору имя для экспортируемой функции. И передавать будем адрес на функцию.
lua.export_function("some_function", &some_function);
Воспользуемся выводом параметров шаблона. Параметры могут быть выведены автоматически, если они будут:
template <typename T>
void some_function(T (*callback)()) {}
template <typename T>
void some_function(void (*callback)(T)) {}
template <typename T>
void some_function(void (T::*method)()) {}
Все эти случаи (и еще несколько других), могут комбинироваться. Можно этим воспользоваться.
template <typename R, typename... Args>
void export_function(const std::string& name, T (*function)(Args...)) {
}
Теперь, можно взяться за собственно экспорт функции. Для каждой функции создадим лямбду, которая будет принимать аргументы от интерпретатора, передавать их в функцию, а потом, возвращать интерпретатору результат. Лямбда должна храниться всё время, что работает экземпляр интерпретатора, поэтому указатель на каждую лямбду я сохраняю внутри класса и удаляю в деструкторе.
template <typename R, typename... Args>
void export_function(const std::string& name, T (*function)(Args...)) {
auto function = new std::function<int(Lua&)>([function](Lua& vm) -> int {
auto tuple = args<Args...>();
return apply_function<std::tuple_size<decltype(tuple)>::value>
::apply(function, tuple);
});
lambda(function);
}
Выглядит странно. Попробуем разобраться. Для начала надо получить все аргументы от интерпретатора.
template <typename T, typename T1, typename... Args>
std::tuple<T, T1, Args...> args(const int i = 1) {
T t = arg<T>(i);
return std::tuple_cat(t, args<T1, Args...>(i+1));
}
Получаем i-ый аргумент и возвращаем его, а с помощью рекурсии получаем остальные аргументы. Но этого мало.
Эту функцию нужно перегрузить, чтобы на последней итерации исполнялся другой код.
template <typename T>
std::tuple<T> args(const int i = 1) {
return std::tuple<T>(arg<T>(i));
}
Функция arg — очевидна, не буду её приводить, всё что требуется — написать несколько специализаций.
Теперь, когда у нас есть все аргументы в одном кортеже, надо передать их все в функцию.
template <int N> struct apply_function {
template <typename R, typename... FunctionArgs, typename... TupleArgs,
typename... Args>
static R apply(R (*function)(Args...), std::tuple<TupleArgs...>,
Args... args) {
return apply_function<N-1>::apply(function, tuple, std::get<N-1>::value, args);
}
};
И нужно специализировать этот шаблон для последней итерации.
template <> struct apply_function<0> {
template <typename R, typename... FunctionArgs, typename... TupleArgs,
typename... Args>
static R apply(R (*function)(Args...), std::tuple<TupleArgs...>,
Args... args) {
return (*function)(args...);
}
};
Помимо всего этого потребуется еще несколько специализаций (проблемы в типе void).
Получилась вполне рабочая обертка для экспорта C++ функций и классов в lua. Из очевидных минусов вижу всего несколько:
Последние три пункта, возможно, сделаю чуть позже.
А теперь плюсы:
Прежде всего нужно создать объект класса util::Lua, при этом проинициализируется интерпретатор.
util::Lua vm;
После этого можно экспортировать функции/классы.
Всё просто. Мы используем только указатель на функцию и имя, под которым она будет доступна в lua.
some_function();
vm.export_function("some_function", &some_function);
Типы всех параметров и возвращаемого значения будут определены и обработаны корректно.
Экспортируемый класс надо подготовить. Для начала нужно унаследовать его от util::LuaClass, чтобы при возвращении объекта интерпретатору был возвращен именно объект, а не userdata. После нужно определить три статических метода.
class A : public util::LuaClass {
public:
static void export_class(Lua& vm);
static void export_me(Lua& vm);
static const std::string class_name();
};
void A::export_me(Lua& vm) {
vm.export_class<A>();
}
class B: public A {
public:
static void export_class(Lua& vm);
static void export_me(Lua& vm);
static const std::string class_name();
};
void B::export_me(Lua& vm) {
vm.export_class<B, A>();
}
Функции util::Lua::export_class передаются в качестве параметров шаблона — класс, который мы хотим
экспортировать и его родитель, чтобы экспортировать и его (если это еще не сделано).
Самое интересное творится в методе export_class. К примеру:
vm.export_constructor<A, int>();
vm.export_function("static_method", &A::static_method);
vm.export_method("method", &A::method);
Всё просто. Статические методы экспортируем как функции, методы — похожим образом, но через отдельную функцию. Конструктор экспортируется как функция с именем new, типы его аргументов необходимо указать явно в качестве
аргументов шаблона, связано это с тем, что на конструктор нельзя взять указатель. Приятная вещь в том, что объекты созданные посредством вызова такого конструктора из lua будет обрабатывать Garbage Collector. Когда все ссылки на объект будут удалены будет вызван delete для объекта C++.
Весь код выложен на гитхабе github.com/alex-ac/LuaCxx/ [1] под MIT лицензией.
Буду рад увидеть комментарии, советы, фичреквесты и багрепорты.
Автор: alexac
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-3/24753
Ссылки в тексте:
[1] github.com/alex-ac/LuaCxx/: https://github.com/alex-ac/LuaCxx/
[2] Источник: http://habrahabr.ru/post/165765/
Нажмите здесь для печати.