Heath's Blog

There is no end, it is just the beginning! - A Game Developer's Notes

Lua和C的那些事

        Lua和C是天生的好基友,语言开发者提供了一系列API,让他们通过栈进行交流。用Lua做游戏逻辑开发有些时日了,下面主要针对Lua C API的应用进行总结。

一、扩展Lua

        Lua核心很小,主要包含一个解释器,其他功能可以通过动态库的形式作为插件来扩展,io、string、math、table等内置库都是通过此方式来实现,只是他们被集成到了一个lua.dll中罢了。制作一个动态库形式的module,需要在代码中通过luaL_Reg数组指定lua function到c function的映射,接着实现c function,最后在luaopen_xxx(xxx为module name)注册这个luaL_Reg。这里给出一个非常简单的例子,它使用VC++创建一个Console DLL:

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include <math.h>

int mysin (lua_State* L);

static const struct luaL_Reg mymathlib [] =
{
    { "sin" , mysin } ,
    { NULL , NULL }
};

static int mysin( lua_State* L )
{
    double d = luaL_checknumber( L , 1);
    lua_pushnumber(L , sin( d));
    return 1;
}

__declspec(dllexport) int luaopen_mymathlib(lua_State * L)
{
    luaL_register(L , "mymathlib" , mymathlib);
    return 1;
}

编译成dll后放到lua解释器目录下。Lua test code:

require "mymathlib"

local a = mymathlib.sin(0.5)
print(a)

二、作为脚本系统

        Lua应用最多的领域当属游戏开发,WOW的UI和插件让它名声大噪。在这种应用中,Lua作为应用程序的一个子系统,用作配置或者业务处理。在将Lua与应用集成起来时,必须用到Lua C API,根据其规范,你需要写一系列的static函数,作为Lua与应用程序的粘合代码。如果要在Lua使用C++对象,可将其作为userdata,为它创建一个metatable,并将粘合函数放入其中,关键是要让__index指向metatable自身,这样当Lua访问userdata的field时,__index会引导它去搜索metatable自身,从而获得注册的粘合函数。

        有很多开源的粘合代码生成器,他们都是在precompile时做了一些工作,因而不是使用macro就是template,这两种方法的代表是toLua++和luaBind。个人更倾向使用toLua++,一方面这种方式比较直白,另一方面本人弱于使用template。在WGAME中也将原来写得不是很好的bind代码替换成了toLua++,目前UI和关卡逻辑重度使用了Lua,产生的bind代码会有几万行,由于项目采用事件驱动的方式,在profile时看到对游戏整体性能影响非常小。luaBind大概了解过,没有在实际项目中用过,在此就不做评论了。

        在使用toLua++时我最好奇的是它对C++关键特性是如何支持的。除了上面说的成员函数外,对于多态的支持,它是通过在static函数后加编号,调用时判断参数是否对应来遍历找到正确static函数的;对于复杂成员变量,它会自动生成get/set方法;而继承关系,则是通过子类将父类作为metatable来实现。秉着重新发明车轮的精神,我试着写了一个简化的自动生成器[我在github上]。我定义了几个关键字作为类与方法的导出标识:

    {module_begin = "LUACBIND_MODULE_BEGIN" , module_end = "LUACBIND_MODULE_END" , method_begin = "LUACBIND_METHOD_BEGIN" , method_end = "LUACBIND_METHOD_END"}

    util.h定义了产生bind代码需要的宏,parser.lua对指定的.h文件进行扫描产生bind代码,在main函数中register后,就可以在lua中使用了。

三、调试器

        Lua的C API和Debug库提供了实现调试器的必要方法,对应了两种实现方式:一种是Remdebug所采用的,直接用lua实现;另外一种是使用C API。不管哪种方式,使用HOOK都是必须的,但使用Lua debug库会比C API更方便,因为不用考虑栈平衡问题。在用C API实现调试器时,可用lua_newthread创建一个coroutine,之后yield/resume/getstack/getlocal都作用它上面,breakpoint通常会采用在hook中yield的方式来实现,但不能等hook返回之后去进行栈回溯,因为traceexec根据hook mask调用对应hook函数后,如果state是为LUA_YIELD状态,将会调用luaD_throw,最终使用longjmp导致无法进行回溯。

        利用春节值班两天清闲时光,基于lua 5.2实现了一个命令行调试器[我在github上],目前仅有几个简单的功能:加载/运行lua脚本、设置/清除断点、单步、查看简单类型变量值,命令格式可参考README。

posted on 2013-02-12 19:17 Heath 阅读(5008) 评论(1)  编辑 收藏 引用 所属分类: Script Programming

Feedback

# re: Lua和C的那些事 2013-03-04 15:33 shaojingliu

绑定lua, 可以试试swig  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理