Administrator
发布于 2024-07-17 / 19 阅读
0
0

Xlua 调用+Xlua源码解析

GitHub - Tencent/xLua: xLua is a lua programming solution for C# ( Unity, .Net, Mono) , it supports android, ios, windows, linux, osx, etc.

xlua的基本调用

using UnityEngine;
using System.Collections;
using XLua;

namespace Tutorial
{
    public class ByString : MonoBehaviour
    {
        LuaEnv luaenv = null;
        // Use this for initialization
        void Start()
        {
            luaenv = new LuaEnv();
            luaenv.DoString("print('hello world')");
        }

        // Update is called once per frame
        void Update()
        {
            if (luaenv != null)
            {
                luaenv.Tick();
            }
        }

        void OnDestroy()
        {
            luaenv.Dispose();
        }
    }
}

Xlua实现最简单的热更新

1.安装xlua且开启Hotfix在project->Player处点other Settings

当工程中 Script /脚本文件有变更的时候

例如增加/删除/增加标签[Hotfix]/增加函数标签[LuaCallCSharp]

注意:只要有任何变动,就需要重新生成和注入脚本

另外如果出现花式报错时,就清除所有,并重新生成和注入

2.自定义Loader

为了测试在工程中调用 Lua 文件

新建一个脚本 ChinarHotFix ,并挂载当前场景

新建一个 Lua 文件放在一个目录下(因为我们自定义的Loader,是要指定该目录中的Lua文件的)

Lua文件的后缀名,要与函数中的 路径后缀 保持一致

C#代码

using System.IO;
using System.Text;
using UnityEngine;
using XLua;


/// <summary>
/// 热更新测试脚本——该脚本新建一个 Lua环境,并完成对 Lua脚本的指向调用
/// </summary>
public class ChinarHotFix : MonoBehaviour
{
    private LuaEnv luaEnv; //声明一个Lua环境对象


    void Start()
    {
        luaEnv = new LuaEnv();                     //实例化一个
        luaEnv.AddLoader(ChinarLoader);            //添加Loader
        luaEnv.DoString("require'ChinarLuaTest'"); //引用名为: ChinarLuaTest 的 Lua 脚本
    }


