找回密码
 立即注册
查看: 279|回复: 0

xLua添加Peer机制

[复制链接]
发表于 2021-8-13 23:23 | 显示全部楼层 |阅读模式
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中添加getpeer和setpeer方法,并导出给lua:
  1. #if LUA_VERSION_NUM == 501
  2. LUA_API intsetpeer(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);return0;};
  3. LUA_API intgetpeer(lua_State* L){/* stack: userdata */lua_getfenv(L,-1);if(lua_rawequal(L,-1, XLUA_NOPEER)){lua_pop(L,1);lua_pushnil(L);};return1;};#endifstaticconst luaL_Reg xlualib[]={{"sethook", profiler_set_hook},{"genaccessor", gen_css_access},{"structclone", css_clone},{"setpeer", setpeer},{"getpeer", getpeer},{NULL,NULL}};
复制代码
其中的XLUA_NOPEER会在后面讲到。
然后在lua文件中,添加生成lua派生类对象的方法:
  1. -- 给userdata绑定peer
  2. local bind_peer
  3. bind_peer =function(ud, cls)iftype(ud)=="userdata" then
  4.         local peer = xlua.getpeer(ud)if not peer then
  5.             peer ={}
  6.             xlua.setpeer(ud, peer)
  7.         end
  8.         setmetatable(peer, cls)
  9.     end
  10. end
  11. --...
  12. cls.new_with_cs_instance =function(cs_instance)
  13.     local obj
  14.     -- 递归调用构造函数
  15.     do
  16.         local create
  17.         create =function(c, cs_instance)if c.super then
  18.                 create(c.super, cs_instance)
  19.             end
  20.             if not obj then
  21.                 obj = cs_instance
  22.                 bind_peer(obj, cls)
  23.                 obj['.is_cs_instance']=true
  24.                 obj.class = cls
  25.             end
  26.             if c.ctor then
  27.                 c.ctor(obj)
  28.             end
  29.         end
  30.         create(cls, cs_instance)
  31.     end
  32.     return obj
  33. end
复制代码
这样c#层通过特殊函数new_with_cs_instance,可以创建指定的lua派生类对象,这些对象在创建时就会绑定一个lua table。
这里有个坑,看下面的代码:
  1. local peer = xlua.getpeer(t)if not peer then
  2.     peer ={}
  3.     xlua.setpeer(t, peer)
  4. end
  5. 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里添加如下代码:
  1. #define XLUA_NOPEER LUA_REGISTRYINDEX// ...
  2. LUA_API voidxlua_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 == 501lua_pushvalue(L, XLUA_NOPEER);lua_setfenv(L,-2);#endif}
复制代码
最后,我们需要修改xLua的通用__index和__newindex函数来支持peer查询。修改后的代码如下:
  1. //upvalue --- [1]: methods, [2]:getters, [3]:csindexer, [4]:base, [5]:indexfuncs, [6]:arrayindexer, [7]:baseindex//param   --- [1]: obj, [2]: key
  2. LUA_API intobj_indexer(lua_State *L){int t =lua_type(L,1);if(t == LUA_TUSERDATA){/* Access alternative table */#ifdef LUA_VERSION_NUM == 501lua_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))return1;};#endif}if(!lua_isnil(L,lua_upvalueindex(1))){lua_pushvalue(L,2);lua_gettable(L,lua_upvalueindex(1));if(!lua_isnil(L,-1)){//has methodreturn1;}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 getterlua_pushvalue(L,1);lua_call(L,1,1);return1;}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);return1;}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)){return1;}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);return1;}else{return0;}}staticvoid storeatubox (lua_State* L,int lo){// 参考tolua++里的实现,暂时去掉了非5.1版本的支持// 如需支持其他lua版本,请自觉根据tolua++的实现,采用额外的一张表存储userdata和其绑定的table#if LUA_VERSION_NUM == 501lua_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
  3. LUA_API intobj_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 setterlua_pushvalue(L,1);lua_pushvalue(L,3);lua_call(L,2,0);return0;}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)){return0;}}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);return0;}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);return0;}elseif(t == LUA_TUSERDATA){storeatubox(L,1);return0;}else{returnluaL_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#的成员变量!
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 11:37 , Processed in 0.163045 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表