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函数注册器的更多相关文章

  1. Lua和C++交互 学习记录之七:C++全局函数注册为Lua模块

    主要内容转载自:子龙山人博客(强烈建议去子龙山人博客完全学习一遍) 部分内容查阅自:<Lua 5.3  参考手册>中文版 译者 云风 制作 Kavcc vs2013+lua-5.3.3 1 ...

  2. Lua 常用函数 一

    lua_getallocf lua_Alloc lua_getallocf (lua_State *L, void **ud); 返回给定状态机的内存分配器函数.如果 ud 不是 NULL ,Lua ...

  3. lua API函数大全

    Lua5.1中的API函数 lua_State* luaL_newstate()Lua脚本的编译执行是相互独立的,在不同的线程上执行.通过luaL_newstate()函数可以申请一个虚拟机,返回指针 ...

  4. DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件

    DjangoRestFramework学习三之认证组件.权限组件.频率组件.url注册器.响应器.分页组件   本节目录 一 认证组件 二 权限组件 三 频率组件 四 URL注册器 五 响应器 六 分 ...

  5. day91 DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件

    DjangoRestFramework学习三之认证组件.权限组件.频率组件.url注册器.响应器.分页组件   本节目录 一 认证组件 二 权限组件 三 频率组件 四 URL注册器 五 响应器 六 分 ...

  6. php设计模式之单例(多例),注册器,观察者模式

    单例(Singleton)模式和不常见的多例(Multiton)模式控制着应用程序中类的数量.如模式名称,单例只能实例化一次,只有一个对象,多例模式可以多次实例化. 基于Singleton的特性,我们 ...

  7. 使用事件注册器进行swoole代码封装

    在使用swoole的时候,事件回调很难维护与编写,写起来很乱.特别在封装一些代码的时候,使用这种注册,先注册用户自己定义的,然后注册些默认的事件函数. Server.php class Server ...

  8. 20.DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件

    一 认证组件 1. 局部认证组件 我们知道,我们不管路由怎么写的,对应的视图类怎么写的,都会走到dispatch方法,进行分发, 在咱们看的APIView类中的dispatch方法的源码中,有个sel ...

  9. rest认证组件,权限组件,频率组件,url注册器,响应器组件,分页器组件

    1.认证组件 1.1 认证组件利用token来实现认证 1.2 token认证的大概流程 用户登录===>获取用户名和密码===>查询用户表 如果用户存在,生成token,否则返回错误信息 ...

随机推荐

  1. Centos 6.5 安装 rar

    wget http://www.rarsoft.com/rar/rarlinux-x64-5.4.0.tar.gz tar -zxvf rarlinux-x64-5.4.0.tar.gz cd rar ...

  2. Servlet 执行流程 生命周期 ServletConfig 线程安全

    Day34 servlet 三.如何使用Servlet 1.继承GenericServlet类(通用) (1)GenericServlet类有一个关键的设计,定义了一个私有的ServletConfig ...

  3. Python3 JSON 数据解析

    JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. Python3 中可以使用 json 模块来对 JSON 数据进 ...

  4. ionic3-ng4学习见闻--(多语言方案)

    1.安装ng2-translate npm install ng2-translate --save 2.app.moudle.ts 引入模块,在下方新增方法 import { TranslateMo ...

  5. sublime text package control 被墙的解决办法

    似乎没有办法 只能碰运气, 时好时坏. 或者手动安装 趁着好的时候, 下载离线包 https://packagecontrol.io/Package%20Control.sublime-package ...

  6. linux找不到动态链接库 .so文件的解决方法(转自:http://www.cnblogs.com/xudong-bupt/p/3698294.html)

    linux找不到动态链接库 .so文件的解决方法 如果使用自己手动生成的动态链接库.so文件,但是这个.so文件,没有加入库文件搜索路劲中,程序运行时可能会出现找不到动态链接库的情形. 可以通过ldd ...

  7. 23 服务的启动Demo2

    MainActivity.java package com.qf.day23_service_demo2; import android.app.Activity; import android.co ...

  8. [ExtJS5学习笔记]第三十三节 sencha extjs 5 grid表格导出excel

    使用extjs肯定少不了使用表格控件,用到表格,领导们(一般)还是惯于使用excel看数据,所以用到extjs表格的技术猿们肯定也会有导出表格excel这一个需求,本文主要针对如何在用extjs将gr ...

  9. 使用Android Studio向GitHub提交代码

    使用Android Studio向GitHub提交代码 在GitHub上创建一个仓库 填写仓库名称.描述.类型(公有.私有)等信息,点击创建 到此,成功创建了我们的仓库,并且初始化创建了README. ...

  10. HTML简单使用

    HTML简单使用 标签 : 前端技术 HTML HTML(Hypertext Marked Language), 即超文本标记语言,能够独立于各种操作系统平台(如UNIX/Linux/Windows等 ...