lua c函数注册器
lua与c的交互
关于lua和c的交互,主要有两个方面,一是lua调用c的函数,而另一个则是c调用lua函数。而这些都是通过lua stack来进行的。
c调用lua
在c里面使用lua,主要是通过lua_call这类函数,下面来自lua manual的例子:
lua_getglobal(L, "f"); /* function to be called */
lua_pushstring(L, "how"); /* 1st argument */
lua_getglobal(L, "t"); /* table to be indexed */
lua_getfield(L, -1, "x"); /* push result of t.x (2nd arg) */
lua_remove(L, -2); /* remove 't' from the stack */
lua_pushinteger(L, 14); /* 3rd argument */
lua_call(L, 3, 1); /* call 'f' with 3 arguments and 1 result */
lua_setglobal(L, "a"); /* set global 'a' */
该例子等同于直接在lua里面调用 a = f("how", t.x, 14)。 通过上面的例子可以看到,在c中使用lua是一件很容易的事情,首先获取需要调用的lua函数,然后将其需要的参数依次压入stack,然后通过lua_call调用,该函数调用的返回值也压入stack,供c去获取。
lua调用c
对于lua调用c,我们首先需要将c函数注册给lua,而注册给lua的函数,需要满足 int (*lua_CFunction)(lua_State* pState) 这种类型。如下例子:
int multi(lua_State* pState)
{
int data1 = int(lua_tonumber(pState, 1));
int data2 = int(lua_tonumber(pState, 2));
lua_pushnumber(pState, data1 * data2);
return 1;
}
lua_register(pState, multi, "multi");
#for use in lua
#a = multi(10, 20)
我们通过lua_register将mutli函数注册给lua,该函数接受两个参数,并且有一个返回值。当lua调用multi的时候,会将参数压入stack,所以我们可以通过lua_tonumber(pState, 1)和lua_tonumber(pState, 2)来获取,其中1为第一个参数10,2为第二个参数20。当运行完成之后,multi函数通过lua_pushnumber将结果压入lua堆栈,并通过return 1告知lua有一个返回值,为200。
可以看到,在lua中使用c也是一件很简单的事情。
lua mainly or c mainly
通过上面的例子可以看出,lua与c是很方便的交互的,但是在实际的游戏项目中,我们首先必须确定的一个问题就是,代码逻辑是以lua为主还是以c为主。
- lua为主的游戏就是逻辑主要由lua负责,核心的对性能要求较高的逻辑则由c负责,游戏中的数据大多由lua负责。
- c为主的游戏则是逻辑主要由c负责,lua只是负责简单的配置。
这两种方式都有优劣,对于实际游戏项目来说,个人认为,应该采用lua为主,c作为高性能核心的方式。之所以这样选择,是因为游戏的逻辑变动很大,我们需要快速的进行代码迭代,这个对于lua来说非常方便。而对于核心引擎,因为变化不大,同时对性能要求较高,所以采用c是一个很好的选择。
reg helper
在实际的游戏项目中,我们会遇到这样一个问题,假设c提供的函数为 int func(int a, int b),如果这个函数要提供给lua使用,我们需要写一个对应的注册函数,如下:
int func_wrapper(lua_State* pState)
{
int a = int(lua_tonumber(pState, 1));
int b = int(lua_tonumber(pState, 2));
int ret = func(a, b);
lua_pushnumber(pState, ret);
return 1;
}
lua_register(pState, func_wrapper, "func");
对于任意的c函数,我们需要写一个对应的wrapper用来注册给lua。如果项目中只有几个c函数,那么无所谓,但是如果需要注册给lua的c函数很多,那么对于每一个c函数写一个wrapper,是一件很不现实的事情。并且如果c函数的参数或者返回值有变化,我们同时需要修改对应的wrapper函数。基于上述原因,我们需要一套自动机制,能够将任意的c函数注册给lua使用。实际来说,我们需要提供一个函数,对于任意的c函数func,我们只需要调用register(func, "func"),那么就能直接注册给lua使用。
traits
首先,我们必须面对的问题就是,不同的c函数,参数和返回值是不一样的,譬如对于int类型的参数,我们需要通过lua_tonumber获取数据,而对于const char* 类型的参数,我们需要通过lua_tostring来获取,同理对于返回值也一样。所以我们需要一套机制,根据c函数不同的参数和返回值类型来调用lua对应的stack操纵函数。我们可以通过c++ traits来实现。
我们提供如下一套函数:
template<typename T>
struct TypeHelper{};
bool getValue(TypeHelper<bool>, lua_State* pState, int index)
{
return lua_toboolean(pState, index) == 1;
}
char getValue(TypeHelper<char>, lua_State* pState, int index)
{
return static_cast<char>(lua_tonumber(pState, index));
}
int getValue(TypeHelper<int>, lua_State* pState, int index)
{
return static_cast<int>(lua_tonumber(pState, index));
}
void pushValue(lua_State* pState, bool value)
{
lua_pushboolean(pState, int(value));
}
void pushValue(lua_State* pState, char value)
{
lua_pushnumber(pState, value);
}
void pushValue(lua_State* pState, int value)
{
lua_pushnumber(pState, value);
}
通过traits技术,对于不同的参数类型,我们可以调用对应的getValue函数,来从lua获取实际的数据,而对于返回值,通过c++自动的参数匹配,就能调用对应的pushValue函数。
CCallHelper
上面解决了类型匹配的问题,下面就需要提供wrapper函数,用以封装c函数。这里我们提供call helper类来实现。
template<typename Ret>
class CCallHelper
{
public:
static int call(Ret (*func)(), lua_State* pState)
{
Ret ret = (*func)();
pushValue(pState, ret);
return 1;
}
template<typename P1>
static int call(Ret (*func)(P1), lua_State* pState)
{
P1 p1 = getValue(TypeHelper<P1>(), pState, 1);
Ret ret = (*func)(p1);
pushValue(pState, ret);
return 1;
}
};
CCallHelper提供了静态的call函数,第一个参数就是实际c函数,通过函数模板可以进行任意c函数的匹配,这里只提供了匹配无参数和一个参数类型的c函数模板,我们可以扩展到支持任意参数个数,但也别太多了。因为对于任意c函数来说,可能有一个返回值,也可能没有返回值,那么我们如何匹配没有返回值的c函数呢?这里就是为什么我们需要CCallHelper的原因。在c++中,是不支持函数级别的模板特化的,但是类却可以,所以我们通过特化CCallHelper来匹配无返回值的c函数。如下:
template<>
class CCallHelper<void>
{
public:
static int call(void (*func)(), lua_State* pState)
{
(*func)();
return 0;
}
template<typename P1>
static int call(void (*func)(P1), lua_State* pState)
{
P1 p1 = getValue(TypeHelper<P1>(), pState, 1);
(*func)(p1);
return 0;
}
};
CCallDispatcher
通过CCallHelper::call(func, pState),我们就可以与lua进行交互,那么又如何调用到相应的CCallHelper呢?这里我们通过CCallDispatcher来进行,如下:
template<typename Func>
class CCallDispatcher
{
public:
template<typename Ret>
static int dispatch(Ret (*func)(), lua_State* pState)
{
return CCallHelper<Ret>::call(func, pState);
}
template<typename Ret, typename P1>
static int dispatch(Ret (*func)(P1), lua_State* pState)
{
return CCallHelper<Ret>::call(func, pState);
}
};
通过CCallDispatcher,我们就可以将不同的c函数dispatch到不同的CCallHelper上面。
CCallRegister and regFunction
解决了c函数派发调用的问题,最后我们就需要处理如何将任意的c函数注册给lua,代码如下:
template<typename Func>
class CCallRegister
{
public:
static int call(lua_State* pState)
{
Func* func = static_cast<Func*>(lua_touserdata(pState, lua_upvalueindex(1));
return CCallDispatcher<Func>::dispatch(*func, pState);
}
};
template<typename Func>
void regFunction(lua_State* pState, Func func, const char* funcName)
{
int funcSize = sizeof(Func);
void* data = lua_newuserdata(pState, funcSize);
memcpy(data, &func, funcSize);
lua_pushcclosure(pState, CCallRegister<Func>::call, 1);
lua_setglobal(pState, funcName);
}
首先,我们提供CCallRegister类,里面提供了一个static的call函数,该函数满足lua注册格式,所以实际我们是将该函数注册给lua,在call函数里面,我们通过lua_touserdata(pState, lua_upvalueindex(1))来获取实际的func,然后传递给CCallDispatcher进行派发。而将call注册则是通过regFunction,该函数将实际的c函数func存储在一个userdata中,然后将该userdata绑定到对应的CCallRgister call上面,作为一个upvalue,这样当在lua里面调用call函数的时候,通过lua_upvalueindex获取对应的upvalue,则可以取到实际的c函数。
一些设计上面的考虑
上述reghelper的实现,我已经放到github luahelper上面,并且在max os,gcc 4.2,lua5.2环境下面测试通过。
这里谈一些设计上面的问题,首先,我说的任意c函数,参数和返回值只能是基本数值类型,如bool,char,short,int,long,float,double以及字符串类型char*等,这里我并没有提供复杂类型譬如class,struct的支持。之所以这样考虑,是因为我想保证lua与 c交互的简单,云风曾经说过lua不是c++,我本人当年也曾经用了2年的时间做了同样的事情。但是实现了这套东西,功能是狠强大了,以至于可以把lua当成c++来用了,但是这真的是使用lua正确的方式吗?我现在觉得,引入复杂的结合层,反而在某些时候会带来更大的复杂性,导致语言侧重点的混淆。所以有时候,我反而觉得,对于这种语言的交互,可能使用其他方式,譬如json,反而来的更容易。
写在后面的话
对于lua和游戏开发的一些东西,其实一直想写,但是以前因为很多方面的原因而中途放弃。现在重新开始,有几个方面的原因,一个在于仍然对于游戏开发的热爱,做了4年的游戏开发,虽然现在从事云存储方面的研究,但是对游戏热情依旧。另一个方面在于lua5.2的发布,觉得是应该对以前游戏人生做一个总结了。
如果需要更强大的lua与c的交互,我觉得swig可能更合适。
lua c函数注册器的更多相关文章
- Lua和C++交互 学习记录之七:C++全局函数注册为Lua模块
主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍) 部分内容查阅自:<Lua 5.3 参考手册>中文版 译者 云风 制作 Kavcc vs2013+lua-5.3.3 1 ...
- Lua 常用函数 一
lua_getallocf lua_Alloc lua_getallocf (lua_State *L, void **ud); 返回给定状态机的内存分配器函数.如果 ud 不是 NULL ,Lua ...
- lua API函数大全
Lua5.1中的API函数 lua_State* luaL_newstate()Lua脚本的编译执行是相互独立的,在不同的线程上执行.通过luaL_newstate()函数可以申请一个虚拟机,返回指针 ...
- DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件
DjangoRestFramework学习三之认证组件.权限组件.频率组件.url注册器.响应器.分页组件 本节目录 一 认证组件 二 权限组件 三 频率组件 四 URL注册器 五 响应器 六 分 ...
- day91 DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件
DjangoRestFramework学习三之认证组件.权限组件.频率组件.url注册器.响应器.分页组件 本节目录 一 认证组件 二 权限组件 三 频率组件 四 URL注册器 五 响应器 六 分 ...
- php设计模式之单例(多例),注册器,观察者模式
单例(Singleton)模式和不常见的多例(Multiton)模式控制着应用程序中类的数量.如模式名称,单例只能实例化一次,只有一个对象,多例模式可以多次实例化. 基于Singleton的特性,我们 ...
- 使用事件注册器进行swoole代码封装
在使用swoole的时候,事件回调很难维护与编写,写起来很乱.特别在封装一些代码的时候,使用这种注册,先注册用户自己定义的,然后注册些默认的事件函数. Server.php class Server ...
- 20.DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件
一 认证组件 1. 局部认证组件 我们知道,我们不管路由怎么写的,对应的视图类怎么写的,都会走到dispatch方法,进行分发, 在咱们看的APIView类中的dispatch方法的源码中,有个sel ...
- rest认证组件,权限组件,频率组件,url注册器,响应器组件,分页器组件
1.认证组件 1.1 认证组件利用token来实现认证 1.2 token认证的大概流程 用户登录===>获取用户名和密码===>查询用户表 如果用户存在,生成token,否则返回错误信息 ...
随机推荐
- 关于Allele(等位基因)的理解
高中学生物的时候关于遗传学的部分,记得当时的教材上为了简化处理一般将基因型定义为AA, Aa, aa.其实这种抽象的理解对应付高考是很有用的,但是实际应用中如果还这样理解那么便会产生一些疑问.之所以会 ...
- 数据结构Java版之交换算法(一)
交换的本质是拷贝,其中拷贝包括两种方式.值拷贝和指针拷贝,在java中没有指针,为此,我们可以理解为地址拷贝,在我看来,指针就是地址. 1.传值方式示例: 由上述示例可得,传值,不能起到交换的作用,原 ...
- jQuery 效果 – 动画
在使用jQuery动画时,你可能想要实现更加丰富的效果,那么你可以通过使用 jQuery animate() 方法自定义动画来达到目的,具体的使用方法如下文所述. jQuery animate() 方 ...
- Winform DevExpress控件库(二) 使用SplashScreenManager控件定制程序加载页面
SplashScreenManager控件:主要作用是显示在进行耗时操作时的等待界面: 位于 工具箱 -> Navigation & Layout(导航栏与布局类控件) 目录下: 在工具 ...
- Bootstrap3 表格-条纹状表格
通过 .table-striped 类可以给 之内的每一行增加斑马条纹样式. 跨浏览器兼容性 条纹状表格是依赖 :nth-child CSS 选择器实现的,而这一功能不被 Internet Explo ...
- 最优秀的网络框架retrofit
由于某学员要求所以我打算写一篇 标题先记录下来 我会在一周内完成此篇文章
- windows curl命令详解
概述 Curl命令可以通过命令行的方式,执行Http请求.在Elasticsearch中有使用的场景,因此这里研究下如何在windows下执行curl命令. 软件下载 下载地址:https://cur ...
- list标准函数的模拟
;反序 ( ) -> ( ) (define (rvs x) (let recur ((x x)(res '())) (if (null? x) res (recur (cdr x) (cons ...
- 快速索引 (对View的自定义)
快速索引 (对View的自定义) 快速索引应用场景: 微信好友列表, 联系人通讯录, 应用管理, 文件管理等. 快速索引7步曲: *1. A-Z索引的绘制. * 2. 处理Touch事件. * 3. ...
- ISP(Interface Segregation Principle),接口隔离原则
ISP(Interface Segregation Principle),接口隔离原则 它要求如下: ① 一个类对另一个类的依赖性要建立在最小接口上. ② 使用多个专门的接口比使用单一的总接口要好 ...