    /// <summary>
    /// 自定义一个 Loader 
    /// </summary>
    /// <param name="luaFileName">Lua文件名</param>
    /// <returns>字节组</returns>
    private byte[] ChinarLoader(ref string luaFileName)
    {
        return Encoding.UTF8.GetBytes(File.ReadAllText(@"C:\Users\Administrator\Desktop\ChinarXLuaDemo\LuaFiles\" + luaFileName + ".lua")); //读指定目录下的 Lua 文件,并返回字节组
    }
}

lua代码

print("Chinar")

释放Lua环境之前

运行后需要对 LuaEnv 环境进行释放

释放 LuaEnv 之前还要反注册,那些注册到C#中的回调函数

不然就会造成 LuaEnv 已经释放了,但是 Xlua 机制中的 Delegate 中的函数回调并没有被释放

直接新建一个 Lua 脚本,专门管理并释放 Delegate 中的函数的释放

例如:你通过Lua脚本 xlua.hotfix(CS.ChinarTest,'ChinarTestMethod',function) 注册到 C# 中的函数

则通过新建一个 Lua 脚本ChinarDispose.lua,写上 xlua.hotfix(CS.ChinarTest,'ChinarTestMethod',nil) 质空,即可完成释放

注意:每通过Lua脚本修改一个C#函数,都需要在ChinarDispose.lua脚本中添加对应函数的释放/删除操作

声明。

委托/回调C#委托、事件、回调函数及结合运用详解_c# 事假、委托和回调组合用法-CSDN博客

更改后,可进行双清、释放的 C# 文件

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using System.IO;
using System.Text;

public class ChinarHotFix : MonoBehaviour
{
    // Start is called before the first frame update
    private LuaEnv luaEnv; //声明一个Lua环境对象
    void Start()
    {
        luaEnv = new LuaEnv();         //实列化
        luaEnv.AddLoader(ChinarLoader); //添加一个Loader
        luaEnv.DoString("require'ChinarLuaTest'"); //引用名为:chinarLuaTest的lua脚本
    }
    /// <summary>
    /// 自定义一个 Loader 
    /// </summary>
    /// <param name="luaFileName">Lua文件名</param>
    /// <returns>字节组</returns>
    private byte[] ChinarLoader(ref string luaFileName)
    {
        return Encoding.UTF8.GetBytes(File.ReadAllText(@"E:\test\Assets\G_Resources\Scripts\Lua\test lua--csharp\" + luaFileName + ".lua")); //读指定目录下的 Lua 文件,并返回字节组
    }
    /// <summary>
    /// 释放掉函数
    /// 此函数会在 OnDestroy 之前调用
    /// </summary>
    private void OnDisable()
    {
        luaEnv.Dostring("require'ChinarDispose'");
    }
    private void OnDestroy()
    {
        luaEnv.Dispose();
    }
}
                    

lua

xlua.hotfix(CS.ChinarTest,'ChinarTestMethod',nil)
--xlua.hotfix(CS.ChinarTest1,'ChinarTestMethod1',nil)只要有修改C#中对应函数,都需要在这里完成释放操作
--xlua.hotfix(CS.ChinarTest2,'ChinarTestMethod2',nil)
--xlua.hotfix(CS.ChinarTest3,'ChinarTestMethod3',nil)

私有变量

正常情况,我们是无法直接通过 Lua 直接访问到 C# 中的私有变量的

然而 Xlua 机制为我们提供了一个非常简便的解决方案

当我们需要访问某个 C# 类中的私有变量时,只需要在 Lua 代码中加上一句话

xlua.private_accessible(CS.ChinarTest)

然后,我们就可以访问到 C# ChinarTest类中的私有变量了

using UnityEngine;
using XLua;

[Hotfix]
public class ChinarTest: MonoBehaviour
{
    private int Number = 666;//私有数字变量--例如这么一个私有变量
}

lua加上

xlua.private_accessible(CS.ChinarTest)--只有加上这句话,才可以访问C#对应类中的私有变量

Lua与C#的相互调用(xLua)_c# lua-CSDN博客

C#与Lua数据通信机制

C#如何调用xlua

无论是Lua调用C#,还是C#调用Lua,都需要一个通信机制,来完成数据的传递。而Lua本身就是由C语言编写的,所以它出生自带一个和C/C++的通信机制。

Lua和C/C++的数据交互通过栈进行,操作数据时,首先将数据拷贝到"栈"上,然后获取数据,栈中的每个数据通过索引值进行定位,索引值为正时表示相对于栈底的偏移索引,索引值为负时表示相对于栈顶的偏移索引,索引值以1或-1为起始值,因此栈顶索引值永远为-1, 栈底索引值永远为1 。 “栈"相当于数据在Lua和C/C++之间的中转地。每种数据都有相应的存取接口

.dill科普:
.dll文件是Dynamic Link Library(动态链接库)文件的缩写,它是一种共享库文件,包含了程序所需的代码和数据。与静态链接库不同,动态链接库可以在程序运行时动态加载,使得程序的内存占用更小,同时也方便了程序的更新和维护。

动态链接库和静态链接库的区别

静态链接库(.LIB) 由函数和数据编译而成的一个二进制文件。使用时,在编译链接阶段,由链接器从库中复制这些函数和数据,并把他们与应用程序的其他模块组合起来创建最终的可执行文件。由于静态链接库中的程序和数据已经被复制并应用到可执行文件中,因此发布产品时不需要发布使用的静态库文件。

动态链接库 (.DLL) 包含被可执行程序或其他DLL调用来完成某项工作的函数,不可以直接运行,也不可以接收消息。动态链接库一半包含两个文件,引入库文件(.LIB)和动态链接库文件(.DLL)。使用时,在编译链接阶段,只需要链接引入库文件,动态链接库中的函数和数据并不复制到程序中,在运行阶段去访问DLL文件中的函数。由于动态链接库中函数和数据并没有被复制,因此发布产品时,必须包含动态链接库文件。

引入库文件 (.LIB): 包含该动态链接库包含的函数和变量的符号名。注意:虽然引入库文件和静态链接库文件的后缀名相同(.LIB),但是他们之间有着本质的区别,不可混淆。

动态链接库文件 (.DLL): 包含该动态链接库实际的函数和数据。在程序运行阶段,加载该文件,并将该文件映射到进程地址空间中,然后访问该文件中的相应函数。

详解释看这个为什么动态链接.dll和.lib都需要(详解静、动态链接库)_windows的动态链接库为什么多个lib文件-CSDN博客

传递Lua table到C#

以TestXLua类为例来看Lua table是如何被传递的,TestXLua有一个LuaTable类型的静态变量,LuaTable是C#这边定义的一个类,封装了一些对Lua table的操作

  public class TestXLua
{
    public static LuaTable tab;
}

在点击Generate Code之后,部分生成代码如下所示。为tab变量生成了对应的set和get包裹方法

static int _g_get_tab(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        translator.Push(L, TestXLua.tab);
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 1;
}

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _s_set_tab(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        TestXLua.tab = (XLua.LuaTable)translator.GetObject(L, 1, typeof(XLua.LuaTable));
    
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return 0;
}

给tab静态变量赋一个lua table ,table中包含一个 num=1的key-value

local t={

	num=1

}

CS.TextXLua.tab=t;

上述代码在赋值时,最终会调用到_s_set_tab包裹方法(具体原理可以查看这里),Lua这边调用_s_set_tab前,会先将参数table t压入到栈中,因此_s_set_tab内部需要通过translator.GetObject拿到这个table,并将其赋值给tab静态变量

// ObjectTranslator.cs
public object GetObject(RealStatePtr L, int index, Type type)
{
    int udata = LuaAPI.xlua_tocsobj_safe(L, index);

    if (udata != -1)
    {
        // 对C#对象的处理
        object obj = objects.Get(udata);
        RawObject rawObject = obj as RawObject;
        return rawObject == null ? obj : rawObject.Target;
    }
    else
    {
        if (LuaAPI.lua_type(L, index) == LuaTypes.LUA_TUSERDATA)
        {
            GetCSObject get;
            int type_id = LuaAPI.xlua_gettypeid(L, index);
            if (type_id != -1 && type_id == decimal_type_id)
            {
                decimal d;
                Get(L, index, out d);
                return d;
            }
            Type type_of_struct;
            if (type_id != -1 && typeMap.TryGetValue(type_id, out type_of_struct) && type.IsAssignableFrom(type_of_struct) && custom_get_funcs.TryGetValue(type, out get))
            {
                return get(L, index);
            }
        }
        return (objectCasters.GetCaster(type)(L, index, null));
    }
}

GetObject方法负责从栈中获取指定类型的对象,对于LuaTable类型是通过objectCasters.GetCaster获取转换器后,通过转换器函数转换得到

// ObjectTranslator.cs
public ObjectCast GetCaster(Type type)
{
    if (type.IsByRef) type = type.GetElementType();  // 如果是按引用传递的,则使用引用的对象的type

    Type underlyingType = Nullable.GetUnderlyingType(type);
    if (underlyingType != null)
    {
        return genNullableCaster(GetCaster(underlyingType)); 
    }
    ObjectCast oc;
    if (!castersMap.TryGetValue(type, out oc))
    {
        oc = genCaster(type);
        castersMap.Add(type, oc);
    }
    return oc;
}

xLua已经默认在castersMap中为一些类型定义好了转换函数,其中就包括LuaTable类型

// ObjectCasters.cs
public ObjectCasters(ObjectTranslator translator)
{
    this.translator = translator;
    castersMap[typeof(char)] = charCaster;
    castersMap[typeof(sbyte)] = sbyteCaster;
    castersMap[typeof(byte)] = byteCaster;
    castersMap[typeof(short)] = shortCaster;
    castersMap[typeof(ushort)] = ushortCaster;
    castersMap[typeof(int)] = intCaster;
    castersMap[typeof(uint)] = uintCaster;
    castersMap[typeof(long)] = longCaster;
    castersMap[typeof(ulong)] = ulongCaster;
    castersMap[typeof(double)] = getDouble;
    castersMap[typeof(float)] = floatCaster;
    castersMap[typeof(decimal)] = decimalCaster;
    castersMap[typeof(bool)] = getBoolean;
    castersMap[typeof(string)] =  getString;
    castersMap[typeof(object)] = getObject;
    castersMap[typeof(byte[])] = getBytes;
    castersMap[typeof(IntPtr)] = getIntptr;
    //special type
    castersMap[typeof(LuaTable)] = getLuaTable;
    castersMap[typeof(LuaFunction)] = getLuaFunction;
}

LuaTable对应的转换函数是getLuaTable

// ObjectCasters.cs
private object getLuaTable(RealStatePtr L, int idx, object target)
{
    if (LuaAPI.lua_type(L, idx) == LuaTypes.LUA_TUSERDATA)
    {
        object obj = translator.SafeGetCSObj(L, idx);
        return (obj != null && obj is LuaTable) ? obj : null;
    }
    if (!LuaAPI.lua_istable(L, idx))
    {
        return null;
    }
    // 处理普通table类型
    LuaAPI.lua_pushvalue(L, idx);
    return new LuaTable(LuaAPI.luaL_ref(L), translator.luaEnv);
}

getLuaTable的主要逻辑是将idx处的table通过luaL_ref添加到Lua注册表中并得到指向该table的索引,然后创建LuaTable对象保存该索引。也就是说Lua table在C#这边对应的是LuaTable对象,它们之间通过一个索引关联起来,这个索引表示Lua table在Lua注册表中的引用,利用这个索引可以获取到Lua table。拿到Lua table后,就可以继续访问Lua table的内容了

// CS测试代码
int num = TestXLua.tab.Get<int>("num");

对Lua table的访问操作都被封装在LuaTable的Get方法中


评论