所以基本上我试图找到一种方法在Lua中使用不是lua_CFunctions的C ++函数(不返回和int并将lua_State作为参数)。基本上你的常规旧C ++功能。但问题是,我正试图找到一种方法来实现它而不用编写自己专用的lua_CFunction(所以基本上想象一下,我已经在C ++中有一个程序或一堆函数,我想在Lua中使用,而且我不喜欢我不想为每个人写一个新功能。)
所以,说我有一个非常简单的C ++函数:
static int doubleInt(int a) {
return a*2;
}
(有或没有静态,它不应该(?)重要)。
假设我想通过在lua脚本中调用doubleInt(10)来在Lua中使用此函数。有没有办法在不编写单独的
的情况下完成此操作static int callFunc(lua_State *L) {
//do stuff to make the function work in lua
}
对于每个单独的功能?那么就像luaBind用def()函数做的那样(我知道它很糟糕,但我不能真正使用一个单独的专用绑定库;必须自己编写)。
我知道我必须为此编写一个带有模板的类,但我对如何在Lua中获取该函数一点也没有任何想法。我不认为C ++中有一种方法可以自动生成自定义函数(大概是在编译时) - 这太棒了 - 所以我甚至不知道从哪里开始。
答案 0 :(得分:1)
这是一个非常开放的问题。
我最近一直在研究lua绑定库,所以我可以解释一下我是如何做到这一点的,但是有很多方法可以做到。
你没有标记这个问题C ++ 11。不过我会假设你使用的是C ++ 11。如果没有,那么这是非常困难的,如果你还不完全了解boost::mpl
以了解如何做到这一点,我会说完全没有实际意义。在这种情况下,你绝对应该使用luabind
。
您需要的第一件事是,您需要创建一些基本的基础结构,告诉您如何将C ++类型转换为相应的lua类型并返回。
IMO最好的方法是使用类型特征,不一个大量重载函数。因此,您将定义主要模板:
namespace traits {
template <typename T>
struct push;
template <>
struct push<int> {
static void to_stack(lua_State * L, int x) { lua_pushinteger(L, x); }
};
template <>
struct push<double> {
static void to_stack(lua_State * L, double d) { lua_pushnumber(L, d); }
};
...
} // end namespace traits
等。您可能还希望将其专门用于std::string
以及类似的事情。
然后你可以制作一个通用的push
函数:
template <typename T>
void push(lua_State * L, const T & t) {
traits::push<T>::to_stack(L, t);
}
此处的优点是,当您致电push
时,不会考虑隐式转化。您传递的类型与您为其定义特征的内容完全匹配,或者失败。并且你不能在double
和int
等之间出现模糊的过载问题,这可能是一个很大的痛苦。
然后,你必须为read
做同样的事情,所以你有一个特征告诉你如何从堆栈中读取给定类型的值。你的read
技术需要以某种方式表示失败,你可以决定是否应该使用异常或不同的技术。
完成此操作后,您可以尝试创建一个adapt
模板,该模板将采用任意函数指针,并尝试将其调整为lua_CFunction
,其大致相同。
基本上,您希望使用可变参数模板,以便可以专门针对函数指针的所有参数。您将这些类型逐个传递给read方法,并使用索引序列从正确的堆栈位置读取。您尝试全部读取它们,如果您可以无错误地执行它们,则可以调用目标函数,然后返回其结果。
如果你想将通用C ++对象作为返回值推回,那么你可以在最后调用你的推送功能。
首先,要提供帮助,您需要一个“索引序列”工具。如果你在C++14
,你可以使用std::make_integer_sequence
,如果没有,那么你必须自己动手。我看起来像这样:
namespace detail {
/***
* Utility for manipulating lists of integers
*/
template <std::size_t... Ss>
struct SizeList {
static constexpr std::size_t size = sizeof...(Ss);
};
template <typename L, typename R>
struct Concat;
template <std::size_t... TL, std::size_t... TR>
struct Concat<SizeList<TL...>, SizeList<TR...>> {
typedef SizeList<TL..., TR...> type;
};
/***
* Count_t<n> produces a sizelist containing numbers 0 to n-1.
*/
template <std::size_t n>
struct Count {
typedef
typename Concat<typename Count<n - 1>::type, SizeList<n - 1>>::type type;
};
template <>
struct Count<0> {
typedef SizeList<> type;
};
template <std::size_t n>
using Count_t = typename Count<n>::type;
} // end namespace detail
以下是您的adapt
课程的样子:
// Primary template
template <typename T, T>
class adapt;
// Specialization for C++ functions: int (lua_State *, ...)
template <typename... Args, int (*target_func)(lua_State * L, Args...)>
class adapt<int (*)(lua_State * L, Args...), target_func> {
template <typename T>
struct impl;
template <std::size_t... indices>
struct impl<detail::SizeList<indices...>> {
static int adapted(lua_State * L) {
try {
return target_func(L, read<Args>(L, 1 + indices)...);
} catch (std::exception & e) {
return luaL_error(L, "Caught an exception: %s", e.what());
}
}
};
public:
static int adapted(lua_State * L) {
using I = detail::Count_t<sizeof...(Args)>;
return impl<I>::adapted(L);
}
};
我的实施中的真实代码是here。我决定这样做而不使用例外。
此技术也可以在编译时工作 - 因为您将函数指针作为非类型模板参数传递给任意C ++函数,adapt
模板生成lua_CFunction
作为静态类成员,当你指向adapt<...>::adapted
时,它必须在编译时解析。这意味着编译器可以内联所有不同的位。
要解决无法推断非类型模板参数类型的问题,比如函数指针(在C ++ 17之前),我使用的宏看起来像这样:
#define PRIMER_ADAPT(F) &::primer::adapt<decltype(F), (F)>::adapted
因此,我可以使用复杂的C ++函数f
,然后使用PRIMER_ADAPT(&f)
,就像它只是lua_CFunction
一样。
你应该意识到,制作所有这些东西并测试它需要很长时间。我在这个库上工作了一个多月,它从另一个项目中的一些代码重构,在那里我对它进行了更长时间的改进。 lua中存在很多与“自动化”堆栈操作相关的陷阱,因为它不会对你进行任何边界检查,你需要调用lua_checkstack
来严格正确。
你绝对应该使用现有的一个库,除非你有一个非常迫切需要阻止它。
答案 1 :(得分:0)
如果你不限于标准的Lua lib,你可以试试LuaJIT。它有ffi支持。调用外部函数非常简单:
local ffi = require("ffi")
ffi.cdef[[
int printf(const char *fmt, ...);
]]
ffi.C.printf("Hello %s!", "world")