前面已经说过来,Call重载函数接受一个函数指针,然后从lua栈中根据函数指针的类型,取得相关的参数,并调用这个函数,然后将返回值压入lua栈,类似于这样:
//伪代码
int Call(pfn, lua_State *L, int idx)
现在的问题是pfn该如何声明?我们知道这是一个函数指针,然而其参数,以及返回值都是未知的类型,如果我们知道返回值和参数的类型,我们可以用一个typedef来声明它:
typedef void (*pfn)();
int Call(pfn fn, lua_State *L, int idx);
我们知道的返回值以及参数的类型只是一个模板参数T,在cpp中,我们不能这样写:
template <typename T>
typedef T (*Func) ();
一种解决办法是使用类模板:
template <typename T>
struct CallHelper
{
typedef T (*Func) ();
};
然后在Call中引用它:
template <typename T>
int Call(typename CallHelper::Func fn, lua_State *L, int idx)
注意typename关键字,如果没有这个关键字,在g++中会产生一个编译警告,它的意思是告诉编译器,CallHelper::Func是一个类型,而不是变量。
如果我们这样来解决,就需要在CallHelper中为每种情况大量定义各种类型的函数指针,还有一种方法,写法比较古怪,考虑一个函数中参数的声明:
void (int n);
首先是类型,然后是变量,而应用于函数指针上:
typedef void (*pfn) ();
void (pfn fn);
事实上,可以将typedef直接在参数表中写出来:
void (void (*pfn)() );
这样,我们的Call函数可以直接这样写:
//针对没有参数的Call函数
template <typename RT>
int Call(RT (*Func) () , lua_State *L, int idx);
{
//调用Func
RT ret = (*Func)();
//将返回值交给lua
Push(L, ret);
//告诉lua有多少个返回值
return 1;
}
//针对有一个参数的Call
template <typename T, typename P1>
int Call(RT (*Func)(P1), lua_State *L, int idx)
{
//从lua中取得参数
if (!Match(TypeWrapper<P1>(), L, -1)
return 0;
RT ret = (*Func) (Get(TypeWrapper<P1>(), L, -1));
Push(L, ret);
return 1;
}
按照上面的写法,我们可以提供任意参数个数的Call函数,现在回到最初的时候,我们的函数指针要通过lua_State *L来存储,这只要利用lua提供的api就可以了,还记得我们的lua_pushdirectclosure函数吗:
template <typename Func>
void lua_pushdirectclosure(Func fn, lua_State *L, int nUpvalue)
{
//伪代码,向L存储函数指针
save_pointer(L);
//向lua提供我们的register_proxy函数
lua_pushcclosure(L, register_proxy<Func>, nUpvalue + 1);
}
其中,save_pointer(L)可以这样实现:
void save_pointer(lua_State *L)
{
unsigned char* buffer = (unsigned char*)lua_newuserdata(L, sizeof(func));
memcpy(buffer, &func, sizeof(func));
}
而在register_proxy函数中:
template <typename Func>
int register_proxy(lua_State *L)
{
//伪代码,通过L参数取得这个指针
unsigned char *buffer = get_pointer(L);
//对这个指针做强制类型转化,调用Call函数
return Call(*(Func*)buffer, L, 1);
}
get_pointer函数可以这样实现:
unsigned char* get_pointer(lua_State *L)
{
return (unsigned char*) lua_touserdata(L, lua_upvalueindex(1));
}
这一点能够有效运作主要依赖于这样一个事实:
我们在lua栈中保存这个指针之后,在没有对栈做任何操作的情况下,又把它从栈中取了出来,所以不会弄乱lua栈中的信息,记住,lua栈中的数据是由用户保证来清空的。
到现在,我们已经可以向lua注册任意个参数的c函数了,只需简单的一行代码:
lua_register_directclosure(L, func)就可以啦。