xLua添加Peer机制

8 篇文章 0 订阅
4 篇文章 1 订阅

xLua添加Peer机制

peer这个概念来自tolua++,是对等、同等的意思。其主要目标是为了解决一个lua和c++(或者c#)交互的问题:

在lua下继承一个c#的类时,我们希望能给这个派生类添加一些新的成员变量,同时,希望在别的地方获取到这个派生类的对象时,也能正常访问到这些新的成员变量。

首先要理解lua下是怎样访问c#的对象的。一个c#的对象传给lua时,会生成一个userdata(相同的对象会用同一个userdata,xLua内部管理),同时设置这个userdata的元表,其元表可以理解为就是其对应的wrap类。

这样在lua下访问这个userdata时,就可以通过其元表访问到c#的成员变量和成员函数。

由于userdata是不能直接添加新成员的,所以我们需要创建一个额外的table,新成员会写入这个table,同时把这个table和userdata绑定,并修改userdata的__index__newindex元方法,令其支持访问这个table即可。

在lua5.1下有个很简单高效的方法将一个table和userdata绑定,即通过environment。参考lua5.1的官方文档,在2.9 - Environment一节有:

Environments associated with userdata have no meaning for Lua. It is only a convenience feature for programmers to associate a table to a userdata.

这样我们可以通过lua_setfenv把userdata和table绑定。

在xlua.c中添加getpeersetpeer方法,并导出给lua:

#if LUA_VERSION_NUM == 501
LUA_API int setpeer(lua_State* L) {

    /* stack: userdata, table */
    if (!lua_isuserdata(L, -2)) {
        lua_pushstring(L, "Invalid argument #1 to setpeer: userdata expected.");
        lua_error(L);
    };

    if (lua_isnil(L, -1)) {

        lua_pop(L, 1);
        lua_pushvalue(L, XLUA_NOPEER);
    };
    lua_setfenv(L, -2);

    return 0;
};

LUA_API int getpeer(lua_State* L) {

    /* stack: userdata */
    lua_getfenv(L, -1);
    if (lua_rawequal(L, -1, XLUA_NOPEER)) {
        lua_pop(L, 1);
        lua_pushnil(L);
    };
    return 1;
};
#endif

static const luaL_Reg xlualib[] = {
    {"sethook", profiler_set_hook},
    {"genaccessor", gen_css_access},
    {"structclone", css_clone},
    {"setpeer", setpeer},
    {"getpeer", getpeer},
    {NULL, NULL}
};

其中的XLUA_NOPEER会在后面讲到。

然后在lua文件中,添加生成lua派生类对象的方法:

-- 给userdata绑定peer
local bind_peer
bind_peer = function(ud, cls)
    if type(ud) == "userdata" then
        local peer = xlua.getpeer(ud)
        if not peer then
            peer = {}
            xlua.setpeer(ud, peer)
        end
        setmetatable(peer, cls)
    end
end

-- ...

cls.new_with_cs_instance = function(cs_instance)
    local obj

    -- 递归调用构造函数
    do
        local create
        create = function(c, cs_instance)
            if c.super then
                create(c.super, cs_instance)
            end

            if not obj then
                obj = cs_instance
                bind_peer(obj, cls)

                obj['.is_cs_instance'] = true

                obj.class = cls
            end

            if c.ctor then
                c.ctor(obj)
            end
        end

        create(cls, cs_instance)
    end

    return obj
end

这样c#层通过特殊函数new_with_cs_instance,可以创建指定的lua派生类对象,这些对象在创建时就会绑定一个lua table。

这里有个坑,看下面的代码:

local peer = xlua.getpeer(t)
if not peer then
    peer = {}
    xlua.setpeer(t, peer)
end
setmetatable(peer, index)

这代码的意思就是,如果这个userdata没有绑定的table,就给它绑定一个新的table。由于底层是通过绑定userdata的环境来实现,但在lua5.1下,这个环境默认不是nil,而且不是一个固定值。可以参考这篇文章how-to-detect-if-a-userdata-has-environment-table

基本思路就是在userdata创建时,我们会给它设置一个默认的环境,后面再通过判断这个userdata的环境是不是默认环境来确定其是否有绑定table。

这里我们采用lua的注册表来当成默认环境。在xlua.c里添加如下代码:

#define XLUA_NOPEER LUA_REGISTRYINDEX


// ...


LUA_API void xlua_pushcsobj(lua_State *L, int key, int meta_ref, int need_cache, int cache_ref) {
    int* pointer = (int*)lua_newuserdata(L, sizeof(int));
    *pointer = key;

    if (need_cache) cacheud(L, key, cache_ref);

    lua_rawgeti(L, LUA_REGISTRYINDEX, meta_ref);

    lua_setmetatable(L, -2);

    #if LUA_VERSION_NUM == 501
    lua_pushvalue(L, XLUA_NOPEER);
    lua_setfenv(L, -2);
    #endif
}

最后,我们需要修改xLua的通用__index__newindex函数来支持peer查询。修改后的代码如下:

//upvalue --- [1]: methods, [2]:getters, [3]:csindexer, [4]:base, [5]:indexfuncs, [6]:arrayindexer, [7]:baseindex
//param   --- [1]: obj, [2]: key
LUA_API int obj_indexer(lua_State *L) {
    int t = lua_type(L,1);
    if (t == LUA_TUSERDATA) {
        /* Access alternative table */
        #ifdef LUA_VERSION_NUM == 501
        lua_getfenv(L,1);
        if (!lua_rawequal(L, -1, XLUA_NOPEER)) {
            lua_pushvalue(L, 2); /* key */
            lua_gettable(L, -2); /* on lua 5.1, we trade the "xlua_peers" lookup for a gettable call */
            if (!lua_isnil(L, -1))
                return 1;
        };
        #endif
    }
 
    if (!lua_isnil(L, lua_upvalueindex(1))) {
        lua_pushvalue(L, 2);
        lua_gettable(L, lua_upvalueindex(1));
        if (!lua_isnil(L, -1)) {//has method
            return 1;
        }
        lua_pop(L, 1);
    }
     
    if (!lua_isnil(L, lua_upvalueindex(2))) {
        lua_pushvalue(L, 2);
        lua_gettable(L, lua_upvalueindex(2));
        if (!lua_isnil(L, -1)) {//has getter
            lua_pushvalue(L, 1);
            lua_call(L, 1, 1);
            return 1;
        }
        lua_pop(L, 1);
    }
     
     
    if (!lua_isnil(L, lua_upvalueindex(6)) && lua_type(L, 2) == LUA_TNUMBER) {
        lua_pushvalue(L, lua_upvalueindex(6));
        lua_pushvalue(L, 1);
        lua_pushvalue(L, 2);
        lua_call(L, 2, 1);
        return 1;
    }
     
    if (!lua_isnil(L, lua_upvalueindex(3))) {
        lua_pushvalue(L, lua_upvalueindex(3));
        lua_pushvalue(L, 1);
        lua_pushvalue(L, 2);
        lua_call(L, 2, 2);
        if (lua_toboolean(L, -2)) {
            return 1;
        }
        lua_pop(L, 2);
    }
     
    if (!lua_isnil(L, lua_upvalueindex(4))) {
        lua_pushvalue(L, lua_upvalueindex(4));
        while(!lua_isnil(L, -1)) {
            lua_pushvalue(L, -1);
            lua_gettable(L, lua_upvalueindex(5));
            if (!lua_isnil(L, -1)) // found
            {
                lua_replace(L, lua_upvalueindex(7)); //baseindex = indexfuncs[base]
                lua_pop(L, 1);
                break;
            }
            lua_pop(L, 1);
            lua_getfield(L, -1, "BaseType");
            lua_remove(L, -2);
        }
        lua_pushnil(L);
        lua_replace(L, lua_upvalueindex(4));//base = nil
    }
     
    if (!lua_isnil(L, lua_upvalueindex(7))) {
        lua_settop(L, 2);
        lua_pushvalue(L, lua_upvalueindex(7));
        lua_insert(L, 1);
        lua_call(L, 2, 1);
        return 1;
    } else {
        return 0;
    }
}
 
 
static void storeatubox (lua_State* L, int lo) {
    // 参考tolua++里的实现,暂时去掉了非5.1版本的支持
    // 如需支持其他lua版本,请自觉根据tolua++的实现,采用额外的一张表存储userdata和其绑定的table
    #if LUA_VERSION_NUM == 501
        lua_getfenv(L, lo);
        if (lua_rawequal(L, -1, XLUA_NOPEER)) {
            lua_pop(L, 1);
            lua_newtable(L);
            lua_pushvalue(L, -1);
            lua_setfenv(L, lo); /* stack: k,v,table  */
        };
        lua_insert(L, -3);
        lua_settable(L, -3); /* on lua 5.1, we trade the "xlua_peers" lookup for a settable call */
        lua_pop(L, 1);
    #endif
}
 
//upvalue --- [1]:setters, [2]:csnewindexer, [3]:base, [4]:newindexfuncs, [5]:arrayindexer, [6]:basenewindex
//param   --- [1]: obj, [2]: key, [3]: value
LUA_API int obj_newindexer(lua_State *L) {
    int t = lua_type(L,1);
    if (!lua_isnil(L, lua_upvalueindex(1))) {
        lua_pushvalue(L, 2);
        lua_gettable(L, lua_upvalueindex(1));
        if (!lua_isnil(L, -1)) {//has setter
            lua_pushvalue(L, 1);
            lua_pushvalue(L, 3);
            lua_call(L, 2, 0);
            return 0;
        }
        lua_pop(L, 1);
    }
     
    if (!lua_isnil(L, lua_upvalueindex(2))) {
        lua_pushvalue(L, lua_upvalueindex(2));
        lua_pushvalue(L, 1);
        lua_pushvalue(L, 2);
        lua_pushvalue(L, 3);
        lua_call(L, 3, 1);
        if (lua_toboolean(L, -1)) {
            return 0;
        }
    }
     
    if (!lua_isnil(L, lua_upvalueindex(5)) && lua_type(L, 2) == LUA_TNUMBER) {
        lua_pushvalue(L, lua_upvalueindex(5));
        lua_pushvalue(L, 1);
        lua_pushvalue(L, 2);
        lua_pushvalue(L, 3);
        lua_call(L, 3, 0);
        return 0;
    }
     
    if (!lua_isnil(L, lua_upvalueindex(3))) {
        lua_pushvalue(L, lua_upvalueindex(3));
        while(!lua_isnil(L, -1)) {
            lua_pushvalue(L, -1);
            lua_gettable(L, lua_upvalueindex(4));
 
            if (!lua_isnil(L, -1)) // found
            {
                lua_replace(L, lua_upvalueindex(6)); //basenewindex = newindexfuncs[base]
                lua_pop(L, 1);
                break;
            }
            lua_pop(L, 1);
            lua_getfield(L, -1, "BaseType");
            lua_remove(L, -2);
        }
        lua_pushnil(L);
        lua_replace(L, lua_upvalueindex(3));//base = nil
    }
     
    if (!lua_isnil(L, lua_upvalueindex(6))) {
        lua_settop(L, 3);
        lua_pushvalue(L, lua_upvalueindex(6));
        lua_insert(L, 1);
        lua_call(L, 3, 0);
        return 0;
    } else if (t == LUA_TUSERDATA) {
        storeatubox(L, 1);
        return 0;
    } else {
        return luaL_error(L, "cannot set %s, no such field", lua_tostring(L, 2));
    }
}

PS:

  • 以上的实现基本参考自tolua++,有兴趣的可以看下tolua++的实现,主要参考文件tolua_map.c以及tolua_event.c
  • 这里只实现了lua5.1版本的支持,由于lua5.3已经取消了lua_setfenv函数,也修改了environment概念。要支持lua5.3,需要额外创建一个xlua_peers的table,记录全局的userdata和table的绑定关系。由于我们采用luajit,也就是用的是lua5.1,有需要支持5.3的可以参考tolua++的实现。
  • 这种实现方法有一个小坑,如果在C#层有一个成员是可访问不可修改的,也就是类似public int a { get; protected set; }这样的情况。这时如果在lua派生类中,复写这个变量,也就是类似self.a = 5,这样以后在lua就只能访问到lua下变量a,访问不到c#的变量a。但在c#层还是只能访问c#的变量a。所以尽量不要复写c#的成员变量!
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值