• [zz]为 Lua 绑定 C/C++ 对象


    转载自: http://blog.codingnow.com/2013/01/binding_c_object_for_lua.html

    如何绑定 C/C++ 对象到 Lua 里?通常是创建一个 userdata ,存放 C/C++ 对象指针,然后给 userdata 添加元表,用 index 元方法映射 C/C++ 中的对象方法。

    也有另一个手段,直接用 lightuserdata 保存 C/C++ 对象指针放到 Lua 中,在 Lua 中创建一个 table 附加元表来来包装这个指针,效果是类似的。区别在于对象生命期的管理方式有所不同。就这个问题,几年前我写过一篇 blog 。

    绑定 C/C++ 对象到 Lua 里的设计难点往往在这个正确的生命期管理上。因为 C/C++ 没有 GC 系统,依赖手工管理资源;而 Lua 则是利用 GC 做自动回收。这两者的差异容易导致在 Lua 中的对象对应的 C/C++ 对象已经销毁而 Lua 层不自知,或 Lua 层中已无对象之引用,而 C/C++ 层中却未能及时回收资源而造成内存泄露。

    理清这个问题,首先你要确定,你打算以 Lua 为主干来维护对象的生命期,还是以 C/C++ 层为主干 Lua 部分只是做一些对这些对象的行为控制。

    我个人主张围绕 Lua 来开发,C/C++ 只是写一些性能相关的库供 Lua 调用,即框架层在 Lua 中。这样,C/C++ 层只提供对象的创建和销毁函数,不要用 C 指针做对象的相互引用。Lua 中对象被回收时,销毁对应的 C 对象即可。

    但是,也有相当多的项目做不到这点。Lua 是在后期引入的,之前 C/C++ 框架层中已做好了相当之复杂的对象管理。或者构架师不希望把脚本层过多的侵入引擎的设计。

    那么,下面给出另一个方案。

    我们将包装进 Lua 的 C 对象称为 script object ,那么只需要提供三个函数即可。

    int
    script_pushobject(lua_State *L, void * object) {
        void **ud;
        if (luaL_newmetatable(L, "script")) {
            // 在注册表中创建一个表存放所有的 object 指针到 userdata 的关系。
            // 这个表应该是一个 weak table ,当 Lua 中不再存在对 C 对象的引用会删除对应的记录。
            lua_newtable(L);
            lua_pushliteral(L, "kv");
            lua_setfield(L, -2, "__mode");
            lua_setmetatable(L, -2);
        }
        lua_rawgetp(L,-1,object);
        if (lua_type(L,-1)==LUA_TUSERDATA) {
            ud = (void **)lua_touserdata(L,-1);
            if (*ud == object) {
                lua_replace(L, -2);
                return 0;
            }
            // C 对象指针被释放后,有可能地址被重用。
            // 这个时候,可能取到曾经保存起来的 userdata ,里面的指针必然为空。
            assert(*ud == NULL);
        }
        ud = (void **)lua_newuserdata(L, sizeof(void*));
        *ud = object;
        lua_pushvalue(L, -1);
        lua_rawsetp(L, -4, object);
        lua_replace(L, -3);
        lua_pop(L,1);
        return 1;
    }
    

    这个函数把一个 C 对象指针置入对应的 userdata ,如果是第一次 push 则创建出新的 userdata ,否则复用曾经创建过的。

    void *
    script_toobject(lua_State *L, int index) {
        void **ud = (void **)lua_touserdata(L,index);
        if (ud == NULL)
            return NULL;
        // 如果 object 已在 C 代码中销毁,*ud 为 NULL 。
        return *ud;
    }
    

    这个函数把 index 处的 userdata 转换为一个 C 对象。如果对象已经销毁,则返回 NULL 指针。 在给这个对象绑定 C 方法时,应注意在 toobject 调用后,全部对指针做检查,空指针应该被正确处理。

    void
    script_deleteobject(lua_State *L, void *object) {
        luaL_getmetatable(L, "script");
        if (lua_istable(L,-1)) {
            lua_rawgetp(L, -1, object);
            if (lua_type(L,-1) == LUA_TUSERDATA) {
                void **ud = (void **)lua_touserdata(L,-1);
                // 这个 assert 防止 deleteobject 被重复调用。
                assert(*ud == object);
                // 销毁一个被 Lua 引用住的对象,只需要把 *ud 置为 NULL 。
                *ud = NULL;
            }
            lua_pop(L,2);
        } else {
            // 有可能从未调用过 pushobject ,此时注册表中 script 项尚未建立。
            lua_pop(L,1);
        }
    }
    

    这个函数会解除 C 对象在 Lua 中的引用,后续在 Lua 中对这个对象的访问,都将得到 NULL 指针。


    这些代码是在我写这篇 blog 的同时随手写的,并未经过严格测试。它们也有许多改进空间,比如给 C 对象加入类型,对 userdata 做更严格的检查,等等。

  • 相关阅读:
    One网络模拟器探索之七:路由模块的工作原理
    An Intuitive Explanation of GraphSAGE
    《k8s 源码分析》- Custom Controller 之 Informer
    《k8s-1.13版本源码分析》-抢占调度
    《k8s-1.13版本源码分析》- Informer 机制
    《k8s-1.13版本源码分析》-调度器初始化
    《k8s-1.13版本源码分析》-源码调试
    《k8s-1.13版本源码分析》-调度优选
    《k8s-1.13版本源码分析》-调度预选
    《k8s-1.13版本源码分析》-调度器框架
  • 原文地址:https://www.cnblogs.com/zhangzhang/p/2854830.html
Copyright © 2020-2023  润新知