|
1. Lua和C,C#交互
lua api
lua_State是lua中的基本类型,用来管理lua虚拟机的执行环境,包含虚拟机中的环境表、注册表、运行堆栈、虚拟机的上下文等数据。一个lua虚拟机可以有多个执行环境,lua_State的最主要的功能就是用于函数调用和C/C++的交互。在xLua中luaState被封装成了LuaEnv类,在toLua中叫作LuaState。
功能包括
- 数据栈管理,交互中的压栈和出栈,函数注册和临时数据的存储
- 调用栈管理,包含栈头栈尾两个指针以及被调用函数指针func
- 全局表管理(注意:全局唯一)
- gc的一些管理
global_state是lua全局状态存储的地方,管理lua虚拟机全局环境,所有lua_state共用一个global_state. 包括内存分配策略,全局字符串hashtable管理,注册表,gc管理,lua_state集合管理,元表管理。
lua内部的值类型是用TValue这种结构来实现的,包括Value和_tt两部分,_tt是value的类型,Value是一个Union,可以表示不同类型的值
/*
** Union of all Lua values
*/
typedef union Value {
GCObject *gc; /* collectable objects */
void *p; /* light userdata */
int b; /* booleans */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
} Value;
#define TValuefields Value value_; int tt_
typedef struct lua_TValue {
TValuefields;
} TValue;栈的index可以用正数或者负数来表示,正数表示由栈底开始计数,负数表示由栈顶开始。
在C中调用Lua的函数步骤
- 首先,Lua中有个全局函数叫test()
- 在C中,使用lua_getglobal(L, "test"); L是lua_State
- 使用lua_pushnumber去入栈参数,lua_pushnumber(L, x), lua_pushnumber(y)
- 使用lua_pcall调用函数,lua_pcall(lua_State, numArgs, numReturnValues, int msgh)
- 使用lua_tonumberx/lua_tocfunction/lua_tointegerx等接口获得函数的返回值,z = lua_tonumberx(L, -1, &isnum); 这里直接操作lua的栈,-1表示从栈顶开始。
- 使用lua_pop(L, 1)。这里pop表示从栈中弹出n个元素,这里是为了保证栈和调用前一致。
double f(lua_State* L, double x, double y) {
int isnum;
double z;
lua_getglobal(L, "test");//获取需要调用的Lua函数f
lua_pushnumber(L, x);//入栈第一个参数
lua_pushnumber(L, y);//入栈第二个参数
//调用函数, 2个参数,1个返回值
if (lua_pcall(L, 2, 1, 0) != LUA_OK)
error(L, "error running function 'f': "%s", lua_tostring(L, -1));
z = lua_tonumberx(L, -1, &isnum);//获取返回值
if (!isnum)
error(L, "function 'f' shound return a number");
lua_pop(L, 1);
return z;
}在C#中调用lua函数,和C中调用lua函数是相似的
首先,从lua_State中获取lua函数
public LuaFunction GetFunction(string funcName)
{
if (_luaState == null) return null;
return _luaState.Global.Get<LuaFunction>(funcName);
}获得fucntion之后,可以调用func.call. 下面是xlua源码。
//deprecated
public object[] Call(object[] args, Type[] returnTypes)
{
#if THREAD_SAFE || HOTFIX_ENABLE
lock (luaEnv.luaEnvLock)
{
#endif
int nArgs = 0;
var L = luaEnv.L;
var translator = luaEnv.translator;
int oldTop = LuaAPI.lua_gettop(L);
int errFunc = LuaAPI.load_error_func(L, luaEnv.errorFuncRef);
LuaAPI.lua_getref(L, luaReference);
if (args != null)
{
nArgs = args.Length;
for (int i = 0; i < args.Length; i++)
{
translator.PushAny(L, args);
}
}
int error = LuaAPI.lua_pcall(L, nArgs, -1, errFunc);
if (error != 0)
luaEnv.ThrowExceptionFromError(oldTop);
LuaAPI.lua_remove(L, errFunc);
if (returnTypes != null)
return translator.popValues(L, oldTop, returnTypes);
else
return translator.popValues(L, oldTop);
#if THREAD_SAFE || HOTFIX_ENABLE
}
#endif
}call函数中进行lua的函数调用,先把参数压栈,然后同样的使用p_call调用函数,然后恢复堆栈。
lua调用C#
使用LuaCallCSharp生成对应的wrap, wrap会注册函数, xlua源代码如下。由lua调用wrap文件,再由warp调用c#
#if GEN_CODE_MINIMIZE
public static void BeginClassRegister(Type type, RealStatePtr L, CSharpWrapper creator, int class_field_count,
int static_getter_count, int static_setter_count)
#else
public static void BeginClassRegister(Type type, RealStatePtr L, LuaCSFunction creator, int class_field_count,
int static_getter_count, int static_setter_count)
#endif
{
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
LuaAPI.lua_createtable(L, 0, class_field_count);
LuaAPI.xlua_pushasciistring(L, &#34;UnderlyingSystemType&#34;);
translator.PushAny(L, type);
LuaAPI.lua_rawset(L, -3);
int cls_table = LuaAPI.lua_gettop(L);
SetCSTable(L, type, cls_table);
LuaAPI.lua_createtable(L, 0, 3);
int meta_table = LuaAPI.lua_gettop(L);
if (creator != null)
{
LuaAPI.xlua_pushasciistring(L, &#34;__call&#34;);
#if GEN_CODE_MINIMIZE
translator.PushCSharpWrapper(L, creator);
#else
LuaAPI.lua_pushstdcallcfunction(L, creator);
#endif
LuaAPI.lua_rawset(L, -3);
}
if (static_getter_count == 0)
{
LuaAPI.lua_pushnil(L);
}
else
{
LuaAPI.lua_createtable(L, 0, static_getter_count);
}
if (static_setter_count == 0)
{
LuaAPI.lua_pushnil(L);
}
else
{
LuaAPI.lua_createtable(L, 0, static_setter_count);
}
LuaAPI.lua_pushvalue(L, meta_table);
LuaAPI.lua_setmetatable(L, cls_table);
}
MonoPInvokeCallbackAttribute, 反向调用,即从非托管代码激发托管代码的时候,函数指针到委托包装类的转换程序基于 JIT 编译器,JIT 编译器在ios下处于禁用模式,这就使得该调用存在一些限制:所要传递的C#方法必须为静态类方法;方法必须标注MonoPInvokeCallbackAttribute
还可以使用反射的方式调用C#的函数,但是效率比较低,因此除非不能具体生成wrap的情况下,应当尽量避免此种情况。
在lua中调用C函数
在lua中调用C函数,想比C#中的调用,要简单很多,先定义一个函数,在C中的函数中,算出结果,把结果push到lua的栈上,然后使用lua_pushcfunction把函数压栈,弹出栈顶元素,使用lua_setglobal使用全局变量存储。
static int l_sin (lua_State *L) {
double d = lua_checknumber(L, 1);//如果传进来的不是number参数,会报错
lua_pushnumber(L, sin(d)); // push result
/* 这里可以看出,C可以返回给Lua多个结果,
* 通过多次调用lua_push*(),之后return返回结果的数量。
*/
return 1;//返回值的个数
}
lua_pushcfunction(L, l_sin);// 将C函数转换为Lua的&#34;function&#34;并压入虚拟栈。
lua_setglobal(L, &#34;mysin&#34;); // 弹出栈顶元素,并在Lua中用名为&#34;mysin&#34;的全局变量存储。2. Lua注册表
xlua可以直接调用CS.UnityEngine来访问C#中库,那么这个是怎么实现的呢?
通过注册表。
看一下定义
LUA_REGISTRYINDEX
Lua provides a registry, a pre-defined table that can be used by any C code to store whatever Lua value it needs to store. This table is always located at pseudo-index LUA_REGISTRYINDEX. Any C library can store data into this table, but it should take care to choose keys different from those used by other libraries, to avoid collisions. Typically, you should use as key a string containing your library name or a light userdata with the address of a C object in your code。
注册表是一个全局的table,只能被C访问(在lua脚本也无法访问),注册表被定义为LUA_REGISTRYINDEX。 它是一个伪索引,因为它的值不在栈上,为了防止索引冲突,才这么设计。所有的C库都可以存储数据在这个table中,但是要注意防止冲突。
另外为了从注册表中注册值和取值,还要理解引用函数。
int luaL_ref (lua_State *L, int t);Creates and returns a reference, in the table at index t, for the object at the top of the stack (and pops the object).
A reference is a unique integer key. As long as you do not manually add integer keys into table t, luaL_ref ensures the uniqueness of the key it returns. You can retrieve an object referred by reference r by calling lua_rawgeti(L, t, r). Function luaL_unref frees a reference and its associated object.
If the object at the top of the stack is nil, luaL_ref returns the constant LUA_REFNIL. The constant LUA_NOREF is guaranteed to be different from any reference returned by luaL_ref.
这个方法创建为栈顶的元素创建一个引用,t为table的index, 并且弹出栈顶的这个元素。
// getglobal对函数进行压栈
LuaDLL.lua_getglobal( lua.L, &#34;foo&#34; );
// 注册压栈的函数进注册表,并且弹出栈顶元素
int key = LuaDLL.luaL_ref( lua.L, LuaIndexes.LUA_REGISTRYINDEX );
// 使用key可以找到函数 并且放到栈顶
LuaDLL.lua_rawgeti( lua.L, LuaIndexes.LUA_REGISTRYINDEX, key );
// 调用栈顶的函数
LuaDLL.lua_pcall( lua.L, 0, 0, 0 );
// 回收引用,因此可以被释放掉了
LuaDLL.luaL_unref( lua.L, LuaIndexes.LUA_REGISTRYINDEX, key );
Xlua就是把所有的C#对象放在LUA_REGISTRYINDEX,并通过wrap把C#类型都放在CS对象上,于是lua就可以通过CS.XX访问C#对象了。
3. Lua面向对象
lua使用元表的方式来实现面向对象。
继承,封装,多态(可以override父类的操作,实现不同的结果)。
继承可以使用元表中的__index来实现,这样就可以访问父类的函数了。
setmetatable(new_class, { __index = super })new的实现,下面是官方的例子
function Account:new (o)
o = o or {} -- create object if user does not provide one
setmetatable(o, self)
self.__index = self
return o
end如果0为空,则新建一个table, metatable设置为它自己。当然这个例子比较简单,还可以实现类的构造函数。
依次调用构造函数
local supers = super_classes(class)
for i=#supers, 1, -1 do
supers.ctor(object, ...)
end
class.ctor(object, ...)***有个方法可能会用到,rawget是为了绕过__index而出现的,直接点,就是让__index方法的重写无效。
rawset可以绕过__index写入
****
云风的类实现
--[[
云风的lua面向对象编程架构,用来模拟一个基类
--]]
local _class={}
function class(super)
local class_type={}
class_type.ctor=false
class_type.super=super
--[[
模拟构造函数的function
--]]
class_type.new=function(...)
local obj={}
do
local create
create = function(c,...)
--如果本类存在着基类,就递归调用基类的创建函数初始化基类的成员
if c.super then
create(c.super,...)
end
-- 如果本类有构造函数,就执行本类的构造函数操作
if c.ctor then
c.ctor(obj,...)
end
end
--前面的这段代码是声明create function,下面的就是执行
create(class_type,...)
end
--将此对象的元表的__index元方法设为下面的虚函数表
setmetatable(obj,{ __index=_class[class_type] })
return obj
end
-- 用一个table来构造类的函数表
local vtbl={}
_class[class_type]=vtbl
--[[
设置表class_type的元表并定义__newindex字段,字段对应的函数,
参数1就是表class_type本身,当添加一个新方法的时候就会执行此__newindex的实现
--]]
setmetatable(class_type,{__newindex=
function(t,k,v)
vtbl[k]=v
end
})
--[[
如果本类有父类的话,将本类虚函数表的原表__index设从父类的函数表,直接从父类的函数表中查找。
--]]
if super then
setmetatable(vtbl,{__index=
function(t,k)
local ret=_class[super][k] --这里,就是查找父类的函数表的操作
vtbl[k]=ret
return ret
end
})
end
return class_type
end4. Table的结构
Lua的table由数组部分和哈西部分组成,数组的key是1-n的整数,哈西是个Open Address Table, 哈希表解决冲突的方法是链地址法结合开放定址法,即在冲突的地方创建一个链表,将不同的键串在这个链表上。链表的所有节点都在
这个node数组中,而不是创建新的节点,因此这里是连续的内存即可。
Table的源码
typedef struct Table {
// 这是一个宏,为GCObject共用部分,展开后就是:
// GCObject *next; lu_byte tt; lu_byte marked
// 所有的GC对象都以这个开始
CommonHeader;
// 和快速判断元方法有关,这里可以先略过
lu_byte flags;
// 哈希部分的长度对数:1 << lsizenode 才能得到实际的size
lu_byte lsizenode;
// 数组部分的长度
unsigned int sizearray;
// 数组部分,为TValue组成的数组
TValue *array;
// 哈希部分,为Node组成的数组,见下面Node的说明
Node *node;
// lastfree指明空闲槽位
Node *lastfree;
// 元表:每个Table对象都可以有独立的元表,当然默认为NULL
struct Table *metatable;
// GC相关的链表,这里先略过
GCObject *gclist;
} Table;
typedef struct Node {
TValue i_val; // value为TValue
TKey i_key; // key为TKey,看下面
} Node;
// TKey其实是一个联合,它可能是TValue,也可能是TValue的内容再加一个next字段。
typedef union TKey {
// 这个结构和TValue的差别只是多了一个next,TValuefields是一个宏,见下面
struct {
// 这部分和TValue的内存是一样的
TValuefields;
// 为了实现冲突结点的链接,当两个结点的key冲突后,用next把结点链接起来
int next; /* for chaining (offset for next node) */
} nk;
TValue tvk;
} TKey;
// TValue包括的域
#define TValuefields Value value_; int tt_插入数据时,如果table已经满了,lua会重新设置数据部分或者哈希表的大小,容量成倍增加。
查找元素时,根据key值的不同,由不同的处理:
- 如果key是nil,则直接返回nil。
- 如果key是整数类形,则调用luaH_getint来处理,因为整形key可能会在array中取值。
- 如果key是浮点数类型,则首先判断key是否能转化为整数,如果是则调用luaH_getint,否则调用getgeneric。
- 如果key是短字符串类型,则调用luaH_getshortstr来处理。(其实这个case有点不理解,短字符串也可以交给getgeneric来处理)
- 其他类型的key,都使用getgeneric来处理。
luaH_getint
- 如果key值是int类型,如果在数组索引范围内,就直接在数组中查找返回。
- 否则就用hash的方法去寻找
- 找不到,返回nil
getgeneric则会直接调用hash方法查找。
luaH_set TODO
Rehash |
|