• 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可能更合适。

  • 相关阅读:
    为什么转速环经过pi调节后输出的是电流?
    如何将mysql、php、nginx设置为开机自启动
    rm-rf /*
    2020/4/23-linux中的定时任务详解
    2020/4/20 一键部署服务
    2020/4/20-ansible一键部署nfs
    2020/4/20-backup之rsync
    2020/4/19-linux中/etc/hosts
    2020/4/18-linux中的selinux
    2020/4/18-linux中的iptables
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6313773.html
Copyright © 2020-2023  润新知